React hooks: Can't update state inside function component - javascript

I'm new to React and am trying to figure out how to make a phonebook. I've gotten quite far but I'm having an issue I can't solve. I'm using React Hooks.
Everything works fine, except for when I call the setNewNumber('') and setNewName('') functions at the end of addPerson(), just before the filteredEntries const.
I want to reset the input fields in the form to empty strings ('') once the other code inside addPerson() is done running, but it seems like the two functions are never called since the value for newName and newNumber don't change to '' (instead they keep the values the user added). However, my other useState functions (setPersons() and setFilteredPersons()) work inside addPerson()...
I've tried reading the documentation and asking around but haven't found a solution. I'd be very grateful for any clues/help!
import React, { useState } from 'react'
import Person from './components/Person'
const App = () => {
const [ persons, setPersons ] = useState([
{ name: 'Cat', number: '111' },
{ name: 'Dog', number: '222' },
{ name: 'Horse', number: '333' },
{ name: 'Owl', number: '444' }
])
const [filteredPersons, setFilteredPersons] = useState([...persons])
const [ newName, setNewName ] = useState('')
const [ newNumber, setNewNumber ] = useState('')
const addPerson = (event) => {
event.preventDefault()
const createPerson = () => {
const personObject = {
name: newName,
number: newNumber,
}
setPersons([...persons, personObject])
setFilteredPersons([...persons, personObject]) //varför går det inte att bara köra [...persons?]
}
const upperCaseNewName = newName.toUpperCase()
let doubleName
persons.map(person => {
const upperCasePerson = person.name.toUpperCase()
if(upperCaseNewName === upperCasePerson) {
doubleName = upperCasePerson
}
return doubleName
})
if (doubleName === undefined) {
createPerson()
} else if(doubleName === upperCaseNewName) {
alert(`${newName} is already in the phonebook`)
}
setNewName('')
setNewNumber('')
}
const filterEntries = event => {
let filtered = persons.filter(person => {
return person.name.toUpperCase().indexOf(event.target.value.toUpperCase()) !== -1
})
setFilteredPersons(filtered)
}
const renderPersons = () => filteredPersons.map(person =>
<Person key={person.name} name={person.name} number={person.number}/>
)
return (
<div>
<h2>Phonebook</h2>
<p>Filter entries:</p> <input onChange={(event) => filterEntries(event)}/>
<form>
<div>
name: <input onChange={(event) => setNewName(event.target.value)}/>
<br/>
phone: <input onChange={(event) => setNewNumber(event.target.value)}/>
</div>
<div>
<button type="submit" onClick={addPerson}>add</button>
</div>
</form>
<h2>Numbers</h2>
{renderPersons()}
</div>
)
}
export default App
The person component at the top just contains this code:
import React from 'react'
const Person = (props) => {
return(
<>
<p>{props.name} {props.number}</p>
</>
)
}
export default Person

Your components are not actually tied to your state values. You need them to be "controlled." Check out the docs for more examples :)
https://reactjs.org/docs/forms.html#controlled-components
<input value={newName} onChange={event => setNewName(event.target.value)} />

The reset is working correctly. What you forgot to do is add the value to each input. Without the value attribute the input is considered an uncontrolled component. By the sounds of it, you're looking to control the value via code.
Change
<div>
name: <input onChange={event => setNewName(event.target.value)} />
<br />
phone: <input onChange={event => setNewNumber(event.target.value)} />
</div>
to
<div>
name: <input value={newName} onChange={event => setNewName(event.target.value)} />
<br />
phone: <input value={newNumber} onChange={event => setNewNumber(event.target.value)} />
</div>
Codesandbox Demo

You have missed adding value attribute to input and thus your component is not the "Controlled" component.
You can read more here.
Changes needed
<input
value={newName}
onChange={event => setNewName(event.target.value)}
/>
<br />
phone:
<input
value={newNumber}
onChange={event => setNewNumber(event.target.value)}
/>
Codesandbox Link: https://codesandbox.io/s/crimson-frog-ecp98
Hope this Helps!

Related

How do I set initial values from state to dynamically added inputs?

