I have the code below:
const [personsState, setPersonsState] = useState({
person: [
{ name: 'Max', age: 28 },
{ name: 'Manu', age: 29 },
{ name: 'Stephanie', age: 26 }
],
showPersons: false
});
Is it best practice to access the person array by using the code below?
persons = (
<div>
{personsState.person.map((person) => {
return <Person name={person.name} age={person.age} />;
})}
</div>
);
If you're using Hooks, React recommends that you separate out different types of state into different variables. Unlike class components, you shouldn't try to put all state into a single variable.
From their FAQ:
We recommend to split state into multiple state variables based on which values tend to change together.
Consider having persons (the array) and showPersons (the boolean) instead:
const [persons, setPersons] = useState([
{ name: 'Max', age: 28 },
{ name: 'Manu', age: 29 },
{ name: 'Stephanie', age: 26 }
]);
const [showPersons, setShowPersons] = useState(false);
And then generate JSX by mapping over the persons variable, instead of personsState.prson.
Your code looks good to me, only one extra suggestion which is related to Lists and Keys section of React documentation:
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.
In order to eliminate warning, you need to add key to each iterated elements in .map() as:
<div>
{personsState.person.map((person, index) => {
return <Person key={index} name={person.name} age={person.age} />;
})}
</div>
Related
I read a comment from someone here in StockOverflow who talks about React keys and said that
'React expects STABLE keys, meaning you should assign the keys once and every item on your list should receive the same key every time, that way React can optimize around your data changes when it is reconciling the virtual DOM and decides which components need to re-render. So, if you are using UUID you need to do it at the data level, not at the UI level',
and I want to ask if anyone know how to apply this in a real code where we have for example a context component that have an array of objects and another component that maps through this array, how can we apply this using uuid() or any other package.
Although it is not a common requirement for you to generate id on FE, it happens some times, so using uuid is a really good way of doing that. It is easy and implementation is quick.
I made an example for you here how to do it:
import { useEffect, useState } from "react";
import { v1 } from "uuid";
import "./styles.css";
const items: { name: string; id?: string }[] = [
{
name: "Name1"
},
{
name: "Name2"
},
{
name: "Name3"
},
{
name: "Name4"
},
{
name: "Name5"
},
{
name: "Name6"
}
];
export default function App() {
const [itemList, setItemList] = useState<{ name: string; id?: string }[]>([]);
useEffect(() => {
setItemList(items.map((item) => ({ ...item, id: v1() })));
}, []);
return (
<div className="App">
{itemList.map((item) => (
<div key={item.id}>
{item.name} - {item.id}
</div>
))}
</div>
);
}
In this example your array is empty at the beginning and after first useEffect gets populated by items with uuid generated ids:
And code sandbox code
You can install react-uuid
import uuid from 'react-uuid'
const array = ['one', 'two', 'three']
export const LineItem = item => <li key={uuid()}>{item}</li>
export const List = () => array.map(item => <LineItem item={item} />)
or you can use crypto.randomUUID() directly without pckgs
Hello I am just learning react and I am looking at tutorials but in the version that was installed, react is no longer using the classes, it only appears functions and I would like to continue that way if possible, but I have a problem with where to change the name in this part with a click but it does not allow me to access persons(const), how could I do it?
import Person from './Person/Person'
function App() {
const persons = {
persons: [
{ name: 'Jose', age: 32},
{ name: 'Gabriel', age: 2}
]
}
const switchNameHandler = () => {
persons({
persons: [
{ name: 'Jose Fernando', age: 32},
{ name: 'Gabriel', age: 2}
]
})
}
return (
<div className="App">
<h1>Hi, I'm a React App!</h1>
<button onClick={switchNameHandler}> Switch Name</button>
<Person name={persons.persons[0].name} age={persons.persons[0].age}/>
<Person name={persons.persons[1].name} age={persons.persons[1].age}> I like play</Person>
</div>
);
}
export default App;
How could I fix the switchNameHandler part?
I know that if I use classes I can access this.setPersons, but is there any way to access without using classes?
You need to use the useState hook. All hooks return two things in a de-structured array (1) the state value and (2) the function to set that state value. and the value you put in the useState() function is the initial value.
For example:
const [name, setName] = useState("Ben");
Here the initial value of name is "Ben". If I wanted to change that value, I could use the setName function and do something like this setName("Jerry");. Now the value of name is "Jerry".
The biggest difference between the setter (in this case setName) and this.setState (in a class component), is that this.setState remembers and spreads the previous state automatically for you if you don't explicitly define it. With hooks, you have to do that your self:
For example:
const [person, setPerson] = useState({ name: "", id: "", age: "" });
If I have this state, and want to edit just the name of the person, I have to remember to spread the previous state object every time I update state, by using a callback function - where the parameter in the callback is the previous state:
// just updating name
setPerson(prevPerson => ({ ...prevPerson, name: "Bob" }));
Here, the only thing that changes was the "name" value and everything else stayed the same:
Result: { name: "Bob", id: "", age: ""}
Check out the react documentation for more tips and examples: https://reactjs.org/docs/hooks-state.html
I'm learning React and i'm stuck on this problem. I've looked for answers but couldn't find why this exact thing was happening.
When i fill in a form and submit it, a new object gets added to the state. No matter how i generate random key (for example in code below i do it with date object and getTime method), when i add two or more identical objects to the state in a row (one exactly after the other), they always generate the same key, same with math.random and all other methods i've tried.
If i add one object and then change any data even a little bit and add another and then add the same as the first one, everything works perfectly. The only problem is when i try to add the same ones in a row.
By identical objects i mean that all their {key: value} pairs contain the same data and they have the same keys.
This is how my state objects like like:
state = {
dmd: [
{name: 'dmd', age: 69, rich: 'yes', id: 1},
{name: 'lk', age: 19, rich: 'true', id: 2},
{name: 'tm', age: 83, rich: 'aye', id: 3}
]
}
Note that when i have this initial state already hard coded, when i add a new object that's the same as the 3rd one, the error doesn't occur. It's just when creating new ones.
newDmd object that is passed to addDmd method would not have id at first and would look like this:
{name: 'john', age: 22, rich: 'yes'}
Method used to change state:
addDmd = (newDmd) => {
newDmd.id = new Date().getTime();
let newArray = [...this.state.dmd, newDmd];
this.setState({
dmd: newArray
});
}
The image below is to show you when the keys become the same (my key is 'id'). You can see that on the last 3 additions, the key is the same, starting to repeat after the second one:
I don't have enough reputation points to post an image, so here's a link: https://i.imgur.com/WQrVHlp.png
I would like to know:
Why this happens
How to fix this
Best practices regarding changing state and generating unique keys
Edit:
Adding code from where addDmd method is called.
class AddDmd extends Component {
state = {
name: null,
age: null,
rich: null
};
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.addDmd(this.state);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor="name">Name:</label>
<input type="text" id="name" onChange={this.handleChange}></input>
<label htmlFor="age">Age:</label>
<input type="text" id="age" onChange={this.handleChange}></input>
<label htmlFor="rich">rich?:</label>
<input type="text" id="rich" onChange={this.handleChange}></input>
<button>Submit</button>
</form>
</div>
)
}
}
Edit 2: added full code of class where addDmd method resides.
class App extends Component {
state = {
dmd: [
{name: 'dmd', age: 69, rich: 'yes', id: 1},
{name: 'lk', age: 19, rich: 'true', id: 2},
{name: 'tm', age: 83, rich: 'aye', id: 3}
]
}
addDmd = (newDmd) => {
newDmd.id = new Date().getTime();
let newArray = [...this.state.dmd, newDmd];
this.setState({
dmd: newArray
});
}
render() {
return (
<div className="App" >
<AddDmd addDmd={this.addDmd} />
</div>
);
}
}
Most likely your issue is related to accidental mutation of nested objects by reference.
1. Why this happens:
I can only speculate for now. If you want to share the usage of addDmd I can confirm. I believe newDmd is populated from a previous entry. Which means doing newDmd.id = will mutate both objects in state.
You can confirm this yourself by checking the id each time a new entry is added. The new id should be different since its based on time, but watch and see if the previous entries are changing along with it.
2. How to fix it:
Change your function to this so that it makes deep copies of the state:
addDmd = (newDmd) => {
newDmd.id = new Date().getTime();
let newArray = [...this.state.dmd.map(nestedObj => ({...nestedObj})), newDmd];
this.setState({
dmd: newArray
});
}
3. Best practices:
As far as I can tell your method of generating unique keys should work fine. The best practice I would recommend is to make sure you make deep copies of state when necessary.
Update
I was pretty close. The issue is you're passing this.state directly to addDmd. this.state gets a new object reference every time the component re-renders, but if you don't change any values in the form and submit again - it doesn't need to re-render. This means you pass addDmd the same object reference.
Simple fix:
handleSubmit = (e) => {
e.preventDefault();
this.props.addDmd({...this.state}); // Create new object out of state
}
Now you can submit the same form as many times as you want and it will always submit a new object reference and will not mutate the previous submission.
Why this happens ?
I totally agree with the reason #Brian Thompsan mentioned.
Also, I have create a example code for your use-case where I am adding a same item with different ids (created using date/time) on the button click. Its working fine and here is the code.
import React from "react";
import Listbox from "./listBox";
class App extends React.Component {
constructor() {
super();
this.state = {
dmd: [
{ name: "dmd", age: 69, rich: "yes", id: 1 },
{ name: "lk", age: 19, rich: "true", id: 2 },
{ name: "tm", age: 83, rich: "aye", id: 3 }
]
};
}
addDmd = () => {
const newId = new Date().getTime();
console.log(newId);
let newArray = [
...this.state.dmd,
{ id: newId, name: "new name", age: "new age" }
];
this.setState({
dmd: newArray
});
};
render() {
return (
<>
<input type="button" onClick={this.addDmd} />
{this.state.dmd.map(item => (
<div>
id: {item.id} name:: {item.name}
</div>
))}
</>
);
}
}
export default App;
For some reason I can't seem to find an answer to this rather simple task I believe. My challenge is that I have a usestate array which holds multiple objects depending on the user input. It can have as many as the user chooses or none. For some reason I can't get it to work right.
What I mean is that whenever I set the state (for example: setFinal(final => [...final, value]);) in any way that I've been able to come up with so far it doesn't work properly. It works fine when the user adds on click a new object to the array but deleting them and particularly coming from 1 to 0 to 1 again causes the state to be invalid. What I mean is that the last one before going to 0 doesn't get deleted and when you start to add new objects from 0 again it has already one value in state.
So, what is the right way to set state so that it works correctly no matter if you're adding or deleting objects from the state?
Hopefully I've made myself clear enough for a solution to this. It should be fairly straightforward but I don't seem to get it right and googling doesn't seem to do the trick. Thanks in advance for anyone helping me.
EDIT:
Something for clarification:
example objects:
0: Object { id: 484, data: [] }
1: Object { id: 524, data: [] }
2: Object { id: 170, data: (3) […] }
What I've tried so far in setting state:
setFinal(final => [...final, value]);
setFinal(value);
setFinal(final => value);
I would need it to work no matter whether you add or delete, delete them all and add again, it should work in all of these conditions.
Are you looking something like this? Live Demo
function App() {
const [arr, setArr] = useState({
numbers: [
{ id: 1, name: "Reactjs" },
{ id: 2, name: "Vuejs" },
{ id: 3, name: "Nodejs" }
]
});
const onclickHandler = event => {
setArr({
...arr,
numbers: [
...arr.numbers,
{
id: Math.floor(Math.random() * 100),
name: `${Math.random()} technology`
}
]
});
};
const deleteIt = item => {
let updated = arr.numbers.filter(a => a.id !== item.id);
setArr({ numbers: updated });
};
return (
<div className="App">
<button onClick={onclickHandler} value="4">
Add
</button>
<ul>
{arr.numbers.map(a => (
<li key={a.id}>
<span>
{a.id}-{a.name}
</span>
<button style={styles} onClick={() => deleteIt(a)}>
X
</button>
</li>
))}
</ul>
</div>
);
}
const styles = {
color: "white",
backgroundColor: "red"
};
You can't delete element from array via setFinal(final => [...final, value]); If value is undefined then setFinal just ignore it. Please see: Delete item from state array in react
if you don't know React etc, read here. Below is the same question but more related to React etc:
I have severel Objects containing one sort of data:
Prices:
'btc-usd' : 2640, 'ltc-usd': 40, ...
Amount of Crypto:
'btc-usd': 2.533, 'ltc-usd': 10.42, ...
How can I take these Objects and create an Array of Objects like:
[ { name: 'Bitcoin', amount: 2.533, value: 2640, id: 'btc-usd' },
{ name: 'Litecoin', amount: 10.42, value: 40, id: 'ltc-usd' }, ...
]
For React Programmers:
To use the new FlatList Component in React you need to provide an Array of Objects
<FlatList
data={[{key: 'a'}, {key: 'b'}]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
I use several reducers that store different sorts of cryptocurrency data.
Reducer1 stores prices:
'btc-usd' : 2640,'ltc-usd': 40`
Reducer2 stores the amount of crypto you have:
'btc-usd': 2.533,'ltc-usd': 10.42
My goal is to iterate through these values and create an array of Objects like
[ { name: 'Bitcoin', amount: 2.533, value: 2640, id: 'btc-usd' },
{ name: 'Litecoin', amount: 10.42, value: 40, id: 'ltc-usd' }, ...
]
So far, I think the proper way is to do it in a Selector with reselect. The part missing is the lodash Part in the createSelector.
Surprisingly I didn't find anything combining lodash and FlatList on the internet.
Thanks a lot for helping.
import { createSelector } from 'reselect';
import _ from 'lodash';
export const selectRate = (state) => state.rate;
export const selectCryptoBalance = (state) => state.cryptoBalance;
export const createCoinArray = createSelector(
selectRate, selectCryptoBalance,
(rate, cryptoBalance) => {
const coinArray = []; //better load array from state?
//IMPROVEMENT NEEDED HERE
_.forIn(selectRate, () => {
coinArray.push(_.assign({ [selectRate.id]: 0 }, cryptoBalance));
console.log('coinArray in loop ', coinArray);
});
return coinArray;
}
);
Solution worked! more solutions here Generate Array of Objects of different Objects Javascript
Thanks for helping! A lot of React users will appreciate
Assuming you have the following objects
prices = {'btc-usd' : 2640,'ltc-usd': 40, ... }
rates = {'btc-usd': 2.533,'ltc-usd': 10.42, ...}
names = {'btc-usd': 'Bitcoin' ...}
You could do
_.map(prices,(value,id) => ({name:names[id], amount:rates[id], value,id}))