React Data Table Component - getting Warning: Each child in a list should have a unique "key" prop - reactjs

I'm using React Data Table Component, and applied filter exactly same as here:
() => {
const [filterText, setFilterText] = React.useState('');
const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false);
const filteredItems = fakeUsers.filter(
item => item.name && item.name.toLowerCase().includes(filterText.toLowerCase()),
);
const subHeaderComponentMemo = React.useMemo(() => {
const handleClear = () => {
if (filterText) {
setResetPaginationToggle(!resetPaginationToggle);
setFilterText('');
}
};
return (
<FilterComponent onFilter={e => setFilterText(e.target.value)} onClear={handleClear} filterText={filterText} />
);
}, [filterText, resetPaginationToggle]);
return (
<DataTable
title="Contact List"
columns={columns}
data={filteredItems}
pagination
paginationResetDefaultPage={resetPaginationToggle} // optionally, a hook to reset pagination to page 1
subHeader
subHeaderComponent={subHeaderComponentMemo}
selectableRows
persistTableHead
/>
);
}
And now I'm getting error message as
Warning: Each child in a list should have a unique "key" prop.
Check the top-level render call using . It was passed a child from Sample.
in FilterComponent (at --Sample.js:288)
I do have the id value in my data, and I also read the article this problem has solved from data-table-component but I'm still on getting this warning.
What do I have to prevent this message?
Is there alternate way to turn it off this message?