I have dynamic inputs I can add and save to the state, but I want to be able to set initial values, to begin with. I would like to update those values and resave those edits at any time.
Here is the full code below. You can also check out the SANDBOX HERE
import { useState } from "react";
// I want to use these as my initial values. This is the object in my database:
const InitialValuesDB = [{name: "John", age: "108"}, {name: "Jane", age: "204"}]
function Form() {
const [formFields, setFormFields] = useState([{ name: "", age: "" }]);
// I can iterate the values like this:
function LoggingMap() {
InitialValuesDB.map((item, i) => {
console.log('Index:', i, 'name:', item.name);
console.log(item.name)
// But I can't access theme outside of this function:
});
}
LoggingMap()
const handleFormChange = (event, index) => {
let data = [...formFields];
data[index][event.target.name] = event.target.value;
setFormFields(data);
};
const submit = (e) => {
e.preventDefault();
console.log(formFields);
};
const addFields = () => {
let object = {
name: "",
age: "",
};
setFormFields([...formFields, object]);
};
const removeFields = (index) => {
let data = [...formFields];
data.splice(index, 1);
setFormFields(data);
};
return (
<div className="App">
<form onSubmit={submit}>
{formFields.map((form, index) => {
return (
<div key={index}>
{/* But how do I set my initial values (item.name, item.age) as initial values, so that when I reload, the saved values return */}
<input
name="name"
placeholder="Name"
onChange={(event) => handleFormChange(event, index)}
value={form.name}
/>
<input
name="age"
placeholder="Age"
onChange={(event) => handleFormChange(event, index)}
value={form.age}
/>
<button onClick={() => removeFields(index)}>Remove</button>
</div>
);
})}
</form>
<button onClick={addFields}>Add More..</button>
<br />
<button onClick={submit}>Submit</button>
</div>
);
}
export default Form;
Expected Results
If I have 5 inputs with values submitted, I want those values saved in a state and on reload, have those as initial values. I want to edit the inputs, resave that, etc.
For initially putting the items
you should replace your useState with the initial value.
Replace this with:
const [formFields, setFormFields] = useState([{ name: "", age: "" }]);
This
const [formFields, setFormFields] = useState(InitialValuesDB);
Use localStorage, write the state values to localStorage when state updates and read from localStorage on initial render to set the state back to what it was previously before the reload.
EDIT: Try out the following code and see if it fits your usecase.
import { useEffect, useState } from "react";
// I want to use these as my initial values. This is the object in my database:
const InitialValuesDB = [
{ name: "John", age: "108" },
{ name: "Jane", age: "204" },
];
function Form() {
const [formFields, setFormFields] = useState(JSON.parse(localStorage.getItem("key"))|| InitialValuesDB || [{ name: "", age: "" }]);
useEffect(() => {
localStorage.setItem("key", JSON.stringify(formFields))
},[formFields])
// I can iterate the values like this:
function LoggingMap() {
InitialValuesDB.map((item, i) => {
console.log("Index:", i, "name:", item.name);
console.log(item.name);
// But I can't access theme outside of this function:
});
}
LoggingMap();
const handleFormChange = (event, index) => {
let data = [...formFields];
data[index][event.target.name] = event.target.value;
setFormFields(data);
};
const submit = (e) => {
e.preventDefault();
console.log(formFields);
};
const addFields = () => {
let object = {
name: "",
age: "",
};
setFormFields([...formFields, object]);
};
const removeFields = (index) => {
let data = [...formFields];
data.splice(index, 1);
setFormFields(data);
};
return (
<div className="App">
<form onSubmit={submit}>
{formFields.map((form, index) => {
return (
<div key={index}>
{/* But how do I set my initial values (item.name, item.age) as initial values, so that when I reload, the saved values return */}
<input
name="name"
placeholder="Name"
onChange={(event) => handleFormChange(event, index)}
value={form.name}
/>
<input
name="age"
placeholder="Age"
onChange={(event) => handleFormChange(event, index)}
value={form.age}
/>
<button onClick={() => removeFields(index)}>Remove</button>
</div>
);
})}
</form>
<button onClick={addFields}>Add More..</button>
<br />
<button onClick={submit}>Submit</button>
</div>
);
}
export default Form;
I think I don't fully understand you question but here my solution.
Just add useEffect after your removeFields function
useEffect(() => {
setFormFields(InitialValuesDB)
}, [])
Use usefieldarry api of react hook form to maintain dynamic input fields in react form that's great and very simple.
Here is working code sandbox link
https://codesandbox.io/s/nice-swartz-7exhy2?file=/src/form.jsx
Note: I have no knowledge of typescript but implemented it in JavaScript I hope you can convert it into typescript

