Updated
I use material ui next, Table component, and Table is stateless component, and also I have constructor component EnhancedTable. Also in Table component when I go map through data, I need to check if selected props(array) has the same item as on data array .
How should I check const isSelected = selected.includes(item) when I map through data array. Also when I click I catch error that selected.includes is not a function
unselectSelected={() => onSelect(selected =>{
debugger
console.log(selected)})}
Table component
let defaultCellRenderer = ({item, key}) =>
item[key]
const Table = props => {
const {data, columns, children, selectable, order, selected, onSelect, onDelete, onSearch, onDuplicate, onSort, search, onUnselectAll} = props
return (
<div>
{selectable &&
<EnhancedTableToolbar
numSelected={selected.length}
handleSearch={() => onSearch(data)}
value={search}
// unselectSelected={() => onUnselectAll(selected)}
unselectSelected={() => onSelect(selected =>{
debugger
console.log(selected)})}
deleteSelected={() => onDelete(selected)}
duplicateSelected={() => onDuplicate(selected)}
/> }
<MuiTable >
{selectable
? <EnhancedTableHead
columns={columns}
numSelected={selected.length}
order={order}
// onSelectAllClick={() => onSelect(Object.keys(new Array(selected.length).fill(0)))}
onSelectAllClick={() => onSelect(
console.log('click')
)}
onRequestSort={property => event => onSort(event, property)}
/>
: <TableHead>
<TableRow >
{columns.map(({label}) =>
<TableCell>
{label}
</TableCell>)}
</TableRow>
</TableHead>
}
<TableBody>
{data.map((item, index) => {
// debugger
const isSelected = selected.includes(item)
debugger
return (
selectable
? <TableRow
hover
onClick={() => onSelect(isSelected
? selected.filter(x => x != item)
: [...selected, item])}
role="checkbox"
aria-checked={isSelected}
tabIndex="-1"
key={index}
selected={isSelected}
>
<TableCell checkbox>
<Checkbox checked={isSelected}/>
</TableCell>
{columns.map(({key, cellRenderer, numeric}) =>
<TableCell key={key} numeric={numeric}>
{(cellRenderer || defaultCellRenderer)({item, key})}
</TableCell>)}
</TableRow>
: <TableRow hover>
{columns.map(({key, cellRenderer, numeric}) =>
<TableCell numeric={numeric}>
{(cellRenderer || defaultCellRenderer)({item, key})}
</TableCell>)}
</TableRow> )
})}
</TableBody>
</MuiTable>
</div>
)
}
EnchancedTable
class EnhancedTable extends Component {
state = {
selected: [],
data,
order: {
direction: 'asc',
by: 'deviceID',
},
search: '',
}
handleRequestSort = (event, property) => {
const orderBy = property
let order = 'desc'
if (this.state.order.by === property && this.state.order.direction === 'desc') {
order = 'asc'
}
const data = this.state.data.sort(
(a, b) => order === 'desc' ? b[orderBy] > a[orderBy] : a[orderBy] > b[orderBy],
)
this.setState({ data, order })
}
deleteSelected = () => {
const {data, selected} = this.state
this.setState({data: data.filter(item => !selected.includes(item)), selected: []})
}
handleSearch = event => {
const {data} = this.state
let filteredDatas = []
filteredDatas = data.filter(e => {
let mathedItems = Object.values(e)
let returnedItems
mathedItems.forEach(e => {
const regex = new RegExp(event.target.value, 'gi')
if (typeof e == 'string')
returnedItems = e.match(regex)
})
return returnedItems
})
this.setState({filterData: filteredDatas, search: event.target.value})
}
unselectSelected = () => {
this.setState({selected: []})
}
duplicate = () => {
const {data, selected} = this.state
this.setState({
// data: data.filter((item, index) => selected.includes(index)).reduce((p, c) => [...p, {...data[index]}], data),
data : [...data, ...selected],
selected: [],
})
}
handleSelectChange = selected => {
this.setState({selected})
}
render = () => {
const {selected, data, search, order} = this.state
return (
<Paper>
<Table
data={data}
selectable
columns={columns}
order={order}
search={search}
selected={selected}
onSelect={this.handleSelectChange}
onDelete= {this.deleteSelected}
onSort={this.handleRequestSort}
onDuplicate={this.duplicate}
onSearch={this.handleSearch}
// test unselect
onUnselectAll = {this.unselectSelected}
/>
</Paper>)
}
}
It seems to me that you've got several things wrong ( and a few things that are maybe just strange) here. Because I don't know the structure of data in your example I can't tell you if your check here for const isSelected = selected.includes(item) is working properly or not. If an item in your data array is a single value this will work. For example if you had:
const data = [ 1,2,3,4 ]
And
const selected = [1,2]
You could do the check the way you are currently doing and it would work. const isSelected = selected.includes(item)
But if your data is for example an object array like:
const data = [ {id: 1},{id: 2},{id: 3} ]
And
const selected = [{id:1}]
Then you would need to check the id value like this:
const isSelected = selected.some((i) => i.id === item.id )
And also it looks like you are not setting selected to a new value when you filter it in your onClick method which is probably why you're getting the selected.includes is not a function error. You should be doing this instead.
onClick={() => onSelect(isSelected ? selected = selected.filter(x => x != item) : [...selected, item])}
I'm not sure why you don't just have a your onSelect method handle both the select and unselect of a row. Something like:
onClick={() => this.onSelect()}
And then outside your render method:
onSelect() {
isSelected ? selected = selected.filter(x => x != item) : [...selected, item]
}
Hope that helps.
Related
I'm building a dynamic table using React-Table and i want to add a new row of editable cells.
At the moment i can add new row but only when i press the global edit button i can edit it, instead i want to add a row which would be editable at first.
This is my code -
Main component
function StyledTable() {
useEffect(() => {
dispatch(getData(mokeJsonData));
}, []);
const [datatoColumns] = useState(columnDataaa.slice(1));
const [skipPageReset, setSkipPageReset] = useState(false);
const data = useSelector((state) => state.dataReducer.data);
const dispatch = useDispatch();
const columns = useMemo(
() => [
{
Header: "",
id: "expander",
Cell2: ({ row }) => {
return (
<span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? "-" : "+"}
</span>
);
},
Cell: () => {
return <div></div>;
},
},
{
Header: columnDataaa[0].Header,
accessor: columnDataaa[0].accessor,
Cell: ({ value, row }) => {
return (
<FlexDiv>
<HighlightOffIcon
style={{ marginRight: "5px", color: "grey", width: "20px" }}
onClick={() => dispatch(deleteRow(row.index))}
/>
{value}
</FlexDiv>
);
},
},
...datatoColumns,
],
[]
);
useEffect(() => {
setSkipPageReset(false);
}, [data]);
const renderRowSubComponent = useCallback(
({ row }) => ({
values: row.original.addInfo && row.original.addInfo,
}),
[]
);
return (
<Styles>
<h1>הגדרת מנהל</h1>
<Table
columns={columns}
skipPageReset={skipPageReset}
renderRowSubComponent={renderRowSubComponent}
/>
</Styles>
);
}
export default StyledTable;
Editable Cell
const EditableCell = ({
value: initialValue,
row: { index },
column: { id, editable, type, width, valueOptions },
}) => {
const [value, setValue] = useState(initialValue);
const onChange = (e) => {
setValue(e.target.value);
};
const dispatch = useDispatch();
const onBlur = () => {
if (value === "") return alert("requiredddd");
return dispatch(updateMyData({ index, id, value }));
};
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
if (type === "singleSelect")
return (
<InputSelect
value={value}
onChange={onChange}
onBlur={onBlur}
style={{ width: width }}
>
{valueOptions.map((item, i) => {
return (
<option value={item.label} key={i}>
{item.label}
</option>
);
})}
</InputSelect>
);
if (type === "date")
return (
<DatePicker
style={{ width: width }}
type="date"
disabled={editable === false}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
);
return (
<input
style={{ width: width }}
disabled={editable === false}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
);
};
export default EditableCell;
Add Row function
addRow: (state, action) => {
const obj = {};
action.payload.slice(1).forEach((item) => {
obj[item.accessor] = '';
});
if (
obj &&
Object.keys(obj).length === 0 &&
Object.getPrototypeOf(obj) === Object.prototype
)
return;
else {
state.data.splice(0, 0, obj);
state.originalData = state.data;
}
},
Thanks
Pass the state variable and method to the useTable() root hook. custom plugin hooks and other variables/methods maintaining the component state are returned from the table instance. These you can later retrieve from anywhere you want.
const {
// all your hooks...
} = useTable(
{
columns,
data,
// all your other hooks...
updateMyData,
// pass state variables so that we can access them in edit hook later
editableRowIndex, // index of the single row we want to edit
setEditableRowIndex // setState hook for toggling edit on/off switch
},
// other hooks...
(hooks) => {
hooks.allColumns.push((columns) => [
// other hooks such as selection hook
...columns,
// edit hook
{
accessor: "edit",
id: "edit",
Header: "edit",
Cell: ({ row, setEditableRowIndex, editableRowIndex }) => (
<button
className="action-button"
onClick={() => {
const currentIndex = row.index;
if (editableRowIndex !== currentIndex) {
// row requested for edit access
setEditableRowIndex(currentIndex);
} else {
// request for saving the updated row
setEditableRowIndex(null); // keep the row closed for edit after we finish updating it
const updatedRow = row.values;
console.log("updated row values:");
console.log(updatedRow);
// call your updateRow API
}
}}
>
{/* single action button supporting 2 modes */}
{editableRowIndex !== row.index ? "Edit" : "Save"}
</button>
)
}
]);
}
);
you can found example from bellow link
github repo link: https://github.com/smmziaul/only-one-row-editable
code sandbox link: https://codesandbox.io/s/github/smmziaul/only-one-row-editable
I have a list of buttons and they are multi selectable. when I select the buttons I want to add , it will be added to the array perfectly and turned to blue and when I click on a already selected button it should be get removed from the array and turned to white but it doesn't. Below shows what I tried so far.
The first array (products) is to save the API data. Second one is to save the selected products.
const [products, setProducts] = useState([]);
const [selectedProducts, setselectedProducts] = useState<any>([]);
{products.length !== 0 ? (
products?.map(
(item: any, index) => (
<SButton
key={item.key}
label={item.value}
onClick={() => {
selectedProducts(item);
}}
isSelected={item.selected === "YES"}
/>
)
)
) : (
<p>No products</p>
)}
function selectedProducts(item:any){
if(selectedProducts.length !== 0){
selectedProducts.map((selecteditem:any)=>{
if(selecteditem.key == item.key ){
item.selected = "NO";
setselectedProducts(selectedProducts.filter((item: any )=> item.key !== selecteditem.key))
}else{
item.selected = "YES";
setselectedProducts([...selectedProducts, item]);
}
})
}else{
setselectedProducts([...selectedProducts, item]);
item.selected = "YES";
}
}
How about something like this?
const [products, setProducts] = useState([]);
const [selectedProduct, setSelectedProduct] = useState();
{(products.length > 0) ? (
<Fragment>
{products.map((item)=>{
const {key, value, selected } = item;
return (
<SButton
key={key}
label={value}
onClick={() => {
setSelectedProduct(item);
const newState = !selected;
products.forEach((p)=>{
if (p.key === item.key) p.selected = newState;
});
setProducts([...products]);
}}
isSelected={selected}
/>
);
})}
</Fragment>
): (
<p>No products</p>
)}
First, you are using selectedProducts both as function name and name of selected products state.
Second, you should not assign values to item. Use spread operator instead.
Also, you can access the previous state from setState instead of using state directly.
function removeFromSelectedProducts(item: any) {
// Set selected to 'NO' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'NO' } : product
)
)
// Remove product from selectedProducts
setSelectedProducts((prevSelectedProducts) =>
prevSelectedProducts.filter((product) => product.key !== item.key)
)
}
function addToSelectedProducts(item: any) {
// Set selected to 'YES' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'YES' } : product
)
)
// Add item to selectedProducts
setSelectedProducts((prevSelectedProducts) => [...prevSelectedProducts, { ...item, selected: 'YES'}])
}
function selectProduct(item: any) => {
if (selectedProducts.some((product) => product.key === item.key)) {
removeFromSelectedProducts(item)
} else {
addToSelectedProducts(item)
}
}
You can simplify this using useReducer hook instead of using separate functions for addition & removal of selected products.
I am trying to filter ResourceItems in my ResourceList by their tag. For exmaple, if a user searches for the tag "Sports", all items with the this tag should be returned.
I have been utilising this example to produce this, but it doesn't actually have any functionality when the user enters a tag to filter by.
This is my code so far, in which I don't get any items back:
const GetProductList = () => {
// State setup
const [taggedWith, setTaggedWith] = useState(null);
const [queryValue, setQueryValue] = useState(null);
// Handled TaggedWith filter
const handleTaggedChange = useCallback(
(value) => setTaggedWith(value),
[],
);
const handleTaggedRemove = useCallback(() => setTaggedWith(null), []);
const handleQueryRemove = useCallback(() => setQueryValue(null), []);
const handleFilterClear = useCallback(() => {
handleTaggedRemove();
handleQueryRemove();
}, [handleQueryRemove, handleTaggedRemove]);
const filters = [
{
key:'taggedWith',
label:'Tagged With',
filter: (
<TextField
label="Tagged With"
value={taggedWith}
onChange={handleTaggedChange}
labelHidden
/>
),
shortcut: true,
}
];
const appliedFilters = !isEmpty(taggedWith)
? [
{
key: 'taggedWith',
label: disambiguateLabel('taggedWith', taggedWith),
onRemove: handleTaggedRemove,
},
]
: [];
const filterControl = (
<Filters
queryValue={queryValue}
filters={filters}
appliedFilters={appliedFilters}
onQueryChange={setQueryValue}
onQueryClear={handleQueryRemove}
onClearAll={handleFilterClear}
children={() => {
<div>Hello World</div>
}}
>
<div>
<Button onClick={() => console.log('New Filter Saved')}>Save</Button>
</div>
</Filters>
)
// Execute GET_PRODUCTS GQL Query
const { loading, error, data } = useQuery(GET_PRODUCTS);
if (loading) return "Loading products...";
if (error) return `Error = ${error}`;
// Return dropdown menu of all products
return (
<Frame>
<Page>
<Layout>
<Layout.Section>
<DisplayText size="large">WeBuy Valuation Tool</DisplayText>
</Layout.Section>
<Layout.Section>
<Card>
<Card.Section>
<div>
<Card>
<ResourceList
resourceName={{singular: 'product', plural: 'products'}}
items={data ? data.products.edges : ""}
renderItem={renderItem}
filterControl={filterControl}
>
</ResourceList>
</Card>
</div>
</Card.Section>
</Card>
</Layout.Section>
</Layout>
</Page>
</Frame>
)
function renderItem(item) {
const { id, title, images, tags } = item.node;
const media = (
<Thumbnail
source={ images.edges[0] ? images.edges[0].node.originalSrc : '' }
alt={ images.edges[0] ? images.edges[0].node.altText : '' }
/>
);
const resourceItem = (
<ResourceItem
id={id}
accessibilityLabel={`View details for ${title}`}
media={media}
>
<Stack>
<Stack.Item fill>
<h3><TextStyle variation="strong">{title}</TextStyle></h3>
<h2>{tags}</h2>
</Stack.Item>
<Stack.Item>
<AddMetafield id={id} />
<DeleteMetafield id={id} />
</Stack.Item>
</Stack>
</ResourceItem>
);
tags ? tags.forEach(tag => {
if (tag == "Sports") {
console.log("has tag")
return resourceItem
}
}) : console.log("Return")
}
function disambiguateLabel (key, value) {
switch(key) {
case 'taggedWith' :
return `Tagged with ${value}`;
default:
return value;
}
}
function isEmpty(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else {
return value === '' || value == null;
}
}
}
I Couldn't find any documentation on how to implement this, and I expected it to be built-in, but I ended up doing something like this:
//add "filter.apply" function to the filter object
const filters = [{...apply:(c:Item)=>...},isApplied=...]
const appliedFilters = filters.filter(f=>f.isApplied);
let filteredItems = pageData.Items;
for (const filter of appliedFilters){
filteredItems = filteredItems.filter(filter.apply)
}
if(queryValue && queryValue.length > 0){
filteredItems = filteredItems.filter((c:FBComment)=>c.text.includes(queryValue))
}
I'm making a filter function by checkbox. I made a reproduce like below. I changed values in array but checkbox checked status not change, what I missed? I think I must re-render list but it's also refill checked array to initial state. What should I do? Thanks!
import * as React from "react";
import "./styles.css";
import { Checkbox } from "antd";
const arr = [
{
name: "haha"
},
{
name: "haha2"
},
{
name: "hahaha"
}
];
const App = () => {
let [viewAll, setViewAll] = React.useState(true);
let checked = new Array(3).fill(true);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "white" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
haha
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
checked = checked.map(() => viewAll);
};
const handleFilter = (value, index) => {
setViewAll(false);
checked.map((_value, _index) => {
if (_index === index) {
return (checked[_index] = !checked[_index]);
} else {
return checked[_index];
}
});
console.log(checked);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>Check all</Checkbox>
{list}
</div>
);
};
export default App;
Here is codesanboxlink
You should create checked state. Check the code below.
let [viewAll, setViewAll] = React.useState(true);
let [checked, setChecked] = React.useState([true, true, true]);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "black" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
{value.name}
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
setChecked(() => checked.map(item => !viewAll));
};
const handleFilter = (value, index) => {
setViewAll(false);
setChecked(() =>
checked.map((_value, _index) => {
if (_index === index) return !checked[_index];
return checked[_index];
})
);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>
{checked}
</Checkbox>
{list}
</div>
);
codesandbox demo
You have to define checked array as a state value.
Right now your code is not firing render function because checked array is not props but also not state.
I have a data set where each object has a heading and an items array with multiple values. I need to be able to filter on the items while maintaining their heading. The filter component below works without the headings. The data output I want is something like:
If I filter on 'ap...'
Output:
fruits
apple
const FilterList = () => {
const [searchTerm, setSearchTerm] = useState("")
const [searchResults, setSearchResults] = useState([])
const data = [
{
heading: 'fruits',
items: [
{ item: 'apple' },
{ item: 'orange' },
{ item: 'peach' }
]
},
{
heading: 'veggies'
items: [
{ item: 'carrot' },
{ item: 'broccoli' },
{ item: 'spinach' },
]
}
]
const handleChange = e => {
setSearchTerm(e.target.value)
}
useEffect(() => {
let results = []
if (data && Object.prototype.hasOwnProperty.call(data[0], "heading")) {
data.forEach(item => {
results = item.values.filter(value =>
value.value.toLowerCase().includes(searchTerm)
)
})
setSearchResults(results)
}
}, [searchTerm])
return (
<div>
<input
type="text"
placeholder={'placeholder}
value={searchTerm}
onChange={handleChange}
/>
<Typography>
<strong>{heading}</strong>
</Typography>
<List>
{searchResults.map((value, i) => (
<ListItem key={i}>
{value.value}
</ListItem>
))}
</List>
</div>
)
}
Modify the useEffect :
useEffect(() => {
console.log("changed");
const newSearchResults = data.map(value => ({
heading: value.heading,
items: value.items.filter(item => item.item.includes(searchTerm))
}));
setSearchResults(newSearchResults);
}, [searchTerm]);
Change your return to:
return (
<div>
<input
type="text"
placeholder={"placeholder"}
value={searchTerm}
onChange={handleChange}
/>
<div>
{searchResults.map(value => {
if (value.items.length !== 0) {
return (
<>
<Typography>
<strong>{value.heading}</strong>
</Typography>
<List>
{value.items.map((item, i) => (
<ListItem key={i}>{item.item}</ListItem>
))}
</List>
</>
);
}
})}
</div>
</div>
const data = [{heading:'fruits',items:[{item:'apple'},{item:'orange'},{item:'peach'}]},{heading:'veggies',items:[{item:'carrot'},{item:'broccoli'},{item:'spinach'},{item: 'peach'}]}]
const filterData = (data, val) => {
const result = []
data.forEach(obj => {
let r = obj.items.filter(item => item.item.includes(val))
if(r.length > 0) {
result.push({
...obj,
items: r
})
}
})
return result
}
console.log(filterData(data, "ap"))
console.log(filterData(data, "pe"))
Hope this helps.
Created a sandbox: https://codesandbox.io/s/strange-robinson-tvl7d?file=/src/App.js
Explanation
In essence, two changes are required:
need to restructure your searchResults to have the same structure as data, with heading and items. Instead of just pushing the items found, we specify the corresponding heading also, in the searchResults array
// new filtering function
useEffect(() => {
const results = [];
data.forEach(cate => {
const { heading, items } = cate;
// remains as is
const filterdItems = items.filter(item =>
item.item.toLowerCase().includes(searchTerm)
);
// only if filtered items are found
// add BOTH category & filtered items to results
if (filterdItems.length) {
results.push({
heading,
items: filterdItems
});
}
});
setSearchResults(results);
}, [searchTerm]);
And then, update the render block to loop through the categories array and then the items inside each category
// updated render
{
searchResults.map((resultCate, i) => {
return (
<div>
<strong>{resultCate.heading}</strong>
{resultCate.items.map((item, idx) => (
<div key={idx}>{item.item}</div>
))}
</div>
);
})}