try this,
const filteredItems = fakeUsers.filter(
item => item.name && item.name.toLowerCase().includes(filterText.toLowerCase()),
);
let records = filteredItems.map((d) => ({ ...d, key: d.ID }));
..............
return (
<DataTable
title="Contact List"
columns={columns}
data={records}
pagination
paginationResetDefaultPage={resetPaginationToggle} // optionally, a hook to reset pagination to page 1
subHeader
subHeaderComponent={subHeaderComponentMemo}
selectableRows
persistTableHead
/>

I solved this problem, and now found out why I was the only one who faced this issue.
I was using another Memo for eanbling the control button.
The warning message was came because, there are 2 'memo'es.
So, I put diffrent key pros to each 'Memo'es as below
--
<FilterComponent key="func_filter" onFilter={e => setFilterText(e.target.value)} onClear={handleClear} filterText={filterText} />
const actionsMemo = useMemo(() =>
<ExportBtn key="func_excel" onExport={() => downloadCSV({rows})} />, [{rows}]);
Then warning message disappeared

Related

I don't get the expected value of a react props in child component

I'm trying to manage some clients in a react js application (that I'm maintaining), it was first written in classes components but I'm now adding a functional child component and im not sure if this is the source of the problem or not (react-table examples only use functional component)
I have a main component that will do the data GET from a rest API and save it in state "entries" then I passe it to a child component as a props to render the data in a react-table, the problem is in this section as I have some buttons to edit and delete the data in react-modal, when I try access the props.entries after the buttons clicks I have an empty array of props.entries.
Here's the sandbox of the issue : https://codesandbox.io/s/stale-prop-one-forked-r6cevx?file=/src/App.js
I did a console.log when the delete button is clicked, and you can see that en entries array is empty.
You need to pass the showEditModal & showEditModal in useMemo dependency array. Since you dependency array is empty, when you click on edit or delete, it just points to the old function reference and it have the old entries value.
You can check the entries values by console.log.
Hope this solve your problem
const showEditModal = useCallback(
(client_id) => {
const tmpClient = props.entries.filter(function (el) {
return el._id === client_id;
})[0];
setClient(tmpClient);
setEditModal(true);
console.log('aaa', props);
console.log(client_id);
console.log(props.entries);
console.log(tmpClient);
},
[props.entries]
);
const showDeleteModal = useCallback(
(client_id) => {
console.log('showDeleteModal entries : ', entries);
const tmpClient = entries.filter(function (el) {
return el._id === client_id;
})[0];
setClient(tmpClient);
setDeleteModal(true);
console.log('Delete', entries);
console.log(client_id);
console.log(tmpClient);
},
[props.entries]
);
const columns = React.useMemo(
() => [
{
Header: 'fact',
accessor: 'fact'
},
{
Header: 'Actions',
accessor: 'length',
Cell: ({ cell }) => (
<>
{cell.row.values.length}
<Tooltip title='Supprimer' placement='top'>
<IconButton
variant='outlined'
id={cell.row.values._id}
onClick={() => showDeleteModal(cell.row.values.length)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
<Tooltip title='Modifier' placement='top'>
<IconButton
variant='outlined'
id={cell.row.values.length}
onClick={() => showEditModal(cell.row.values.length)}
>
<EditIcon />
</IconButton>
</Tooltip>
</>
)
}
],
[showEditModal, showDeleteModal]
);

React Hook "useEffect" is called conditionally, in supposedly simple get-display-retrieve inputs component

Please help,
I am going around in circles (new to React).
I either get 'React Hook "useState" is called conditionally' and if I change it to be the beneath code,
I get 'React Hook "useEffect" is called conditionally'.
The idea is to
retrieve the data via useQuery and the db record id pased in via props.
if that data is null, e.g. the query used the id=0 (like an insert not an update of a record), then deviceObject to the empty record, else to the retrieved data.
Set 'deviceObject' into state.
i.e. The order is important, but setRow should only be called once, not multiple times, which leads to react crashing with too many renders.
export default function DeviceModal(props) {
const dataRowId = props.dataRowId;
const classes = useStyles();
const [row, setRow] = useState('')
const device = useQuery(getDevice_query, {variables: {id: dataRowId}});
if (device.loading) return <DataLoader/>;
if (device.error) return <p style={{color: 'white'}}>{("GraphQL Error " + device.error)})</p>;
// Create an empty recordObject to be populated and sent back for insert to db.
const emptyDevice = {
id : 0,
deviceId : 0,
deviceClass :{name : '',},
serialNumber: 0,
}
const deviceObject = device.data.getDevice !== null ? device.data.getDevice : emptyDevice;
useEffect(()=>{
setRow(deviceObject)
},[])
const handleSave = (value) => {
};
const HandleChange = e => {
useEffect(()=>{
setRow({...row, [e.target.name]: e.target.value })
},[])
};
return (
<div>
<Modal ...>
<div className={classes.paper}>
<Grid container direction="row" justify="center" alignItems="center">
<Grid item xs={4}>
<TextField
id="deviceId"
name="deviceId"
defaultValue={row.deviceId}
onChange={HandleChange}
/>
</Grid>
</Grid>
{/* 30 other textfields to capture*/}
....
</div>
</Modal>
</div>
)};
Edit as per Long Nguyen:
// Set the device object record to be either the empty record, or the records retrieved from db if those are populated (not null)
let deviceObject = {};
const Component = () => {
deviceObject = device.data.getDevice !== null ? device.data.getDevice: emptyDevice;
return <RefactorComponent />;
}
// Set the device object (empty or populated with db-retrieved rows,) into state
const RefactorComponent = () =>
{
useEffect(()=>{
setRow(deviceObject)
},[deviceObject])
// return ()
}
Component();
You make a call of hooks after condition. It makes the hook that you call does not appear in the same order between renders, because “React relies on the order in which Hooks are called”.
You can find useful information, also the way to solve your problem in this document.
https://reactjs.org/docs/hooks-rules.html#explanation
UPDATE:
You should not place hooks in a handle, of course.
If you still want to keep the original code, you can extract the block after condition to the other component like this
const Component = () => {
if(conditions) return <Other />
// 🚫 bad, this hook order is not fixed
// it can appear or not
useEffect(() => {
...the effect after condition...
});
return (...);
}
const Component = () => {
if(conditions) return <Other />
return <RefactorComponent />
}
const RefactorComponent = () => {
// ✅ ok, the hooks order is fixed
useEffect(() => {
...the effect after condition...
});
return (...);
}