My form is not updating when its. preloaded with default value - react

I have these input fields I want to already show default values in, but i also want to be able to update those values
here is the code- https://stackblitz.com/edit/react-vfcqkh?file=src/App.js
import React, { useState } from 'react';
import './style.css';
export default function App() {
const baseData = [
{nutrient: 'Energy',per100: '449kcal', per35: '157kcal', "id": 6 },
{nutrient: 'Fat',per100: '24.4g', per35: '8.6g', "id": 1 },
{ nutrient: 'Saturated fat',per100: '4.5g', per35: '1.6g', "id": 2 },
{ nutrient: 'Monounsaturated fat',per100: '13.6g', per35: '4.8g', "id": 3 }
];
// set original state as your data
const [data, setData] = useState(baseData);
const updateValue = e => {
const copiedData = [...data]; //to avoid mutating state
let index = copiedData.findIndex(obj => obj.id == e.target.id);
copiedData[index].nutrient = e.target.value;
setData(copiedData);
};
return (
<div>
<div>
<div>
{ Object.keys(baseData[0]).map(({id,nutrient}) => (
<input type="text" key={id} id={id} value={nutrient} name="productName" onChange={(e) => updateValue(e)} />
))}
</div>
{baseData.map((item) => (
<div key={item.id}>
{Object.values(item).map(({id,nutrient}) => (
<input type="text" key={id} id={id} value={nutrient} id="name" name="productName" onChange={(e) => updateValue(e)} />
))}
</div>
))}
</div>
</div>
);
}
I think you are trying to display the initial values of the form then while the user is typing or typed in values the value in the <input type="text" name="name"/> will be update or do you want to update the state with the value typed in the input field?
Displaying Default values
change the value prop/attribute of the input to defaultValue
<input type="text" key={id} id={id} defaultValue={nutrient} name="productName" onChange={(e) => updateValue(e)} />
This way user can type in value and the initial values will also be displayed.
Update values
in you update function, you can access the value that the user typed by doing this:
updateValue(e){
let value= e.target.value
// then do state update here
}
Re-defined Solution
This should work properly as I have tested the code and run it successfully
import React, {useState} from 'react'
import './style.css';
export default function App() {
const baseData = [
{ nutrient: "Energy", per100: "449kcal", per35: "157kcal", id: 6 },
{ nutrient: "Fat", per100: "24.4g", per35: "8.6g", id: 1 },
{ nutrient: "Saturated fat", per100: "4.5g", per35: "1.6g", id: 2 },
{ nutrient: "Monounsaturated fat", per100: "13.6g", per35: "4.8g", id: 3 },
];
// set original state as your data
const [data, setData] = useState(baseData);
const updateValue = (e) => {
const copiedData = [...data]; //to avoid mutating state
let index = copiedData.findIndex((obj) => obj.id === e.target.id);
copiedData[index].nutrient = e.target.value;
setData(copiedData);
};
// initialize vars to hold the baseData object keys and vals
let baseDataKeys = [],
baseDataVals = [];
// first get all the keys of the objects and store them in this var
baseData.forEach((keys) => {
baseDataKeys = Object.keys(keys);
});
// debug to see the keys
console.log(baseDataKeys);
// Secondly get all the values of the objects and store then in this var
baseData.forEach((vals) => {
baseDataVals = Object.values(vals);
});
// debug to see the valus
console.log(baseDataVals);
return (
<div>
<div>
<div>
{baseDataKeys.map((keys) => (
<input
type="text"
key={keys}
id={keys}
defaultValue={keys}
name="productName"
onChange={(e) => updateValue(e)}
/>
))}
</div>
{/* This is for the values */}
{baseDataVals.map((vals) => (
<div key={vals}>
<input
type="text"
id={vals}
defaultValue={vals}
id="name"
name="productName"
onChange={(e) => updateValue(e)}
/>
</div>
))}
</div>
</div>
);
}
Try this updated solution this is going to work accurately

React Js : How to use UseState In CallBack?

