DataGrid selection not extractable - javascript

I'm using a DataGrid and I want to dispatch the selection to my redux store.
return (
<div style={{ height: '470px', width: '100%' }}>
<p>*{statement.multiplier}</p>
<DataGrid
rows={rows}
columns={columns}
checkboxSelection
disableSelectionOnClick
onSelectionModelChange={(ids) => {
const selectedIDs = new Set(ids);
const selectedRows = rows.filter((row) =>
selectedIDs.has(row.id),
);
setSelectedRows(selectedRows);
dispatch(setSelection(selectedRows))
}}
/>
<br/>
</div>
);
But for some reason, selectedRows never gets filled. I find this especially strange since I literally copy/pasted this from one of my earlier projects.
Does anyone know why this line:
const selectedRows = rows.filter((row) =>
selectedIDs.has(row.id),
);
Comes up empty every time?

Related

MUI : Out-of-range value `X` for the select component

I'm making a glossary where each of the definitions are a cards that can be flipped (CardFlip) I build an array where I send for each card, the data via props to my component "CardFlip" dealing with the actual construction of cards with my data
This is my first component sending everything :
<div>
{glossaire.map((carte, index) => (
<Fragment key={carte.dat_id}>
<CardFlip item={carte} tags={tags} />
</Fragment>
))}
</div>
First prop ,"item", contains information such as: a title, a definition, a search tag
Second prop, "tags", is the list of tags that a definition can have, a definition can have only one tag, right now only those tags are available : "Application", "Entreprise", "Technique" and "Télécom"
And here is the code for my second component (only the interesting part):
export default function CardFlip = ({ item, user, tags }) => {
// -- [Variables] --
// Flip front / back
const [isFlipped, setIsFlipped] = useState(false);
// Storage
const [titreDef, setTitreDef] = useState("");
const [definitionDef, setDefinitionDef] = useState("");
const [tagSelected, setTagSelected] = useState("");
// Flag for error
const [errorTitre, setErrorTitre] = useState(false);
const [errorDefinition, setErrorDefinition] = useState(false);
const [errorSelect, setErrorSelect] = useState(false);
console.log(item.dat_tag);
console.log(tags);
// -- [Handlers] --
// UPDATE
const handleTitre = (data) => {
setTitreDef(data);
setErrorTitre(false);
};
const handleDefinition = (data) => {
setDefinitionDef(data);
setErrorDefinition(false);
};
const handleSelect = (event) => {
const {
target: { value },
} = event;
setTagSelected(value);
setErrorSelect(false);
}
return (
<Grow in style={{ transformOrigin: "0 0 0" }} {...{ timeout: 1000 }}>
<div style={{ display: "flex", padding: "10px" }}>
<ReactCardFlip
isFlipped={isFlipped}
flipDirection="horizontal"
style={{ height: "100%" }}
>
<div
className={style.CardBack}
style={{ display: "flex", height: "100%" }}
>
<Card className={style.mainCard}>
<CardActions className={style.buttonFlipCard}>
<Tooltip title="Retour">
<IconButton
className={style.iconFlipCard}
disableRipple
onClick={() => setIsFlipped((prev) => !prev)}
>
<ChevronLeftIcon />
</IconButton>
</Tooltip>
</CardActions>
<CardContent>
<div className={style.divTagBack}>
<FormControl
sx={{width: "90%"}}
>
<InputLabel
id="SelectLabel"
sx={{display: "flex"}}
>
{<TagIcon />}
{" Tag"}
</InputLabel>
<Select
labelId="SelectLabel"
label={<TagIcon /> + " Tag"}
renderValue={(selected) => (
<Chip
onMouseDown={(event) => {
event.stopPropagation();
}}
key={selected}
label={selected}
icon={<TagIcon />}
/>
)}
defaultValue={item.dat_tag}
onChange={handleSelect}
>
{tags && tags.map((tag) => (
<MenuItem key={tag.dat_tag} value={tag.dat_tag}>
{tag.dat_tag}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</CardContent>
</Card>
</div>
</ReactCardFlip>
</div>
</Grow>
);
};
When the user returns the card, he can change the title, description and tag of the chosen card.
My problem is with the Select.
In order to display the selected tag before any modification, I display the tag in defaultValue={item.dat_tag}
(Also tried with value and not defaultValue)
Then with my second prop, I build the list of my menu.
This is where I get my warning (which seems to extend the rendering time of the page considerably (Since it load / render like +100 definitions, getting a warning for every definition)
MUI: You have provided an out-of-range value Entreprise for the select component.
Consider providing a value that matches one of the available options or ''. The available values are "".
This is an example of what a console.logs told me about my props :
item.dat_tag
Entreprise
tags
0: {dat_tag: "Applicatif"}
1: {dat_tag: "Entreprise"}
2: {dat_tag: "Technique"}
3: {dat_tag: "Télécom"}
I already looked at several posts saying to put in a string variable my tag data (item.dat_tag) or to display my menu when it is not empty. No change.

How to target single item in list with onClick when mapping JSON array in React

I have a JSON file with an array of objects and each object consists of "Question" and "Answer" (I'm creating an FAQ section). What I'm doing is mapping over the array and displaying the list of questions, which works just fine. Next to each question is an icon and I want the icon to change when I click on it but it is changing EVERY icon in the list instead of just that one item that was clicked on.
I'm using Material UI and hooks and this is how I have my handleClick set up:
const [click, setClick] = useState(true);
const handleClick = () => {
setClick(!click);
};
This is how I have the array mapping set up:
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
{faqdata.map((item) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{click ? <AddIcon /> : <RemoveIcon />}
</ListItemIcon>
<ListItemText primary={item.Question} onClick={handleClick} />
</ListItem>
))}
</List>
How can I make it to where the icon changes on only the list item that I click on instead of every list item in the list? Is my onClick in the incorrect spot? Any help would be greatly appreciated. Thanks!
Issue
You are using a single boolean value to store a "clicked" state, and all your mapped UI uses that single state to cue from.
Solution
Assuming you would like multiple items to be clicked, and also assuming your mapped data is static (i.e. the faqData isn't added to, removed from, or sorted) then using the mapped index to toggle the "clicked" state is acceptable. use an object to store "clicked" indices and update the handleClick callback to toggle the state. For this use case I like to make the callback a curried handler to enclose in scope the value I wish to use in the callback.
const [clickedIndex, setClickedIndex] = useState({});
const handleClick = (index) => () => {
setClickedIndex(state => ({
...state, // <-- copy previous state
[index]: !state[index] // <-- update value by index key
}));
};
...
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
{faqdata.map((item, index) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{clickedIndex[index] ? <AddIcon /> : <RemoveIcon />} // <-- check if index is truthy in clickedIndex state
</ListItemIcon>
<ListItemText
primary={item.Question}
onClick={handleClick(index)} // <-- pass index to handler
/>
</ListItem>
))}
</List>
Is this what you're looking for?
import { useState } from "react";
import "./styles.css";
const faqdata = [
{ Question: "Q1", Answer: "A1" },
{ Question: "Q2", Answer: "A2" },
{ Question: "Q3", Answer: "A3" },
{ Question: "Q4", Answer: "A4" }
];
const AddIcon = () => <span class="icon">+</span>;
const RemoveIcon = () => <span class="icon">☓</span>;
function ListItem({ d }) {
const [checked, setChecked] = useState(false);
return (
<li
onClick={() => {
setChecked(!checked);
}}
>
{checked ? <RemoveIcon /> : <AddIcon />}
{d.Question}
</li>
);
}
function List() {
return (
<ul>
{faqdata.map((d) => {
return <ListItem d={d} />;
})}
</ul>
);
}
You can try it out here
The problem with the current approach is that there's only one variable to store the added/removed status of every question. So, when the click boolean updates, it updates the state of all elements.
In the code shared above, the ListItem component is responsible for maintaining the added/removed status of each question separately. So, one item in the list can change without affecting the other.
It's one of my test. You should save selected ids and check if the id exists in that array.
const [selectedfaqdataIds, setSelectedfaqdataIds] = useState([]);
const handleSelect = (event, id) => {
const selectedIndex = selectedfaqdataIds.indexOf(id);
let newselectedfaqdataIds = [];
if (selectedIndex === -1) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(selectedfaqdataIds, id);
} else if (selectedIndex === 0) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(selectedfaqdataIds.slice(1));
} else if (selectedIndex === selectedfaqdataIds.length - 1) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(selectedfaqdataIds.slice(0, -1));
} else if (selectedIndex > 0) {
newselectedfaqdataIds = newselectedfaqdataIds.concat(
selectedfaqdataIds.slice(0, selectedIndex),
selectedfaqdataIds.slice(selectedIndex + 1)
);
}
setSelectedfaqdataIds(newselectedfaqdataIds);
};
{faqdatas.map((faqdata) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{selectedfaqdataIds.indexOf(faqdata.id) !== -1}? <AddIcon /> : <RemoveIcon />}
</ListItemIcon>
<ListItemText primary={faqdata.Question} onClick={(event) => handleSelect(event, faqdata.id)} />
</ListItem>
))}