React custom hook with state variable as parameter

I have a react function component which sets an array of ids based on an user event on a link click(which opens a popup with some options that can be selected and has a callback once it is closed which will return the id of the selected element). these ids are passed to a child component which has a custom hook which uses these ids to perform some action. whenever i click on the link and select an element and close the popup.. get the error
"VM10715 react_devtools_backend.js:2430 You have changed a parameter while calling a hook which is supposed to remain unchanged [Array(2)]
0: (2) ["", "asdsadsad"]
lastIndex: (...)
lastItem: (...)
length: 1"
is there a way to make this work without running into this error? please see the code sample below
const TestComp = () => {
const [newIds, setNewIds] = useState([]);
const onPopupElementSelect = (ids) => {
setNewIds([...newIds, ids]);
};
return (
//renders some components
<>
<ImageComponent images={images} ids={newIds} onClick={handleClick} />
<Popup onSelect={onPopupElementSelect} />
</>
);
};
const ImageComponent = (props) => {
const { newIds, images } = props;
const newImages = useImages(ids || ['']); //customhook that fetches image details by ids
const imgs = images.map((i) => (
<div key={i.imageId}>
<img src={i.imageUrl} alt="" />
<Link onClick={handleClick} /> //opens the popup for user to select a new
image
</div>
));
return <div>{imgs}</div>;
};
ps: the paramerter names are not the issue.. this code is just a sample to give the basic idea of what i'm trying to do.
I think it is because you gave the same name to parameter and the state may be try newID as the parameter name
const onPopupElementSelect = (newId) => {
setIds(oldIds => [...oldIds, newId]);
};

Incorrect use of useEffect() when filtering an array

I have this React app that's is getting data from a file showing in cards. I have an input to filter the cards to show. The problem I have is that after I filter once, then it doesn't go back to all the cards. I guess that I'm using useEffect wrong. How can I fix this?
import { data } from './data';
const SearchBox = ({ onSearchChange }) => {
return (
<div>
<input
type='search'
placeholder='search'
onChange={(e) => {
onSearchChange(e.target.value);
}}
/>
</div>
);
};
function App() {
const [cards, setCards] = useState(data);
const [searchField, setSearchField] = useState('');
useEffect(() => {
const filteredCards = cards.filter((card) => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
});
setCards(filteredCards);
}, [searchField]);
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={cards} />
</div>
);
}
you should Include both of your state "Card", "searchedField" as dependincies to useEffect method.once any change happens of anyone of them, your component will re-render to keep your data up to date,
useEffect(() => { // your code }, [searchField, cards]);
cards original state will be forever lost unless you filter over original data like const filteredCards = data.filter().
though, in a real project it's not interesting to modify your cards state based on your filter. instead you can remove useEffect and create a filter function wrapped at useCallback:
const filteredCards = useCallback(() => cards.filter(card => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
}), [JSON.stringify(cards), searchField])
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={filteredCards()} />
</div>
);
working example
about array as dependency (cards)
adding an object, or array as dependency at useEffect may crash your app (it will throw Maximum update depth exceeded). it will rerun useEffect forever since its object reference will change everytime. one approach to avoid that is to pass your dependency stringified [JSON.stringify(cards)]

How to target a specific item to toggleClick on using React Hooks?