I have below code :
import React,{useState} from 'react'
function ReactForm() {
const iState =[{
Name : '',
Email :'',
Salary :0
}]
const [state, setstate] = useState(iState);
function validationHandler()
{
console.log(state);
}
return (
<div>
Name : <input type="text" onChange={(e)=>{setstate(...state, state.Name=e.target.value)}}></input>
<br></br>
Email : <input type="text" onChange={(e)=>{setstate(...state, state.Email=e.target.value)}}></input>
<br></br>
Salary : <input type="text" onChange={(e)=>{setstate(...state, state.Salary=e.target.value)}}></input>
<br></br>
<button onClick={validationHandler}>Validate Us</button>
</div>
)
}
export default ReactForm
I am performing basic validations here. I am receiving error : TypeError: state is not iterable
After going through few links on stackoverflow , I added - [ ] over state , but it did not helped.
EDIT 1 :
After Adding :- setstate({...state, state.Name: e.target.value}) : Unexpected token, expected "," (18:79)
Instead of having the setState called for each of the inputs you can make use of the name attribute and can refactor the code as below
import React, {useState} from 'react';
function ReactForm() {
const [state, setstate] = useState({
Name: '',
Email: '',
Salary: 0,
});
const handleChange = (e) => {
const {name, value} = e.target;
setstate((prevState) => ({...prevState, [name]: value}));
};
function validationHandler() {
console.log(state);
}
return (
<div>
Name :{' '}
<input
type="text"
value={state.Name}
name="Name"
onChange={handleChange}
/>
<br></br>
Email :{' '}
<input
type="text"
value={state.Email}
name="Email"
onChange={handleChange}
/>
<br></br>
Salary :{' '}
<input
type="text"
value={state.Salary}
name="Salary"
onChange={handleChange}
/>
<br></br>
<button onClick={validationHandler}>Validate Us</button>
</div>
);
}
export default ReactForm;
Refer:
Controlled Component
Your initial state is an array of objects. I'm not sure whether this is what you are looking for.
Assume your iState is (Just an object)
const iState = {
Name: '',
Email: '',
Salary: 0
}
Then you should do something like this in your onChange listener
// setState should use camel case for best pratice BTW
const handleChangeName = e => setstate({
...state,
Name: e.target.value
});
If you are sticking to the array state, the listener should look something like this instead.
const handleChangeName = e => setstate([
...state,
{
...state[0], // or whatever index you may use in the future
Name: e.target.value
}
]);
You can do the following assignment state.Name=e.target.value ****:
You are using an array not an object, so there is nothing you can access using state.Name=e.target.value
So if wanna access it directly the same way you used you have to use state property as OBJECT not as ARRAY:
const iState = {
Name: '',
Email: '',
Salary: 0
}
And the standard for the component that has form to handle is to use stateful component
OR
You can use stateless (functional) component and make form each form field its own state:
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [salary, setSalary] = useState(0);
So the component will be:
import React, { useState } from 'react'
function ReactForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [salary, setSalary] = useState(0)
function validationHandler() {
console.log('Name: ' + name);
console.log('Email: ' + email);
console.log('Salary: ' + salary);
}
return (
<div>
Name : <input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}></input>
<br></br>
Email : <input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}></input>
<br></br>
Salary : <input
type="text"
value={salary}
onChange={(e) => setSalary(e.target.value)}></input>
<br></br>
<button onClick={validationHandler}>Validate Us</button>
</div>
)
}
export default ReactForm;

React Hooks: handle multiple inputs