React Context Provider with multiple values being updated in several places

I am new to React and Context and am trying to use a global context provider in React.
export const GlobalDataContext = React.createContext({ user: null, textSearchList:[] });
user is updated in the same file this way:
return (
<GlobalDataContext.Provider value={{ currUser: user, textSearchList: []}}>
{children}
</GlobalDataContext.Provider>
);
I want to use the same context provider to update the textSearchList for a search bar in another component like this:
<GlobalDataContext.Provider value={{textSearchList:this.state.splitSearchList}}>
<SearchBar
value={this.state.value}
onChange={(newValue) => {
this.setState({ value: newValue });
}}
onRequestSearch={() => {
this.setSplitList(this.state.value);
}}
style={{
margin: '0 auto',
maxWidth: 800
}}
/>
{children}
</GlobalDataContext.Provider>
The above code is calling this function:
setSplitList = (searchString) =>{
var splitString = this.state.value.split(" ");
this.state.splitSearchList= splitString;
}
I can see that this is not updating the global context because it does not cause a re-rendering at the consumer which is here:
<GlobalDataContext.Consumer >
{({textSearchList}) =>
<Query query={GET_POSTS} pollInterval={500}>
{({ data, loading }) => (
loading
? <Loading />
: <div>
{data.posts.filter(function(post){
console.log(`Filtering based on this: ${textSearchList}`);
return this.textContainsStrings(post.text, textSearchList)
}).map(post => (
<div key={post._id}>
<PostBox post={post} />
</div>
))}
</div>
)}
</Query>
}
</GlobalDataContext.Consumer>
Is this approach even possible? If so, then what might I be doing wrong?