I have a navbar component with that actual info being pulled in from a CMS. Some of the nav links have a dropdown component onclick, while others do not. I'm having a hard time figuring out how to target a specific menus index with React Hooks - currently onClick, it opens ALL the dropdown menus at once instead of the specific one I clicked on.
The prop toggleOpen is being passed down to a styled component based on the handleDropDownClick event handler.
Heres my component.
const NavBar = props => {
const [links, setLinks] = useState(null);
const [notFound, setNotFound] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const fetchLinks = () => {
if (props.prismicCtx) {
// We are using the function to get a document by its uid
const data = props.prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'navbar'),
]);
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
});
}
return null;
};
const checkForLinks = () => {
if (props.prismicCtx) {
fetchLinks(props);
} else {
setNotFound(true);
}
};
useEffect(() => {
checkForLinks();
});
const handleDropdownClick = e => {
e.preventDefault();
setIsOpen(!isOpen);
};
if (links) {
const linkname = links.map(item => {
// Check to see if NavItem contains Dropdown Children
return item.items.length > 1 ? (
<Fragment>
<StyledNavBar.NavLink onClick={handleDropdownClick} href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
<Dropdown toggleOpen={isOpen}>
{item.items.map(subitem => {
return (
<StyledNavBar.NavLink href={subitem.sub_nav_link.url}>
<span>{subitem.sub_nav_link_label[0].text}</span>
</StyledNavBar.NavLink>
);
})}
</Dropdown>
</Fragment>
) : (
<StyledNavBar.NavLink href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
);
});
// Render
return (
<StyledNavBar>
<StyledNavBar.NavContainer wide>
<StyledNavBar.NavWrapper row center>
<Logo />
{linkname}
</StyledNavBar.NavWrapper>
</StyledNavBar.NavContainer>
</StyledNavBar>
);
}
if (notFound) {
return <NotFound />;
}
return <h2>Loading Nav</h2>;
};
export default NavBar;
Your problem is that your state only handles a boolean (is open or not), but you actually need multiple booleans (one "is open or not" for each menu item). You could try something like this:
const [isOpen, setIsOpen] = useState({});
const handleDropdownClick = e => {
e.preventDefault();
const currentID = e.currentTarget.id;
const newIsOpenState = isOpen[id] = !isOpen[id];
setIsOpen(newIsOpenState);
};
And finally in your HTML:
const linkname = links.map((item, index) => {
// Check to see if NavItem contains Dropdown Children
return item.items.length > 1 ? (
<Fragment>
<StyledNavBar.NavLink id={index} onClick={handleDropdownClick} href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
<Dropdown toggleOpen={isOpen[index]}>
// ... rest of your component
Note the new index variable in the .map function, which is used to identify which menu item you are clicking.
UPDATE:
One point that I was missing was the initialization, as mention in the other answer by #MattYao. Inside your load data, do this:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
setIsOpen(navlinks.map((link, index) => {index: false}));
});
Not related to your question, but you may want to consider skipping effects and including a key to your .map
I can see the first two useState hooks are working as expected. The problem is your 3rd useState() hook.
The issue is pretty obvious that you are referring the same state variable isOpen by a list of elements so they all have the same state. To fix the problems, I suggest the following way:
Instead of having one value of isOpen, you will need to initialise the state with an array or Map so you can refer each individual one:
const initialOpenState = [] // or using ES6 Map - new Map([]);
In your fetchLink function callback, initialise your isOpen state array values to be false. So you can put it here:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
// init your isOpen state here
navlinks.forEach(link => isOpen.push({ linkId: link.id, value: false })) //I suppose you can get an id or similar identifers
});
In your handleClick function, you have to target the link object and set it to true, instead of setting everything to true. You might need to use .find() to locate the link you are clicking:
handleClick = e => {
const currentOpenState = state;
const clickedLink = e.target.value // use your own identifier
currentOpenState[clickedLink].value = !currentOpenState[clickedLink].value;
setIsOpen(currentOpenState);
}
Update your component so the correct isOpen state is used:
<Dropdown toggleOpen={isOpen[item].value}> // replace this value
{item.items.map(subitem => {
return (
<StyledNavBar.NavLink href={subitem.sub_nav_link.url}>
<span>{subitem.sub_nav_link_label[0].text}</span>
</StyledNavBar.NavLink>
);
})}
</Dropdown>
The above code may not work for you if you just copy & paste. But it should give you an idea how things should work together.

Resources