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>
);
};
Related
I have been working with RHF for a while and it helps a lot actually, but I have been trying to do something for what I have not enough knowledge maybe.
So the thing its that I have a nested object that I bring to my componen throw props
const Child = ({ globalObject, register }) => {
const renderNested = Object.entries(globalObject.nestedObject);
return (
<span>
{renderNested.map(([key, value], index) => {
return (
<div key={index}>
<Field
type="text"
label={key}
name{`nestedObject.${key}`}
defaultValue={value}
ref={register}
/>
</div>
);
})}
</span>
);
};
All good, now, one of the keys inside this nestedObject has another object as a value, for which when I map over them and display, the field will show: [object Object]
I know how I would solve that issue if I am using a useState, for example.
Not sure if its a good practice but I would go with something like:
defaultValue={typeof value === 'someKey' ? value[key] : value}
but in this case using register (which I want to use since it saved me from other nightmares) I am not sure how to solve this.
Should I filter the array outside and then render for one side the keys that dont have an object as a value and then the rest?
It looks to me like it has to be something better than that.
Can anyone give advices?
just to clarify, nestedObject looks like:
nestedObject: {
key: value
key: {key:value}
}
You can access the fields using . dot notation, as per documentation for the register method. So register("name.key") works to retrieve the nested object as well as arrays, however note that in React Hook Form v7 the syntax to retrieve nested array items changed from arrayName[0] to arrayName.0.
Your component would look similar to the following:
const Child = ({ globalObject, register }) => {
const nestedKeys = Object.keys(globalObject.nestedObject);
return (
<>
{nestedKeys.map((key) => (
<Field key={key} {...register(`nestedObject.${key}`)} />
))}
</>
);
};
You should not use index as key prop in this case, as you have another stable identifier for each array element which comes from unique nested object keys.
This question already has answers here:
How can I use multiple refs for an array of elements with hooks?
(17 answers)
Closed 1 year ago.
Since my pathfinding project has too many cells for me to animate it by changing state I was told to use ref. I went with the forward ref approach, which I assume works becase in devtools each cell appears with the forwardRef tag.
Even when I try to console.log each ref it points me to the correct cell in the grid. The problem is when I try to mutate a property like className/bgColor, program crashes and gives me a TypeError: Cannot add property className, object is not extensible.
const node = (props, ref) => {
return (
<div ref={ref}>
</div >
)
}
const Node = React.forwardRef(node);
export default Node
function Grid() {
// How I created the refs
const refCollection = useRef(matrix.map(rows => rows.map(nodes => React.createRef())));
let grid = matrix.map((rows, i) => rows.map((_, j) => <Node
ref={refCollection.current[i][j]}
></Node>))
function createMazeAndAnimate() {
const orderCarved = generateMaze(rows, cols, setWall, setStartNode,
setGoalNode, setTraversed, setCarvedOrder);
orderCarved.forEach(([y, x], i) => {
setTimeout(() => {
refCollection.current[y][x].className = 'traversed'; //PROGRAM CRASHES HERE
console.log(refCollection.current[y][x]); // IF I JUST LOG THIS LINE I CAN
// SEE EACH CELL REFERENCED
// CORRECTLY
}, i * 20);
})
}
return (
<>
<Toolbar
generateMaze={createMazeAndAnimate}
/>
<div
className='grid'
>
{grid.map((row, i) => <div key={i} className='board-row'>{row}</div>)}
</div>
</>
)
}
Thank You!
You're trying to modify refs directly. You don't mutate a ref object itself, you mutate whatever's in ref.current. So you want refCollection.current[y][x].current.
Putting a ref inside a ref is a code smell, as a ref itself is designed to be mutated, putting it inside something else you mutate seems extraneous. You might want to put your ref array in state instead.
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
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