I'm trying to organize my state by using nested property like this:
this.state = {
someProperty: {
flag:true
}
}
But updating state like this,
this.setState({ someProperty.flag: false });
doesn't work. How can this be done correctly?
In order to setState for a nested object you can follow the below approach as I think setState doesn't handle nested updates.
var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
The idea is to create a dummy object perform operations on it and then replace the component's state with the updated object
Now, the spread operator creates only one level nested copy of the object. If your state is highly nested like:
this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}
You could setState using spread operator at each level like
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
However the above syntax get every ugly as the state becomes more and more nested and hence I recommend you to use immutability-helper package to update the state.
See this answer on how to update state with immutability-helper.
To write it in one line
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
Sometimes direct answers are not the best ones :)
Short version:
this code
this.state = {
someProperty: {
flag: true
}
}
should be simplified as something like
this.state = {
somePropertyFlag: true
}
Long version:
Currently you shouldn't want to work with nested state in React. Because React is not oriented to work with nested states and all solutions proposed here look as hacks. They don't use the framework but fight with it. They suggest to write not so clear code for doubtful purpose of grouping some properties. So they are very interesting as an answer to the challenge but practically useless.
Lets imagine the following state:
{
parent: {
child1: 'value 1',
child2: 'value 2',
...
child100: 'value 100'
}
}
What will happen if you change just a value of child1? React will not re-render the view because it uses shallow comparison and it will find that parent property didn't change. BTW mutating the state object directly is considered to be a bad practice in general.
So you need to re-create the whole parent object. But in this case we will meet another problem. React will think that all children have changed their values and will re-render all of them. Of course it is not good for performance.
It is still possible to solve that problem by writing some complicated logic in shouldComponentUpdate() but I would prefer to stop here and use simple solution from the short version.
Disclaimer
Nested State in React is wrong design
Read this excellent answer.
Reasoning behind this answer:
React's setState is just a built-in convenience, but you soon realise
that it has its limits. Using custom properties and intelligent use of
forceUpdate gives you much more.
eg:
class MyClass extends React.Component {
myState = someObject
inputValue = 42
...
MobX, for example, ditches state completely and uses custom observable properties.
Use Observables instead of state in React components.
the answer to your misery - see example here
There is another shorter way to update whatever nested property.
this.setState(state => {
state.nested.flag = false
state.another.deep.prop = true
return state
})
On one line
this.setState(state => (state.nested.flag = false, state))
note: This here is Comma operator ~MDN, see it in action here (Sandbox).
It is similar to (though this doesn't change state reference)
this.state.nested.flag = false
this.forceUpdate()
For the subtle difference in this context between forceUpdate and setState see the linked example and sandbox.
Of course this is abusing some core principles, as the state should be read-only, but since you are immediately discarding the old state and replacing it with new state, it is completely ok.
Warning
Even though the component containing the state will update and rerender properly (except this gotcha), the props will fail to propagate to children (see Spymaster's comment below). Only use this technique if you know what you are doing.
For example, you may pass a changed flat prop that is updated and passed easily.
render(
//some complex render with your nested state
<ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)
Now even though reference for complexNestedProp did not change (shouldComponentUpdate)
this.props.complexNestedProp === nextProps.complexNestedProp
the component will rerender whenever parent component updates, which is the case after calling this.setState or this.forceUpdate in the parent.
Effects of mutating the state sandbox
Using nested state and mutating the state directly is dangerous because different objects might hold (intentionally or not) different (older) references to the state and might not necessarily know when to update (for example when using PureComponent or if shouldComponentUpdate is implemented to return false) OR are intended to display old data like in the example below.
Imagine a timeline that is supposed to render historic data, mutating the data under the hand will result in unexpected behaviour as it will also change previous items.
Anyway here you can see that Nested PureChildClass is not rerendered due to props failing to propagate.
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);
If you are using ES2015 you have access to the Object.assign. You can use it as follows to update a nested object.
this.setState({
someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});
You merge the updated properties with the existing and use the returned object to update the state.
Edit: Added an empty object as target to the assign function to make sure the state isn't mutated directly as carkod pointed out.
We use Immer https://github.com/mweststrate/immer to handle these kinds of issues.
Just replaced this code in one of our components
this.setState(prevState => ({
...prevState,
preferences: {
...prevState.preferences,
[key]: newValue
}
}));
With this
import produce from 'immer';
this.setState(produce(draft => {
draft.preferences[key] = newValue;
}));
With immer you handle your state as a "normal object".
The magic happens behind the scene with proxy objects.
There are many libraries to help with this. For example, using immutability-helper:
import update from 'immutability-helper';
const newState = update(this.state, {
someProperty: {flag: {$set: false}},
};
this.setState(newState);
Using lodash/fp set:
import {set} from 'lodash/fp';
const newState = set(["someProperty", "flag"], false, this.state);
Using lodash/fp merge:
import {merge} from 'lodash/fp';
const newState = merge(this.state, {
someProperty: {flag: false},
});
Although you asked about a state of class-based React component, the same problem exists with useState hook. Even worse: useState hook does not accept partial updates. So this question became very relevant when useState hook was introduced.
I have decided to post the following answer to make sure the question covers more modern scenarios where the useState hook is used:
If you have:
const [state, setState] = useState({
someProperty: {
flag: true,
otherNestedProp: 1
},
otherProp: 2
})
you can set the nested property by cloning the current and patching the required segments of the data, for example:
setState(current => { ...current,
someProperty: { ...current.someProperty,
flag: false
}
});
Or you can use Immer library to simplify the cloning and patching of the object.
Or you can use Hookstate library (disclaimer: I am an author) to simply the management of complex (local and global) state data entirely and improve the performance (read: not to worry about rendering optimization):
import { useHookstate } from '#hookstate/core'
const state = useHookstate({
someProperty: {
flag: true,
otherNestedProp: 1
},
otherProp: 2
})
get the field to render:
state.someProperty.flag.get()
// or
state.get().someProperty.flag
set the nested field:
state.someProperty.flag.set(false)
Here is the Hookstate example, where the state is deeply / recursively nested in tree-like data structure.
Here's a variation on the first answer given in this thread which doesn't require any extra packages, libraries or special functions.
state = {
someProperty: {
flag: 'string'
}
}
handleChange = (value) => {
const newState = {...this.state.someProperty, flag: value}
this.setState({ someProperty: newState })
}
In order to set the state of a specific nested field, you have set the whole object. I did this by creating a variable, newState and spreading the contents of the current state into it first using the ES2015 spread operator. Then, I replaced the value of this.state.flag with the new value (since I set flag: value after I spread the current state into the object, the flag field in the current state is overridden). Then, I simply set the state of someProperty to my newState object.
I used this solution.
If you have a nested state like this:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
you can declare the handleChange function that copy current status and re-assigns it with changed values
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
here the html with the event listener
<input type="text" onChange={this.handleChange} " name="friendName" />
Although nesting isn't really how you should treat a component state, sometimes for something easy for single tier nesting.
For a state like this
state = {
contact: {
phone: '888-888-8888',
email: 'test#test.com'
}
address: {
street:''
},
occupation: {
}
}
A re-useable method ive used would look like this.
handleChange = (obj) => e => {
let x = this.state[obj];
x[e.target.name] = e.target.value;
this.setState({ [obj]: x });
};
then just passing in the obj name for each nesting you want to address...
<TextField
name="street"
onChange={handleChange('address')}
/>
Create a copy of the state:
let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))
make changes in this object:
someProperty.flag = "false"
now update the state
this.setState({someProperty})
Not sure if this is technically correct according to the framework's standards, but sometimes you simply need to update nested objects. Here is my solution using hooks.
setInputState({
...inputState,
[parentKey]: { ...inputState[parentKey], [childKey]: value },
});
I am seeing everyone has given the class based component state update solve which is expected because he asked that for but I am trying to give the same solution for hook.
const [state, setState] = useState({
state1: false,
state2: 'lorem ipsum'
})
Now if you want to change the nested object key state1 only then you can do the any of the following:
Process 1
let oldState = state;
oldState.state1 = true
setState({...oldState);
Process 2
setState(prevState => ({
...prevState,
state1: true
}))
I prefer the process 2 most.
Two other options not mentioned yet:
If you have deeply nested state, consider if you can restructure the child objects to sit at the root. This makes the data easier to update.
There are many handy libraries available for handling immutable state listed in the Redux docs. I recommend Immer since it allows you to write code in a mutative manner but handles the necessary cloning behind the scenes. It also freezes the resulting object so you can't accidentally mutate it later.
To make things generic, I worked on #ShubhamKhatri's and #Qwerty's answers.
state object
this.state = {
name: '',
grandParent: {
parent1: {
child: ''
},
parent2: {
child: ''
}
}
};
input controls
<input
value={this.state.name}
onChange={this.updateState}
type="text"
name="name"
/>
<input
value={this.state.grandParent.parent1.child}
onChange={this.updateState}
type="text"
name="grandParent.parent1.child"
/>
<input
value={this.state.grandParent.parent2.child}
onChange={this.updateState}
type="text"
name="grandParent.parent2.child"
/>
updateState method
setState as #ShubhamKhatri's answer
updateState(event) {
const path = event.target.name.split('.');
const depth = path.length;
const oldstate = this.state;
const newstate = { ...oldstate };
let newStateLevel = newstate;
let oldStateLevel = oldstate;
for (let i = 0; i < depth; i += 1) {
if (i === depth - 1) {
newStateLevel[path[i]] = event.target.value;
} else {
newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
oldStateLevel = oldStateLevel[path[i]];
newStateLevel = newStateLevel[path[i]];
}
}
this.setState(newstate);
}
setState as #Qwerty's answer
updateState(event) {
const path = event.target.name.split('.');
const depth = path.length;
const state = { ...this.state };
let ref = state;
for (let i = 0; i < depth; i += 1) {
if (i === depth - 1) {
ref[path[i]] = event.target.value;
} else {
ref = ref[path[i]];
}
}
this.setState(state);
}
Note: These above methods won't work for arrays
I take very seriously the concerns already voiced around creating a complete copy of your component state. With that said, I would strongly suggest Immer.
import produce from 'immer';
<Input
value={this.state.form.username}
onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
This should work for React.PureComponent (i.e. shallow state comparisons by React) as Immer cleverly uses a proxy object to efficiently copy an arbitrarily deep state tree. Immer is also more typesafe compared to libraries like Immutability Helper, and is ideal for Javascript and Typescript users alike.
Typescript utility function
function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s:
Draft<Readonly<S>>) => any) {
comp.setState(produce(comp.state, s => { fn(s); }))
}
onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}
setInputState((pre)=> ({...pre,[parentKey]: {...pre[parentKey], [childKey]: value}}));
I'd like this
If you want to set the state dynamically
following example sets the state of form dynamically where each key in state is object
onChange(e:React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
this.setState({ [e.target.name]: { ...this.state[e.target.name], value: e.target.value } });
}
I found this to work for me, having a project form in my case where for example you have an id, and a name and I'd rather maintain state for a nested project.
return (
<div>
<h2>Project Details</h2>
<form>
<Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
<Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
</form>
</div>
)
Let me know!
stateUpdate = () => {
let obj = this.state;
if(this.props.v12_data.values.email) {
obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
}
this.setState(obj)
}
This is clearly not the right or best way to do, however it is cleaner to my view:
this.state.hugeNestedObject = hugeNestedObject;
this.state.anotherHugeNestedObject = anotherHugeNestedObject;
this.setState({})
However, React itself should iterate thought nested objects and update state and DOM accordingly which is not there yet.
Use this for multiple input control and dynamic nested name
<input type="text" name="title" placeholder="add title" onChange={this.handleInputChange} />
<input type="checkbox" name="chkusein" onChange={this.handleInputChange} />
<textarea name="body" id="" cols="30" rows="10" placeholder="add blog content" onChange={this.handleInputChange}></textarea>
the code very readable
the handler
handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
const newState = { ...this.state.someProperty, [name]: value }
this.setState({ someProperty: newState })
}
Here's a full example using nested state (one level) with the solution in this answer, for a component implemented as a class:
class CaveEditModal extends React.Component {
// ...
constructor(props, context) {
super(props);
this.state = {
tabValue: '1',
isModalOpen: this.props.isModalOpen,
// ...
caveData: {
latitude: 1,
longitude: 2
}
};
// ...
const updateNestedFieldEvent = fullKey => ev => {
var [parentProperty, _key] = fullKey.split(".", 2);
this.setState({[parentProperty]: { ...this.state[parentProperty], [_key]: ev.target.value} });
};
// ...
this.handleLatitudeChange = updateNestedFieldEvent('caveData.latitude');
this.handleLongitudeChange = updateNestedFieldEvent('caveData.longitude');
}
render () {
return (
<div>
<TextField id="latitude" label="Latitude" type="number" value={this.state.caveData.latitude} onChange={this.handleLatitudeChange} />
<TextField id="longitude" label="Longitude" type="number" value={this.state.caveData.longitude} onChange={this.handleLongitudeChange} />
<span>lat={this.state.caveData.latitude} long={this.state.caveData.longitude}</span>
</div>
);
};
}
Note that the state updater function updateNestedFieldEvent works only for one level nested object like a.b, not like a.b.c.
For someone who read in 2022:
constructor(props) {
super(props);
this.state = {
someProperty: {
flag: true
}
otherValues: '',
errors: {}
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
const someProperty = { ...this.state.someProperty };
someProperty[name] = value;
this.setState({
someProperty: someProperty
});
}
.......
Use arrow function instead, this should do the trick.
setItems((prevState) => {
prevState.nestedData = newNestedData;
prevState.nestedData1 = newNestedData1;
});
don't forget to use the arrow function (prevState) => {update js assignment statements...}
Something like this might suffice,
const isObject = (thing) => {
if(thing &&
typeof thing === 'object' &&
typeof thing !== null
&& !(Array.isArray(thing))
){
return true;
}
return false;
}
/*
Call with an array containing the path to the property you want to access
And the current component/redux state.
For example if we want to update `hello` within the following obj
const obj = {
somePrimitive:false,
someNestedObj:{
hello:1
}
}
we would do :
//clone the object
const cloned = clone(['someNestedObj','hello'],obj)
//Set the new value
cloned.someNestedObj.hello = 5;
*/
const clone = (arr, state) => {
let clonedObj = {...state}
const originalObj = clonedObj;
arr.forEach(property => {
if(!(property in clonedObj)){
throw new Error('State missing property')
}
if(isObject(clonedObj[property])){
clonedObj[property] = {...originalObj[property]};
clonedObj = clonedObj[property];
}
})
return originalObj;
}
const nestedObj = {
someProperty:true,
someNestedObj:{
someOtherProperty:true
}
}
const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true
console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty)
I know it is an old question but still wanted to share how i achieved this. Assuming state in constructor looks like this:
constructor(props) {
super(props);
this.state = {
loading: false,
user: {
email: ""
},
organization: {
name: ""
}
};
this.handleChange = this.handleChange.bind(this);
}
My handleChange function is like this:
handleChange(e) {
const names = e.target.name.split(".");
const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
this.setState((state) => {
state[names[0]][names[1]] = value;
return {[names[0]]: state[names[0]]};
});
}
And make sure you name inputs accordingly:
<input
type="text"
name="user.email"
onChange={this.handleChange}
value={this.state.user.firstName}
placeholder="Email Address"
/>
<input
type="text"
name="organization.name"
onChange={this.handleChange}
value={this.state.organization.name}
placeholder="Organization Name"
/>
I do nested updates with a reduce search:
Example:
The nested variables in state:
state = {
coords: {
x: 0,
y: 0,
z: 0
}
}
The function:
handleChange = nestedAttr => event => {
const { target: { value } } = event;
const attrs = nestedAttr.split('.');
let stateVar = this.state[attrs[0]];
if(attrs.length>1)
attrs.reduce((a,b,index,arr)=>{
if(index==arr.length-1)
a[b] = value;
else if(a[b]!=null)
return a[b]
else
return a;
},stateVar);
else
stateVar = value;
this.setState({[attrs[0]]: stateVar})
}
Use:
<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>
I need to destructure the filter Object from Array with useState in React, but I can't get it.
const [filter, setFilter] = useState([]);
const { column, comparison, value } = filter;
console.log(column); // undefined
I tried braces, brackets, and still getting undefined.
Does anyone knows how to get that values?
The filter object:
filter: [
{
column: 'population',
comparison: 'greater than',
value: '100000',
}
]
Console log:
Your filter is an array, not an object. Try:
const { column, comparison, value } = filter[0];
Or you can destrcuture your filter array and then do the object destructiong:
const [firstItem] = filter;
const { column, comparison, value } = firstItem;
Based on your latest screenshot of the entire component, the problem is that you are destructuring the filterByNumericValues array before it is hydrated with the data. Then your console.log has the correct data because it is in the useEffect hook AFTER the state has been updated with the data.
Since it looks like you are only using the column, comparison, and value variables in the checkFilterByNumeric function, I would destructure the state in that scope.
function checkFilterByNumeric(planet) {
const { column, comparison, value } = filterByNumericValues[0];
if (comparison === "maior que") {
...
}
The easiest way is to destructure the object as the first element of the array.
But how you're getting filter into the component, whether it's via an API request in a useEffect, component props, a third-party state management library like Redux, or even React's own context, will determine how you can log/render the information.
The data at the point where you're trying to log/render it simply might not be available at that time.
Here's an small example that passes in filter as a component prop, and destructures the object from the array.
const { useState } = React;
function Example({ data }) {
const [ filter, setFilter ] = useState(data);
// The object that is the first element of the array
const [{ column, comparison, value }] = filter;
return <div>{column}</div>;
}
const filter = [{
column: 'population',
comparison: 'greater than',
value: '100000'
}];
ReactDOM.render(
<Example data={filter} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can do this, for example:
const defaultValue = { column: 'empty', comparison: 'empty', value: 0 }
const { column, comparison, value } = filter[0] || defaultValue;
hoping this is a simple answer but it's racking my brain. Let's say I have the following React pseudocode:
const [cats, setCats] = useState({});
console.log(cats);
cats = {
}
I am trying to add uh cats to the cats object via a button click. I'm not sure what the exact code should be, as any guess I have so far doesn't work. The closest i've gotten is the following pseudo code:
heres the object i'm sending in:
{
Bruto:{
id : 2,
name: 'Bruto',
}
}
const addCat (e, catName) => {
setCats(...cats, event.target.value);
}
<input onKeyDown={e=>addCat(e,catDetails)}></input>
Which all it does is just add the word typed into the input as cats, but doesn't add it structurally. Ideally what i'd want is the following:
1. Check to see if catDetails exists in cats
2. If not, add the catDetails.id to the object
3. Add catDetails.name to cats[catDetails.id] such as the object should be:
cats{
0: {
name: "Heathcliff"
}
2: {
name: "Bruto"
}
}
Hoping someone can help. Below is the code I have so far
const addTag = (event, cats) => {
if (event.key === "Enter"){
setCats(...cats, event.target.value)
}
console.log(cats);
}
Thank you!
You'll probably find an array easier to use.
Have one state (an array) for all your cat objects, and have another (string) for the cat you're adding in the input, and then update the cats array with the current cat if it's not been found.
const { useEffect, useState } = React;
function Example() {
// Initialise cat states
const [ cats, setCats ] = useState([]);
const [currentCat, setCurrentCat] = useState('');
// Updates the currentCat state when the input changes
function handleChange(e) {
setCurrentCat(e.target.value);
}
// First find out if the cat name we're entering exists
// in the cats array. If it doesn't, add it
function addCat(e) {
const found = cats.find(cat => cat.name === currentCat);
if (!found) {
setCats([
...cats,
{ id: cats.length + 1, name: currentCat }
]);
}
}
useEffect(() => console.log(cats), [cats]);
return (
<div>
<input
type="text"
onChange={handleChange}
/>
<button
type="button"
onClick={addCat}
>Add cat
</button>
</div>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can do:
setCats( prevCats => {...prevCats, {id:2, name:"Bruto"});
Or if you want the IDs as keys:
setCats( prevCats => {...prevCats, [id]: name});
Instead of passing the new state to setCats you pass a function which will automatically receive the previous state, and whatever is returned will be used as the new state.
Or even better: you can use useReducer instead of useState
I want to update setTopic without overriding previous state. But I am getting topic is not iterable error.
What I tried?
I tried looking a different examples on stack overflow, But still couldn't figure out how to append updated state without losing previous state.
Also, which is a better way to save multiple topics : an array of objects, simply objects or simply array?
const AddTopic = (props) => {
const { subjectName } = props;
const [topic, setTopic] = useState([
{
topics: [
{
id: Math.random().toString(36).substr(2, 7),
topicName: "topic name",
subject: subjectName,
},
],
},
]);
const addTopicHandler = () => {
setTopic(
[...topic].map((item) => {
return {
...item,
id: Math.random().toString(36).substr(2, 7),
topicName: "another topic name",
subject: subjectName,
};
})
);
};
console.log(topic);
Instead of the child component using state lift the state to a parent component, and then just create dumb Topic components from the state.
Rename your state. Call it topics, and the update function setTopics. Initialise it as an array, not an array containing one object containing an array.
You can't immediately log an updated state. You need to use useEffect to watch for changes in state, and then log something.
const { useEffect, useState } = React;
// Topic component - just gets handed the
// subject (in this example) in the props
function Topic({ subject, category }) {
return <div>{subject}: {category}</div>;
}
function Example() {
// Initialise `topics` as an array
const [ topics, setTopics ] = useState([]);
// When `topics` is updated, log the updated state
useEffect(() => console.log(JSON.stringify(topics)), [topics]);
// Helper function that maps over the state and
// produces an array of topics
function getTopics() {
return topics.map(topic => {
const { subject, category } = topic;
return (
<Topic
subject={subject}
category={category}
/>
);
});
}
// Helper function to add a new topic object
// to the topics state
function addTopic() {
const obj = { subject: 'Math', category: 'Fish' };
setTopics([...topics, obj ]);
}
return (
<div>
<div>{getTopics()}</div>
<button onClick={addTopic}>Add topic</button>
</div>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try to replace [...topic].map with [...topic.topics].map.
edit
Sorry, I didn't see that the topic itself is an arr of objs, so it should be: [...topic[0].topics].map.