on react docs forms section there is the following example using class components:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
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;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
Considering Hooks can only be called either in a React function component or a custom React Hook function is there a way of doing it using hooks instead?
you can clean up #adam 's final solution a bit by not using the useCallback hook, and instead simply using the useState hook as a controlled component.
const MyComponent = () => {
const [inputs, setInputs] = useState({});
const handleChange = e => setInputs(prevState => ({ ...prevState, [e.target.name]: e.target.value }));
return (
<>
<input name="field1" value={inputs.field1 || ''} onChange={handleChange} />
<input name="field2" value={inputs.field2 || ''} onChange={handleChange} />
</>
)
}
example
const MyComponent = () => {
const [inputs,setInputs] = useState({});
return (
<>
<input key="field1" name="field1" onChange={({target}) => setInputs(state => ({...state,field1:target.value}))} value={inputs.field1}/>
<input key="field2" name="field2" onChange={({target}) => setInputs(state => ({...state,field2:target.value}))} value={inputs.field2}/>
</>
)
}
you can pass in initial values like this:
const MyComponent = (initialValues = {}) => {
const [inputs,setInputs] = useState(initialValues);
...
}
EDIT: A nice short onChange according to #hamidreza's comment
const MyComponent = (initialValues = {}) => {
const [inputs,setInputs] = useState(initialValues);
const onChangeHandler = useCallback(
({target:{name,value}}) => setInputs(state => ({ ...state, [name]:value }), [])
);
return (
<>
<input key="field1" name="field1" onChange={onChangeHandler} value={inputs.field1}/>
<input key="field2" name="field2" onChange={onChangeHandler} value={inputs.field2}/>
</>
)
}
etc, etc, etc
Maybe, on the last example onChangeForField('...') will be triggered on each render, so maybe you have to write onChange={()=>onChangeForField('...')} or if you want the event to get passed onChange={(e)=>onChangeForField('...', e)}
I was looking for the same answer,but i was finding difficulty to understand the previous solutions,so i tried in my own way ,and i found a solution.
const [inputs,setInputs] = useState({
'field1':'',
'field2':'',
});
const handleChange = (e) => {
const name = e.target.name; //it is the name of that input
const value = e.target.value; //value of that input
setInputs((prev) => {
prev[name] = value;//changing the updated value to the previous state
return prev;
});
};
return (
<>
<input key="field1" name="field1" onChange={handleChange} value={inputs.field1}/>
<input key="field2" name="field2" onChange={handleChange} value={inputs.field2}/>
</>
adding to Adam's answer and for those who are looking towards typescript solution,
interface MyIType {
field1: string;
...
}
//Partial from typescript to make properties optional
interface MyFormType extends Partial<MyIType> {}
const [inputs,setInputs] = useState<MyFormType>(initialValues);
const onChangeForField = useCallback(({target}) =>
setInputs(_state => {
return {
..._state,
[target.name]: target.value,
};
}),
[]
);
If you were like me, having multiple inputs on multiple pages using the same input id/name/key, try value={data.xxx || ''} .
Full code:
const [data, setData] = useState<any>({});
const handleValueChanges = e => {
setData({
...data,
[e.target.name]: e.target.value,
});
};
<InputText (using prime react)
id="firstName"
name="firstName"
value={data.firstName || ''}
onChange={handleUpdate}
/>
As of v6 you can use .forEach(), Please refer to the migrate guide
[{name: "firstName", value: "Safwat" }, {name: "lastName", value: "Fathi", }].forEach(({name, value}) => setValue(name, value));

How to implement multiple checkbox using react hook

I want to implement multiple checkboxes on my HTML page using react-hook.
I tried implementing using this URL: https://medium.com/#Zh0uzi/my-concerns-with-react-hooks-6afda0acc672. In the provided link it is done using class component and working perfectly but whenever I am using React hook setCheckedItems to update checkbox checked status it's not re-rendering the view.
The very first time the view is rendering and console.log() is printing from Checkbox component. After clicking on checkbox function handleChange gets called and checkedItems updates the value but the view is not rendering again (no console.log() printing). And {checkedItems.get("check-box-1")} is also not printing any value.
Below is my sample code.
CheckboxExample :
import React, { useState } from 'react';
import Checkbox from '../helper/Checkbox';
const CheckboxExample = () => {
const [checkedItems, setCheckedItems] = useState(new Map());
const handleChange = (event) => {
setCheckedItems(checkedItems => checkedItems.set(event.target.name, event.target.checked));
console.log("checkedItems: ", checkedItems);
}
const checkboxes = [
{
name: 'check-box-1',
key: 'checkBox1',
label: 'Check Box 1',
},
{
name: 'check-box-2',
key: 'checkBox2',
label: 'Check Box 2',
}
];
return (
<div>
<lable>Checked item name : {checkedItems.get("check-box-1")} </lable> <br/>
{
checkboxes.map(item => (
<label key={item.key}>
{item.name}
<Checkbox name={item.name} checked={checkedItems.get(item.name)} onChange={handleChange} />
</label>
))
}
</div>
);
}
export default Example;
Checkbox:
import React from 'react';
const Checkbox = ({ type = 'checkbox', name, checked = false, onChange }) => {
console.log("Checkbox: ", name, checked);
return (<input type={type} name={name} checked={checked} onChange={onChange} /> )
}
export default Checkbox;
I don't think using a Map to represent the state is the best idea.
I have implemented your example using a plain Object and it works:
https://codesandbox.io/s/react-hooks-usestate-xzvq5
const CheckboxExample = () => {
const [checkedItems, setCheckedItems] = useState({}); //plain object as state
const handleChange = (event) => {
// updating an object instead of a Map
setCheckedItems({...checkedItems, [event.target.name] : event.target.checked });
}
useEffect(() => {
console.log("checkedItems: ", checkedItems);
}, [checkedItems]);
const checkboxes = [
{
name: 'check-box-1',
key: 'checkBox1',
label: 'Check Box 1',
},
{
name: 'check-box-2',
key: 'checkBox2',
label: 'Check Box 2',
}
];
return (
<div>
<lable>Checked item name : {checkedItems["check-box-1"]} </lable> <br/>
{
checkboxes.map(item => (
<label key={item.key}>
{item.name}
<Checkbox name={item.name} checked={checkedItems[item.name]} onChange={handleChange} />
</label>
))
}
</div>
);
}
EDIT:
Turns out a Map can work as the state value, but to trigger a re-render you need to replace the Map with a new one instead of simply mutating it, which is not picked by React, i.e.:
const handleChange = (event) => {
// mutate the current Map
checkedItems.set(event.target.name, event.target.checked)
// update the state by creating a new Map
setCheckedItems(new Map(checkedItems) );
console.log("checkedItems: ", checkedItems);
}
but in this case, I think there is no benefit to using a Map other than maybe cleaner syntax with .get() and .set() instead of x[y].
As an alternative to Map, you might consider using a Set. Then you don't have to worry about initially setting every item to false to mean unchecked. A quick POC:
const [selectedItems, setSelectedItems] = useState(new Set())
function handleCheckboxChange(itemKey: string) {
// first, make a copy of the original set rather than mutating the original
const newSelectedItems = new Set(selectedItems)
if (!newSelectedItems.has(itemKey)) {
newSelectedItems.add(itemKey)
} else {
newSelectedItems.delete(itemKey)
}
setSelectedItems(newSelectedItems)
}
...
<input
type="checkbox"
checked={selectedItems.has(item.key)}
onChange={() => handleCheckboxChange(item.key)}
/>
Seems a bit of a long way round but if you spread the map out and apply it to a new Map your component will re-render. I think using a Object reference instead of a Map would work best here.
const {useState} = React
const Mapper = () => {
const [map, setMap] = useState(new Map());
const addToMap = () => {
const RNDM = Math.random().toFixed(5)
map.set(`foo${RNDM}`, `bar${RNDM}`);
setMap(new Map([...map]));
}
return (
<div>
<ul>
{[...map].map(([v, k]) => (
<li key={k}>
{k} : {v}
</li>
))}
</ul>
<button onClick={addToMap}>add to map</button>
</div>
);
};
const rootElement = document.getElementById("react");
ReactDOM.render(<Mapper />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
As a supplement to using a single object to hold the state of numerous items, the updates will not occur as expected if updating multiple items within a single render. Newer commits within the render cycle will simply overwrite previous commits.
The solution to this is to batch up all the changes in a single object and commit them all at once, like so:
// An object that will hold multiple states
const [myStates, setMyStates] = useState({});
// An object that will batch all the desired updates
const statesUpdates = {};
// Set all the updates...
statesUpdates[state1] = true;
statesUpdates[state2] = false;
// etc...
// Create a new state object and commit it
setMyStates(Object.assign({}, myStates, statesUpdates));
export default function Input(props) {
const {
name,
isChecked,
onChange,
index,
} = props;
return (
<>
<input
className="popup-cookie__input"
id={name}
type="checkbox"
name={name}
checked={isChecked}
onChange={onChange}
data-action={index}
/>
<label htmlFor={name} className="popup-cookie__label">{name}</label>
</>
);
}
const checkboxesList = [
{name: 'essential', isChecked: true},
{name: 'statistics', isChecked: false},
{name: 'advertising', isChecked: false},
];
export default function CheckboxesList() {
const [checkedItems, setCheckedItems] = useState(checkboxesList);
const handleChange = (event) => {
const newCheckboxes = [...checkedItems];
newCheckboxes[event.target.dataset.action].isChecked = event.target.checked;
setCheckedItems(newCheckboxes);
console.log('checkedItems: ', checkedItems);
};
return (
<ul className="popup-cookie-checkbox-list">
{checkboxesList.map((checkbox, index) => (
<li className="popup-cookie-checkbox-list__item" key={checkbox.name}>
<Input
id={checkbox.name}
name={checkbox.name}
isChecked={checkbox.isChecked}
onChange={handleChange}
index={index}
/>
</li>
))}
</ul>
);
}```

Categories

Resources