Trying to update child component 1 with data from child component 2 without rerender child 2

I'm generating a heavy JSX array from a loop.
It creates a lot of table.
I would like to update a Badge with the data on a row selected. But it rerender all my tables. It's pretty long for updating a single badge.
I tried to use useMemo() to prevent the creation of the table if my data doesn't change, but the callback fonction from the parent does not update state.
A code example what i'm trying to do =>
function App() {
const [tableData, setTableData] = useState(null)
const [badgeData, setBadgeData] = useState(null)
const jsx = useMemo(() => createTable(tableData), [tableData])
function updateBadge(selectedRows) {
setBadgeData(addNewRow(selectedRows))
}
function createTable(data) {
let jsx = []
data.forEach((item) => {
jsx.push(<TableComponent data={data.var} onRowSelect={updateBadge}/>)
})
return jsx;
}
return (
<div>
<HandleDataGeneration setData={setTableData}/>
<MyBadgeComponent data={badgeData}/>
{jsx}
</div>
);
}
In this case, only the first call to updateBadge function rerender the parent, but not the nexts calls (i guess it's because i don't send the new props and the function is copied and not linked)
Maybe my architecture is bad, or maybe there is some solution for update this badgeComponent without rerender all my Tables. Thanks you for your help
EDIT:
TableComponent
const TableCompoennt = React.memo((props) => { // after edit but it was a classic fn
const classes = useStyles();
const [expanded, setExpanded] = useState(props.data ? `panel${props.i}` : false);
let disabled = false;
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
if (isNaN(props.data.var)) {
props.data.var = x
}
if (!props.data)
disabled = true;
return (
<ExpansionPanel TransitionProps={{ unmountOnExit: false }} expanded={expanded === `panel${props.i}`} onChange={handleChange(`panel${props.i}`)} disabled={disabled}>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon/>}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Tooltip title={props.data.var}>
<Typography className={classes.heading}>{props.data.var}-{props.data.var}</Typography>
</Tooltip>
{!disabled ?
<Typography
className={classes.secondaryHeading}>{expanded ? "click to hide data" : "click to display data"}</Typography> :
<Typography
className={classes.secondaryHeading}>no data</Typography>
}
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<MyTable data={props.data} var={props.var}
key={props.i} id={props.i} style={{width: "100%"}} updateBadge={props.updateBadge}/>
</ExpansionPanelDetails>
</ExpansionPanel>
)
})
MyTable
export default React.memo((props) => { // same here
const [open, setOpen] = useState(false);
const [rowData, setRowData] = useState(null);
const [rows, setRows] = useState(props.myRates);
calcTotal(rows);
useEffect(() => {
setRows(props.myRates)
}, [props]);
return (
<div style={{width: "100%"}}>
{(rows && rows.length) &&
<div style={{width: "100%"}}>
<Modal open={open} rowData={rowData} setRowData={setRowData}
setOpen={(value) => setOpen(value)}/>
<Paper style={{height: 400, width: '100%'}}>
<SimpleTable
columns={columns}
rows={rows}
handleRowClick={(row) =>{
setOpen(true);
setRowData(row);
}}
handleSelect={(row) => {
if (!row.selected)
row.selected = false;
row.selected = !row.selected;
props.updateBadge(row)
}}
/>
</Paper>
</div>}
</div>
);
})
SimpleTable
const SimpleTable = React.memo((props) => {
const classes = useStyles();
let dataLabel = generateLabel(props.columns);
function getRowData(row) {
props.handleRowClick(row);
}
return (
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
{dataLabel}
</TableRow>
</TableHead>
<TableBody>
{props.rows.map((row) => (
<TableRow key={row.unique_code} selected={row.selected} hover onClick={() => {getRowData(row)}}>
{generateRow(props.columns, row)}
<TableCell onClick={(event) => {
event.stopPropagation();
}
}>
<Checkbox onClick={(event) => {
event.stopPropagation();
props.handleSelect(row);
}}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
})
Instead of using useMemo inside the App Component, you must make use of React.memo for the TableComponent assuming its a functional component or extend it with React.PureComponent if its a class component
However in such a case, you must make sure that you are not recreating the updateBadge Function on each re-render. To make sure of that, use useCallback hook
Also don't forget to add a unique key to TableComponent's instances which are rendered from the loop
function App() {
const [tableData, setTableData] = useState(null)
const [badgeData, setBadgeData] = useState(null)
const updateBadge = useCallback((selectedRows) {
setBadgeData(addNewRow(selectedRows))
}, []);
return (
<div>
<HandleDataGeneration setData={setTableData}/>
<MyBadgeComponent data={badgeData}/>
{data.map((item) => {
return <TableComponent key={item.id} props={item} onRowSelect={updateBadge}/>)
})}
</div>
);
}
and in TableComponent
const TableComponent = React.memo((props) => {
// Rest of the TableComponent code
})
EDIT:
Adding a working demo based on your comment codesandbox: https://codesandbox.io/s/clever-fast-0cq32
Few changes
made of use of useCallback method and also converted state updater to use callback approach
Removed the logic for JSON.parse and JSON.stringify and instead stored the entire data in array. The reason for this is that everytime you use JSON.parse it returns you a new object reference and hence memo functionality fails in child since it just does a reference check. This will happen each time your component re-renders i.e on update of badge state.
if you still need to use JSON.parse and JSON.stringify, add a custom comparator that compares the data values deeply

multiple iteration is done for creating the generic filters

I am trying to develop a generic filter component which can have many fields to filter on like color,
size, price range etc and each field might have different types of elements like color may have
checkboxes, radio button and price range might have input element, dropdown etc. To support such varied
cases, I tried to go with this pattern but here I have to iterate the same things multiple times.
I am not sure of this data structure. If anyone has suggestion please help me to improve this code but
the main problem here is "multiple iteration". How can i improve this code?
const filterParams = {
field: {
id : 1, label : 'Field', content: <FieldFilter />
},
employee: {
id : 1, label : 'Employee', content: <Employee />
}
}
<Filter filterParams={filterParams} activeFilterParam="field" />
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
return (
<>
<Button secondary icon={filter} onClick={() => setShow(!show)}>Filter</Button>
{show && (
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List
render={() => {
return (
Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
)
}))
}} />
<Tabs.Panels>
{Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
)
})}
</Tabs.Panels>
</Tabs>
</Card.Content>
<Card.Footer>
<Button>
<Button.Content style={{ marginRight: 10 }}>Save</Button.Content>
<Button.Content secondary onClick={()=>setShow(!show)}>Cancel</Button.Content>
</Button>
</Card.Footer>
</Card>
)}
</>
)
}
If you're not liking the multiple calls to Object.keys(filterParams).map, you could move the loop to the top of the component function. Something like the below might work:
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
const {tabs, panels} = Object.keys(filterParams)
.reduce((acc, filterParam) => {
acc.tabs.push(
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
);
acc.panels.push(
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
);
return acc;
}, { tabs: [], panels: [] });
return (
...
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List render={() => tabs} />
<Tabs.Panels>
{panels}
</Tabs.Panels>
</Tabs>
...
</Card>
...
)
}
Note that I haven't run this - it likely won't be quite right, but should give the general idea...

Categories

Resources