I have a warning: Each child in an array or iterator should have a unique "key" prop.
But I used a key.
This is my code:
return (
<li onClick={this.handleOnMarkAsCompleted} key={Date.now()}>
{ completed ? <b>{value}</b> : value }
</li>
)
Any Ideas? Why doe it happen?
consider these two examples:
const Item = ({ i }) => <li key={i}>li</li>;
const List = ({ data }) => <ul>{data.map((_, i) => <Item i={i} />)}</ul>;
in this case, you'd get:
Warning: Each child in an array or iterator should have a unique "key" prop.
because li is not an array item. it's inside of Item which is an array item.
So key on Item would eliminate the problem:
const Item = ({ i }) => <li key={i}>li</li>;
const List = ({ data }) => <ul>{data.map((_, i) => <Item key={i} />)}</ul>;
code sandbox: https://codesandbox.io/s/oojwjq0lj6
from docs:
Keys only make sense in the context of the surrounding array.
For example, if you extract a ListItem component, you should keep the
key on the <ListItem /> elements in the array rather than on the <li>
element in the ListItem itself.
a note regarding use of Date.now():
Keys should be stable, predictable, and unique. Unstable keys (like
those produced by Math.random()) will cause many component instances
and DOM nodes to be unnecessarily recreated, which can cause
performance degradation and lost state in child components.
Date.now() generates current time UNIX timestamp which is the same every time (in the second which all items are rendered). Key neeeds to be unique, as described in the error. Add some kind of id or (if no alternative is possible) an iterator.
As per ReactJS docs, Keys only make sense in the context of the surrounding array.
Example of Correct key usage.
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Try it on codepen
You need to use the key prop at the parent of this component. Lets suppose your component is named TaskItem, and the component in which you are using it is called TaskItems:
const TaskItems = (props) => { return ( <ul> {props.items.map((itemElement) => ( <TaskItem key={itemElement.id} ></TaskItem> ))} </ul> ); };
You can use index of array or keys of object row
Date.now is duplicate
Related
This is more of a conceptual question than anything else.
I'm trying to draw a SVG line between two elements in my application. The way I'm currently doing this is by having a top level ref, inside of which I store a ref for each of the child elements indexed by an arbitrary key. I then draw the arrows, deduced from a 2 dimensional array of pairs of these keys, look up the ref, and use the positioning of those elements to create the SVG's coordinates.
The problem with this method is, besides initial render, every render update afterwards uses outdated ref data as the children get rendered (and therefore positioned) after the parent, inside which the SVG layer is contained.
I've already thought of the obvious answers, useEffects, setStates, etc., but there doesn't seem to be a good solution here that I can think of. A lot of the obvious answers don't necessarily work in this case since they cause render loops. Another solution that I initially thought of was breaking down the arrow rendering to within each of the children, but this had problems of its own that initially caused the transition to having all of the refs live at the parent level.
My current solution is to key the SVG Layer component to a state variable, and then change the value of this variable in a useEffect once the final item renders, but this solution feels messy and improper.
Not exact but here's some code of the issue
Parent Component:
export default ({ items }) => {
const [svgKey, setSvgKey] = useState(0);
const pairs = items.reduce((arr, item) => {
for (const subItem of item.subItems) {
arr.push([subItem, item]);
}
}, [])
const itemRefs = useRef({});
return (
<>
{items.map(item => <Row items={items} setSvgKey={setSvgKey} item={item} refs={refs} key={item.key} />}
<SVGLayer key={svgKey} pairs={pairs} refs={refs} />
</>
);
}
SVG Layer
export default ({ pairs, refs }) => (
<svg>
{pairs.map(([a, b], i) => (
<Arrow key={i} a={refs.current[a.key]} b={refs.current[b.key]} />
)}
</svg>
);
Arrow
export default ({ a, b }) => <path d={[math for line coords here]} />;
Row
export default ({ refs, item, items, setSvgKey }) => {
useEffect(() => {
if (item.key === items[items.length - 1].key) {
setSvgKey(key => ++key);
}
});
return (
<div ref={el => (refs.current[item.key] = el)} />
);
}
I got the following warning:
Warning: Each child in a list should have a unique "key" prop.
Check the render method of TabContext. See
https://reactjs.org/link/warning-keys for more information.
in TabForGroupInList (at Product.tsx:148)
in TabContext (at Product.tsx:144)
Here it is the render method of TabContext:
export default function TabContext(props: any) {
const selectedTabId = useSelector((state:any) => state.selectedTabId[props.id]);
...
return (
<>
{props.tabContent(selectedTabId, onChangeHandler)}
</>
);
And here TabContext is called:
export default function Product() {
const iter = (node: any, name: string) => {
let c = child(node, name);
let result: any = [];
c.items.forEach(
(e: any) => result.push(nodes[e])
);
return result;
}
return (
<>
<TabContext id={id(rootNode, 'Multi_Page1')} tabContent ={(selectedTabId:any,
changeHandler:any)=>(
<Tabs value={selectedTabId} changeHandler={changeHandler}>
{iter(rootNode, 'Multi_Page1').map((Multi__Page1: any, index: number) => { return (
<TabForGroupInList value={Multi__Page1.id} id={Multi__Page1.id} tabId={rootNode.id} />
)})}
</Tabs>
</>
)
}
I need to find a way to iterate thru props.tabContent() (TabContext), in order to get the different tabId props (TabForGroupInList) as a key in the render method of TabContext, but props.tabContent() is not an array, it is a function and I cannot use map(). Or is there any other way to get unique keys from props.tabContent()?
add key={index} or any unique identifier to your TabForGroupInList component.
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity. It's quite often to see the key prop when we are iterating an array with methods like map (source)
The key prop is available in every React component you create out of the box
This question already has answers here:
How to loop an object in React?
(8 answers)
Closed 3 years ago.
I have ControlSection component that one of it's props is statistics object prop. I want to display with <h2> all the key and values of the object. How can I do it?
ControlSection:
const ControlSection = ({ statistics }) => {
return (
<div className='gameStatistics'>
{
Object.keys(statistics).forEach(key =>
<h2>key: statistics[key]</h2>
)
}
</div>
)
}
Statistics example:
const statistics = {
Score: score,
Level: level,
Best: bestScore,
};
forEach returns undefined. Use Array#map, which returns the JSX for each item in a new array.
JSX also requires the { } braces to evaluate text as JS.
Since you're creating a series of <h2> elements, React needs a key property to be set on the elements. Using keys from the object is sufficient since they're guaranteed to be unique strings. However, you might consider using a list (<ul> or <ol> parent element with <li> children) as an alternative to the header elements.
Also, you can use Object.entries to access both the key and value for each pair in an object in a slightly more intuitive way than statistics[key]:
const ControlSection = ({ statistics }) => {
return (
<div className='gameStatistics'>
{
Object.entries(statistics).map(([key, val]) =>
<h2 key={key}>{key}: {val}</h2>
)
}
</div>
);
};
I'm trying to forward multiple refs to multiple children DOM nodes:
I need references to the 7 buttons, so I can manage focus between them.
I tried by using an array of React.createRef() and then attach each element of this array to a child using index, but all refs refers to the last button.
Why and is there another solution?
class RestaurantsList extends Component {
references = Array(7).fill(React.createRef());
render() {
return (
<ul
id="restaurants-list"
role="menu"
>
{
this.props.restaurants.map((restaurant, index) => {
return (
<Restaurant
ref={this.references[index]}
name={restaurant.name}
/>
);
})
}
</ul>
);
}
}
const Restaurant = React.forwardRef((props, ref) => {
return (
<li>
<button
ref={ref}
>
{name}
</button>
</li>
);
})
As it was discussed in comments the best way is to keep list of references as an array or object property inside parent component.
As for Array.prototype.fill() its arguments is calculated just once. In other words fill(React.createRef()) will generate list where each entry will refer to the same object - and you will get equal ref to last element. So you need to use .map() for getting unique reference objects.
references = Array(7).fill(0).map(() => React.createRef());
Anyway in real world project this will rather happen in constructor() or componentDidUpdate().
But I believe it's better to have hashmap:
references = {};
getOrCreateRef(id) {
if (!this.references.hasOwnProperty(id)) {
this.references[id] = React.createRef();
}
return this.references[id];
}
render() {
return (
....
{
this.props.restaurants.map((restaurant, index) => {
return (
<Restaurant
ref={this.getOrCreateRef(restaurant.id)}
key={restaurant.id}
name={restaurant.name}
/>
);
})
}
Also you will need some helper methods to avoid exposing this.references to outer world:
focusById(id) {
this.references[id].current && this.references[id].current.focus();
}
And take special attention to cleaning up references to unmounted elements. Otherwise you may got memory leak if list of restaurants is changed dynamically(if ref stays in this.references it keeps reference to HTML element even if it has been detached). Actual need depends on how is your component used. Also this memory leakage will be fixed once container(that has reference = {}) is unmounted itself due to navigating away.
This question already has answers here:
React Warning: flattenChildren(...): Encountered two children with the same key
(4 answers)
Closed 4 years ago.
Application works, my classes really adds a new element but I see below warning in console!
Warning: Encountered two children with the same key, [object
Object]. Keys should be unique so that components maintain their
identity across updates. Non-unique keys may cause children to be
duplicated and/or omitted — the behavior is unsupported and could
change in a future version.
in div (created by ContentBody)
in ContentBody
Here is my render part :
return (
<div ref={this.myRef} style={this.state.myHomeStyle} >
{this.state.elements.map((i: any) => {
console.log(">>i>>>>", i);
return <span style={i.myStyle} key={i} >{i}</span>;
})}
</div>
);
// Where i init
public componentDidMount() {
console.log('componentDidMount');
this.myDOM = this.myRef.current;
this.myDOM.addEventListener(myEventsList.adaptCss, this.adaptCss);
this.add(12,this.INLINE_TEST_ELE, null);
this.add(13,this.INLINE_TEST_ELE, null);
}
// Function add
private add = (id: number, content: any, event: any ) => {
let localArr: any[] = [];
let mEvent: any = null;
if (event !== undefined) {
mEvent = event;
}
localArr = this.state.elements;
localArr.push(React.createElement("div", { key: id , onClick : mEvent }, content));
this.setState(
{
elements: localArr,
visibility : true,
},
);
}
Any suggestions?
Update:
Here is the link for my starter project:
https://github.com/zlatnaspirala/react-vs-typescript-starter
You can pass another parameter within your map function like so:
this.state.elements.map((element, index) => {
return <span style={element.myStyle} key={index} >{element}</span>;
});
The second parameter of the Array.prototype.map function actually contains the current index of the particular element in that array.
This way, you'll be sure that your key is not duplicated.
You are passing element not index. And if your element is same then the error is being thrown. To pass the index use second param:
.map((element, index)=>
Now, using index will give you different key.
See this for more understanding in "key" related warnings and best practices
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Visit this link https://reactjs.org/docs/lists-and-keys.html#extracting-components-with-keys for more information