I am new to React and I am trying to use state for the first time. For some reason statelist.name does not return anything. Do I need to use a constructor ? Any Help would be great.
import React from 'react';
class HorizantScroller extends React.Component {
state = {
statelist: [
{name: "Brands",
items: ["1", "2", "3"]
},
{name: "Films",
items: ["f1", "f2", "f3"]
},
{name: "Holiday Destination",
items: ["f1", "f2", "f3"]
}
]
};
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(list, selected);
const {statelist} = this.state;
return (
<div>
<div name={statelist.name}></div>
</div>
);
}
}
export default HorizantScroller;
StateList is an array, you will need to specify the index of the state first and the get the property 'name'. Example: statelist[0].name
You could do something like this to make it dynamic:
{statelist.map((list,index)=>{
return (
<div key={index} name={list.name}></div>
);
})
}
This way you would have all the values in the statelist. replace the <div name={statelist.name}></div> with the above code.
Notice how i have added a key to the div. this is important for react to distinguish between the divs and also it'll complain in the console if you don't do it :).
Your problem is not with the react state, is with the data types you're using. First of all statelist is not an object so you can't use statelist.name, it's an array so you need to pass an index of the position of the array you're trying to retrieve, it should be something like statelist[0].name.
If you want to access all the names of statelist not just the first item you need iterate in the array and do something like this:
{statelist.map((element,index) => (
<div key={index} name={element.name}></div>
)) }
statelist is an array and does not have a field named name. The elements of the array has the field name. This means you have to access an element. You can do this with an index:
let index = 0;
<div name={statelist[0].name}></div>
If you want to get the names of all elements you can use the map function:
{statelist.map(element => {
return (
<div name={element.name}></div>
);
})}
Related
Let's say I have a <SelectPicker/> component where I can select an option. What I want is how to add another <SelectPicker/> after I selected an option.
function DynamicComponent() {
const [state, setState] = useState([
{ name: null, id: '1' },
]);
const handleAdd = (value) => {
// Updating logic
};
return(
<>
{ state.map(item => {
return <SelectPicker
onSelect={handleAdd}
key={item.id}
value={item.name}
data={options} />
})
}
</>
);
}
In the example above, let's say there is default SelectPicker which is not selected. After selection, I think handleAdd function should update object that has id equal to '1' and add another object like this { name: null, id: '2' }.
What is the best way to achieve such functionality in react? Any help would be appreciated.
Thank you.
On an abstract level, what you want to do is have an array of components inside your state which is then called by the Render function of DynamicComponent. This array should get its first SelectPicker component immediately, and every time handleAdd is called you add a new SelectPicker to the array using your setState function. You can get the id for each new SelectPicker component by finding array.length.
In addition to the question, the below note from OP is also addressed in the below question
what if I want to update object's name property that has id:1 and add
new object to state at the same time?
This may be one possible solution to achieve the desired result:
function DynamicComponent() {
const [myState, setMyState] = useState([
{name: null, id: '1'}
]);
const handleAdd = arrIdx => setMyState(prev => {
const newArr = [...prev];
prev[arrIdx]?.name = ">>>>----new name goes here---<<<<";
return [
...newArr,
{
name: null,
id: (prev.length + 1).toString()
}
]
});
return(
<div>
{myState.map((item, idx) => (
<SelectPicker
onSelect={() => handleAdd(idx)}
key={item.id}
value={item.name}
data={options}
/>
)}
</div>
);
}
NOTES
Avoid using variable-names such as "state"
Passed the "index" from the .map() iteration
This helps in tracking the exact array-element
The new element with name: null is added as the last
The id is calculated by incrementing the current length by 1
Dynamic forms with react and antd are eluding me. I have scoured the web looking for answers to no avail. Here is a codepen with a recreation of the issue I am having: https://codepen.io/sethen/pen/RwrrmVw
Essentially, the issue boils down to when you want loop through a bunch of values that are stored in state, like so:
class MyClass extends React.Component<{}, {}> {
constructor(props) {
super(props);
this.state = {
data: [
{ name: 'foo' },
{ name: 'bar' },
{ name: 'baz' }
]
};
}
You can think of these values as being fetched from some remote API.
As you can see, I have an array of objects with the key of name in the state. Further on down in the render cycle is the following:
return data.map((value, index) => {
const { name } = value;
return (
<Form key={ index } initialValues={ { name } }>
<Form.Item name='name'>
<Input type='text' />
</Form.Item>
<Button onClick={ this.handleOnDeleteClick.bind(this, index) }>Delete</Button>
</Form>
);
This attempts to loop through the values stored in the state and put the values into an input. It also adds a little delete button to get rid of that item. The first time it renders, it does as you expect it to loading the value into the input value.
The issue is when you try to delete one of the items, like the middle one, it will delete the next item. The core of the issue is that the render is acting different than I expect it to when deleting an item. I am expecting that when I delete an item, it will take it out of state and load the ones that are left. This is not happening.
My question is, how am I able to load dynamic data in this way with antd whilst being able to delete each item?
The main mistake in this form that you assign the key property as the array index, and on deleting the middle item, the last component will get a new key.
In React, changing the key will unmount the component and lose its state.
Don’t pass something like Math.random() to keys. It is important that keys have a “stable identity” across re-renders so that React can determine when items are added, removed, or re-ordered. Ideally, keys should correspond to unique and stable identifiers coming from your data, such as post.id.
Also, in your example, you actually render three forms instead of a single form and three fields.
Every <form/> has in its inner state all states of its form fields, so you will have a single object with all input values in it.
Antd.Form just a wrapper for such form, you can get Form.Item values in onFinish callback for example.
class MyClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{ name: "foo" }, { name: "bar" }, { name: "baz" }]
};
}
handleOnDeleteClick = index => {
this.setState({
data: [
...this.state.data.slice(0, index),
...this.state.data.slice(index + 1)
]
});
};
render() {
const { data } = this.state;
return (
<Form>
{data.map(({ name }, index) => {
return (
<Form.Item key={name}>
<Input type="text" />
<Button onClick={() => this.handleOnDeleteClick(index)}>
Delete
</Button>
</Form.Item>
);
})}
</Form>
);
}
}
Let's say i have a tabbed component. Each tab presents a person object. The user can switch the active tab, modify or remove each one and even change their order:
state={
activeTab:0,
tabs:[
{
name:'John',
age:34
},
{
name:'Bob',
age:31
},
]
}
Let's say i want to modify one of the fields, of a specific tab(person). I could have this function:
modifyTab = (index, prop, value) => {
const tabs = [...this.state.tabs];
const tab = tabs[index];
tab[prop] = value;
this.setState({ tabs })
}
The problem with this, is that it relies on the index of the given tab. But what if the tab index changes, if i provide, let's say, the ability of switching the tab order?(like the browsers do for their tabs).
Or what if i need to register some callback for an a-sync operation, that might be called when the relevant person sits in a different tab(maybe the tab was moved from 1 to 0, by the time the callback was called)?
Is there any way to just rely on object reference, regardless of id, index or any other "identifier", which makes the code much more complicated than it needs to be?
For those who are familiar with VueJS and Angular, i'm sure you know how easy it is to modify objects, being that you do not need to return a new state tree on each change.
If you are changing the order of the array, you cannot rely on the array index for the key prop when you are rendering. One common way around this is to add a unique property to every object in the array and use that instead, e.g. an id.
Passing in the entire object reference to modifyTab would be perfectly fine. You could figure out what object in the array that is with a simple indexOf.
Example
class App extends React.Component {
state = {
tabs: [
{
name: "John",
age: 34,
id: 1
},
{
name: "Bob",
age: 31,
id: 2
}
]
};
modifyTab = (tab, prop, value) => {
this.setState(prevState => {
const tabs = [...prevState.tabs];
const index = tabs.indexOf(tab);
tabs[index] = { ...tabs[index], [prop]: value };
return { tabs };
});
};
render() {
return (
<div>
{this.state.tabs.map(tab => (
<span
key={tab.id}
onClick={() => this.modifyTab(tab, "name", Math.random())}
style={{ margin: '0 10px' }}
>
{tab.name}
</span>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I was watching an React Tutorial and the instructor did not explain a part very well for me to understand. He was basically trying teach how to render the list dynamically in three different input boxes. Whatever is typed in each input box will render to the according div element above it. And the instructor told us we should not touch the state directly which was where this code got more complicated. Any easier way to write this code? Not understanding him. The code that instructor instructed is in the nameChangeHandler function. Please see code below. Thanks!
import React, { Component } from 'react';
import './App.css';
import Person from "./Person/Person"
class App extends React.Component {
state={
persons: [
{id: 1,name: "Max", age:28 },
{id:2,name: "Manu", age: 29},
{id:3, name: "Stephanie", age: 26 }
],
showPersons: false
}
deletePersonHandler=(index)=> {
const persons = [...this.state.persons];
persons.splice(index, 1)
this.setState({ persons: persons});
console.log(persons)
}
nameChangedHandler = (e, id ) => {
const personIndex = this.state.persons.findIndex(p=> {
return p.id === id;
})
const person = {
...this.state.persons[personIndex]
};
person.name= e.target.value;
const persons = [...this.state.persons];
persons[personIndex] = person;
this.setState({
persons: persons
})
}
togglePersonsHandler=()=> {
const showing = this.state.showPersons;
this.setState({ showPersons: !showing })
}
render() {
const style={
backgroundColor: "white",
font:"inherit",
border:"1px solid blue",
padding:"8px",
cursor:"pointer"
}
let persons=null;
if(this.state.showPersons) {
persons=(
<div>
{this.state.persons.map((person, index)=> {
return(
<Person
key={person.id}
changed={(e)=>this.nameChangedHandler(e, person.id)}
click={()=>this.deletePersonHandler(index)}
name={person.name}
age={person.age}/>
)
})}
</div>)
}
return (
<div className="App">
<h1>Hi, Im a React App</h1>
<p>This is really working!!!</p>
<button style={style} onClick={this.togglePersonsHandler}>Toggle Persons</button>
{persons}
</div>
);
}
}
export default App;
As per your request in the comments here is a brief explenation of this code:
nameChangedHandler = (e, id ) => {
const personIndex = this.state.persons.findIndex(p=> {
return p.id === id;
})
What you see is an arrow function. For the purpose of this entire answer, treat them as normal function (it is not the same, however it could be done with regular functions as well). Semantically speaking, arrow functions or regular functions does not change what the code is doing/its intention so I will not go into details, you should just be aware of what you are seeing. If you are unfamiliar with them though, you should read up on it, they are very useful. The signature for an arrow function is either (a,b) => {}, a => {} or a => <expression>. So roughly speaking the above can be logically interpreted as function(e,id){} and function(p){} just to clear that up before I proceed (it would not work if written that way, but that is the message it conveys).
The code itself extracts the index of the person that matches the id parameter that you passed to the nameChangeHandler. This is done using findIndex, a function that iterates through the array (.persons array of your state in this case), and returns the index of the first element that passes the test function given. This index is then stored inside a variable for usage later in the code.
The values
e and id are coming from invocation of the function itself, I cannot give you more detail, since I do not see what the <Person> class is, but it is safe to assume that this handler is being attached to an input field. Once a change happens via the onChange handler on an input field, react will trigger a handler and pass an event containing the event data to it. Your handler is actually not the nameChangeHandler function, it is an arrow function which takes an event e, and then calls the nameChangeHandler passing both the event e as well as the id for the person, you can see that here changed={(e)=>this.nameChangedHandler(e, person.id)}. Rest of the values are read from your state.
Let's continue with the code:
const person = {
...this.state.persons[personIndex]
};
What we have here is called a spread. It essentially "unpacks and repacks" the object or an array, you can read more about it on the MDN link given. This is a powerful new feature of ES6 that makes life a lot easier.
So the above code is used to cleverly shallow copy a person object from the array into a new local variable (or rather a const, since variable would imply a possibility of change).We do this because in javascript, object data is stored by reference, so we cannot simply change the person object inside the initial array, that would mutate the state. We do not want to mutate the state. Immutable is the key here.
person.name= e.target.value;
Following that we have a simple asignment. The new person object we just created is an exact (sort of) copy of what the person inside the state's .persons array was, and that is no good, we want to change the name, so we do exactly that. We access the event e and read the value of the target that triggered it, assign the new value to our person object, and now we have a "changed man" (pun intended).
What is left for us to do is, push these changes into the state so that a new render can show them, so we do:
const persons = [...this.state.persons];
persons[personIndex] = person;
This code again uses the spread to clone/copy an old array from the state into a new local array persons. It is equvivalent to using const persons = this.state.persons.slice(). You can read more about .slice() on the MDN (intentionally not leaving a direct link for you so that you search for it and learn that part as well, MDN is really a great source for documentation of all sorts and getting to know your way around that website is a lifesaver.). Lastly, after the array is cloned, we find the original person and replace it with out local person object, that has a changed name.
this.setState({
persons: persons
})
Lastly we use the .setState method that react provides (see documentation) to immutably change the state. This change will trigger a new render() and you will be able to see the changes in the UI. The .setState() itself operates by doing a shallow merge. This means that only the properties that you pass to the method itself will be changed/added to the state, rest of the properties will be kept, as-is. Since the only thing we pass is a different array of persons which is our local array, with the changed person, that is the only thing that changes.
When updating local state based off of current local state, you should be using the setState callback pattern, because of the way calls to this.setState are batched by React.
Also, shallow-copy and update is perfectly fine, however you could also use the standard Array.prototype.map method to perform the update:
nameChangedHandler = (e, id ) => {
this.setState((prevState) => {
const persons = prevState.persons.map(person, i => {
return i === id ? { ...person, name: e.target.name } : person;
});
return {
persons: persons,
};
});
}
import React, { Component} from 'react';
import './App.css';
import Person from './Person/Person';
class App extends Component{
state = {
persons : [
{id:1, name : 'sohan', age : 28},
{id:2, name : 'Rohan', age : 29},
{id:3, name : 'Stephani', age : 21}
],
otherState : 'hiii',
showPersons : false
}
nameChangeHandler = (event, id)=>{
const personInedex = this.state.persons.find(p => {
return p.id === id;
})
personInedex.name = event.target.value;
this.setState({persons:this.state.persons})
}
toglePerson = ()=>{
this.setState({showPersons:!this.state.showPersons})
}
delete = (item) =>{
const persons = [...this.state.persons];
persons.splice(item, 1);
this.setState({persons: persons});
}
render(){
const style = {
backgroundColor: '#88dc707d',
margin: 0,
font: 'inherit',
padding : '15px',
cursor: 'pointer',
color: 'white',
boxShadow: '15px 10px 12px grey',
borderRadius: '22px 5px 22px 5px'
}
let persons = null
if(this.state.showPersons){
persons = (
<div>
{this.state.persons.map((item, index) => {
return <Person key={item.id}
name={item.name}
age={item.age}
onchange={(event) => this.nameChangeHandler(event, item.id)}
deleteName={()=> this.delete(index)} />
})}
</div>
)
}
return (
<div className="App">
<h1> hii i am React App</h1>
<button
style={style}
onClick={this.toglePerson}>Togle Name</button>
{persons}
</div>
);
}
}
export default App;
// state = {
// persons : [
// {name : 'sohan', age : 28},
// {name : 'Rohan', age : 29},
// {name : 'Stephani', age : 21}
// ],
// otherState : 'hiii'
// }
// switchNameHandler = () => {
// console.log(this.state)
// this.setState({
// persons : [
// {name : 'Sudan Lal', age : 28},
// {name : 'Rohan', age : 29},
// {name : 'Stephani', age : 25}`enter code here`
// ]
// })
// }
New to React.
I have a handler, as follows, that updates state of an array. The data is a set of animal pairs.
class Animal extends Component {
state = {
pairs: [
{ fromAnimal: 'Dog', toAnimal: 'Cat' },
{ fromAnimal: 'Lion', toAnimal: 'Tiger' },
{ fromAnimal: 'Rabbit', toAnimal: 'Bear' }
]
};
closePairHandler = (fromAnimal, toAnimal) => {
let newPairs = this.state.pairs.filter((pair) => {
return !(pair.fromAnimal === fromAnimal && pair.toAnimal === toAnimal);
});
console.log('pairs', newPairs); // This shows that the correct pair was removed from the array.
this.setState({ pairs: newPairs });
};
render() {
return (
<div>
{
this.state.pairs.map((pair, index) => {
return <SomeComponent key={index} pair={pair} closePair={(fromAnimal, toAnimal) => this.closePairHandler(fromAnimal, toAnimal)} />;
}
}
</div>
);
};
};
export default Animal;
This is a super simplified version of the code I have. BUT, when the closePairHandler is called to remove an animal pair (for example, Lion/Tiger). The console.log in the closePairHandler shows that the array has been updated successfully.
However, when the components render. It is removing the LAST component in the array and not the one that was selected. It's reducing the array size by 1, but not removing the correct item in the mapping (in render), althought the closePairHandler console.log is showing the array correctly updated before setting the state.
Can anyone explain to me what is going on here?
Thanks again!
You are not providing the key for your mapped data while rendering SomeComponent and hence react is not able to correctly identify what element got changed. You can use index as the key if you don't have a unique id in your pair object else you should use that for performance reasons
return (
<div>
{
this.state.pairs.map((pair, index) => {
return <SomeComponent key={index} pair={pair} closePair={(fromAnimal, toAnimal) => this.closePairHandler(fromAnimal, toAnimal)} />;
}
}
</div>
);