I have created a React Component that renders a set of sub-elements given an array of ids. The array of ids is kept in the state of the parent component, and then I run some ajax calls based on the ids to fetch data to render. The fetched data is stored in a separate data array in the state. The rendered components use the id as key.
The ids can change based on actions outside of the component, so I use setState on the component to replace the array. The updated id-state will probably contain some of the same ids as the in the original array. At the same time I empty the 'data array' so that everything will be rendered again.
When I do this I sometimes get the key-warning:
Warning: flattenChildren(...): Encountered two children with the same key. Child keys must be unique; when two children share a key, only the first child will be used.
The new array does not contain any duplicates. So why does it happen, and what can I do to avoid this?
Edit: Added some code by request. Note: I am using the Infinite Scroll module. Could this be causing it?
getInitialState: function() { return { hasMore: true, num: 0, movieIds: this.props.movieIds, movies: [] }; },
render: function() { var InfiniteScroll = React.addons.InfiniteScroll; return ( <InfiniteScroll pageStart={0} loadMore={this.loadMore} threshold='20' hasMore={this.state.hasMore}> <ul className="movieList"> {this.state.movies} </ul> </InfiniteScroll> ); }
comp = this; $.ajax( { url: url, contentType: "json", success: function (data) { var m = createMovieLi(data); var updatedMovies = comp.state.movies; updatedMovies[num] = m; comp.setState({movies: updatedMovies}); } });
And finally when updating outside the component:
movieBox.setState({ hasMore: true, num: 0, movieIds: filteredIds, movies: [] });
As we already saw before, React re-renders a component when you call the setState function to change the state (or the provided function from the useState hook in function components). As a result, the child components only update when the parent component's state changes with one of those functions.
Each state change in the parent component triggers re-rendering of all its subsequent child components even if no props are changed.
To change child component's state from parent component with React, we can pass props. const Child = ({ open }) => { return <Drawer open={open} />; }; const ParentComponent = () => { const [isOpen, setIsOpen] = useState(false); const toggleChildMenu = () => { setIsOpen((prevValue) => !
Sending state/props to another component using the onClick event: So first we store the state/props into the parent component i.e in which component where we trigger the onClick event. Then to pass the state into another component, we simply pass it as a prop.
I figured out my mistake, and it had nothing to do with React per se. It was a classic case of missing javascript closure inside a loop.
Because of the possibility of duplicates I stored each ajax response in window.localStorage, on the movieId. Or so I thought.
In React Inifinite Scroll each item in your list is drawn sequentially with a call to the loadMore-function. Inside this function I did my ajax call, and stored the result in the browser cache. The code looked something like this:
var cachedValue = window.localStorage.getItem(String(movieId)); var cachedData = cachedValue ? JSON.parse(cachedValue) : cachedValue; if (cachedData != null) { comp.drawNextMovie(cachedData); } else { $.ajax( { type: "GET", url: this.state.movieUrl + movieId, contentType: "json", success: function (movieData) { window.localStorage.setItem(String(movieId), JSON.stringify(movieData)); comp.drawNextMovie(movieData); } }); }
Can you spot the mistake? When the ajax-call returns, movieId is no longer what is was. So I end up storing the data by the wrong id, and get some strange React warnings in return. Because this was done inside the loadMore function called by the InfiniteScroll-module, I was not aware that this function was not properly scoped.
I fixed it by adding a Immediately-invoked function expression.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With