React validation of dynamic checkboxes - reactjs

I have 7 checkboxes that are rendered within a map method.. each checkbox has a question and when all checkboxes are checked, a button should be activated.. can someone please tell me how can I validate this using useState() hook.. I know how to do this with one checkbox, but I don't know how to handle multiple checkboxes that are rendered within a map method. A code example should be very helpful. Any help will be appreciated.
const [isChecked, setIsChecked] = useState(false);
{ questions.map(q => (
<input
type={q.radio ? "radio" : "checkbox"}
onClick={() => setIsChecked(!isChecked)}
value={isChecked}
id={option.id}
value={option.value}
name={`${q.name}`}
/>
There are 7 checkboxes rendered with this map method. How should I handle the state in this case?

If your questions are dynamic list you can use below approach:
const QUESTIONS = ["Do you use Stackoverflow?", "Do you asked a question?"];
function Confirmation({ data }) {
const [questions, setQuestions] = React.useState(
data.map((question, index) => {
return { id: index, text: question, checked: false };
})
);
const handleClick = (id, checked) => {
const newQuestions = [...questions];
const index = newQuestions.findIndex((q) => q.id === id);
newQuestions[index].checked = checked;
setQuestions(newQuestions);
};
const isButtonDisabled = questions.some((q) => q.checked === false);
return (
<div>
{questions.map((question) => (
<React.Fragment>
<input
key={question.id}
name={`question-${question.id}`}
type="checkbox"
checked={question.checked}
onClick={(e) => handleClick(question.id, e.target.checked)}
/>
<label htmlFor={`question-${question.id}`}>{question.text}</label>
</React.Fragment>
))}
<button disabled={isButtonDisabled}>Confirm</button>
</div>
);
}
ReactDOM.render(<Confirmation data={QUESTIONS} />, document.getElementById("root"));
<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="root"></div>

To add to thedude's answer, you can dynamically adjust the checked array based on changes to your questions array so that you don't have to manually maintain the initial states or remember to sync it to the new length of questions if it changes.
const [checked, setChecked] = useState([])
const arrLength = questions.length;
useEffect(() => {
setChecked(() => (
Array(arrLength).fill().map((_, i) => checked[i] || false)
));
}, [arrLength]);

Following your example of mapping over your questions.
You could load those questions in useState and append the check value to them.
Click the Run Code Snippet below to see it working.
const { useState, useEffect } = React;
const App = props => {
const { questions } = props;
const [state, setState] = useState(null);
const onChangeCheckbox = index => event => {
const newState = [...state];
newState[index].checked = !newState[index].checked;
setState(newState);
}
useEffect(() => {
setState(questions.map(i => ({...i, checked: false }) ));
}, [questions]);
// Not loaded yet
if (!state) return <div></div>;
return <div>
{state && state.length > 0 && <div>
{state.map((i, k) => <p key={`question-${k}`}>
<label><input onChange={onChangeCheckbox(k)} type="checkbox" name={i.name} checked={i.checked} /> {i.question}</label>
</p>)}
</div>}
<hr />
<p><small>Debug</small></p>
<pre><code>{JSON.stringify(state, null, ' ')}</code></pre>
</div>;
};
const questions = [
{
name: 'cyborg',
question: 'Are you a cyborg?'
},
{
name: 'earth',
question: 'Do you live on earth?'
},
{
name: 'alien',
question: 'Are you from another planet?'
}
];
ReactDOM.render(<App questions={questions} />, document.querySelector('#root'));
body {
font-family: Arial, sans-serif;
}
pre {
background: #efefef;
padding: 20px;
}
<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="root"></div>

Use the key param to give each checkbox an identifier so it can be modified.
const [isChecked, setIsChecked] = useState([])
useEffect(() => {
compareList()
})
{ questions.map((q,i) => (
<input
type={q.radio ? "radio" : "checkbox"}
onClick={() => setIsChecked(!isChecked[i])}
value={isChecked[i]}
id={option.id}
value={option.value}
name={`${q.name}`}
key=i
/>

If you use controlled input the solution might be like this:
const initialState = [
{
name: "Check option 1",
checked: false
},
{
name: "Check option 2",
checked: false
},
]
const App = () => {
const [checkboxes, setCheckboxes] = useState(initialState)
const [quizFinished, setQuizFinished] = useState(false)
// the useEffect checks if all questions are completed
useEffect(()=>{
setQuizFinished(checkboxes.reduce((acc, cbx) => acc && cbx.checked, true))
}, [checkboxes])
function handleChange(e) {
setCheckboxes(
checkboxes.map(cbx => cbx.name === e.target.name ? {...cbx, checked:!cbx.checked} : cbx)
)
}
return (
<>
{checkboxes.map(cbx => <Checkbox name={cbx.name} checked={cbx.checked} onChange={handleChange}/>)}
{quizFinished && <button>Finish</button>}
</>
)
}
const Checkbox = ({name, checked, onChange}) => {
return (
<>
<label>
{name}
<input type="checkbox" name={name} checked={checked} onChange={onChange} />
</label>
<br/>
</>
)
}

You can model your state to fit your usecase. For a list of 7 checkboxes you can have an array with 7 values:
const [checked, setChecked] = useState([false, false, false, false, false, false, false])
The when a check box is clicked:
{ questions.map((q, i) => (
<input
type={q.radio ? "radio" : "checkbox"}
onClick={() => setChecked(current => {
const newState = [...current]
newState[i] = !newState[i]
return newState
} )}
value={checked[i]}
id={option.id}
value={option.value}
name={`${q.name}`}
/>
To know if all are checked you can defined a variable:
const areAllChecked = checked.every(Boolean)

Related

React.js working code breaks on re-render

I have a weird bug, where my code works on first attempt, but breaks on page re-render.
I've created a filter function using an object with filter names and array of filter values:
const filterOptions = {
'size': ['s', 'm', 'l'],
'color': ['black', 'white', 'pink', 'beige'],
'fit': ['relaxed fit','slim fit', 'skinny fit', 'oversize'],
'pattern': ['patterned', 'spotted', 'solid color'],
'material': ['wool', 'cotton', 'leather', 'denim', 'satin']
}
The idea was to create a separate object with all the values and corresponding 'checked' attribute and than use it to check if checkbox is checked:
const [checkedValue, setCheckedValue] = useState({})
useEffect(() => {
const filterValuesArray = Object.values(filterOptions).flat()
filterValuesArray.map(filter => setCheckedValue(currentState => ({...currentState, [filter]: { ...currentState[filter], checked: false }})))}, [])
FilterValue here is array of values from FilterOptions:
<div className='popper'>
{filterValue.map(value => {
return (
<div key={`${value}`} className='popper-item'>
<label className='popper-label'>{value}</label>
<input onChange={handleCheckbox} checked={checkedValue[value].checked} type='checkbox' value={value} className="popper-checkbox" />
</div>
)}
)}
</div>
There is onChange function as wel, which could be a part of problem:
const handleCheckbox = (event) => {
const value = event.target.value;
setCheckedValue({...checkedValue, [value]: { ...checkedValue[value], checked: !checkedValue[value].checked }})
if(activeFilters.includes(value)) {
const deleteFromArray = activeFilters.filter(item => item !== value)
setActiveFilters(deleteFromArray)
} else {
setActiveFilters([...activeFilters, value])
}}
I've tried keeping filterOptions in parent component and in Context, but it gives exactly the same result. It always work as planned on first render, and on next render it shows this error, until you delete the checked attribute of input. I've noticed that on re-render the 'checkedValue' object returns as empty, but I can't find out why. Would be really helpful if somebody could explain me a reason.
Uncaught TypeError: Cannot read properties of undefined (reading 'checked')
Edit: full code looks like this:
Parent Component
const Filter = () => {
return (
<div className='filter'>
<div className="price-filter">
<p>Price: </p>
<Slider onChange={handleSliderChange} value={[min, max]} valueLabelDisplay="on" disableSwap style={{width:"70%"}} min={0} max={250} />
</div>
<Divider />
<ul className='filter-list'>
{Object.entries(filterOptions).map((filter, i) => {
return (
<Fragment key={`${filter[0]}${i}`}>
<FilterOption className='filter-option' filterName={filter[0]} filterValue={filter[1]} />
<Divider key={`${i}${Math.random()}`} />
</Fragment>
)
})}
</ul>
</div>
)
}
Child Component
const FilterOption = ({ filterName, filterValue }) => {
const { checkedValue, setCheckedValue, activeFilters, setActiveFilters, filterOptions } = useContext(FilterContext)
useEffect(() => {
const filterValuesArray = Object.values(filterOptions).flat()
filterValuesArray.map(filter => setCheckedValue(currentState => ({...currentState, [filter]: { ...currentState[filter], checked: false }})))
}, [])
const handleCheckbox = (event) => {
const value = event.target.value;
setCheckedValue({...checkedValue, [value]: { ...checkedValue[value], checked: !checkedValue[value].checked }})
if(activeFilters.includes(value)) {
const deleteFromArray = activeFilters.filter(item => item !== value)
setActiveFilters(deleteFromArray)
} else {
setActiveFilters([...activeFilters, value])
}
}
return (
<div className='popper' key={filterName}>
{filterValue.map(value => {
return (
<div key={`${value}`} className='popper-item'>
<label className='popper-label'>{value}</label>
<input onChange={handleCheckbox} checked={checkedValue[value].checked} type='checkbox' value={value} className="popper-checkbox" />
</div>
)}
)}
</div>
)

useState updates state on second click

I've seen that this issue has been asked many times before but none of the answers make sense to me. After trying about every solution i could find, decided i'd just ask it myself.
Below is my code, the main issue being that in the DOM filteredData only changes on second click. Note, projs is the prop containing data fetched that gets filtered out and displayed
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, []);
const handleCategory = (res) => {
setCategory(res.data);
const filter = projectData.filter((data) => data.category === category);
setFilteredData(filter);
};
button:
{langs.map((data, index) => (
<button
key={index}
class="px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
I'm pretty lost at the moment, any help would be greatly appreciated
You can't use the newly set value of a state in the same render cycle. To continue calling both setState calls in the same handler you need to use the category from res.data to generate the new filtered array and then set both.
const handleCategory = (res) => {
const filter = projectData.filter((data) => data.category === res.data);
setCategory(res.data);
setFilteredData(filter);
};
const { useState, useEffect } = React;
function App({ projs }) {
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
const handleCategory = (res) => {
const filter = projectData.filter((data) => data.category === res.data);
setCategory(res.data);
setFilteredData(filter);
};
return (
<div className="App">
{langs.map((data) => (
<button
key={data} // avoid using index as key as this will lead to erros in updating.
class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
<div>
<h3>{category}</h3>
{filteredData.length
? filteredData.map(p => (
<div>
<p>{p.value} – {p.category}</p>
</div>
))
: <p>No projects match</p>}
</div>
</div>
);
}
const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
ReactDOM.render(
<App projs={projectData} />,
document.getElementById("root")
);
.selected {
background-color: tomato;
}
<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="root"></div>
Alternatively, update filteredData in a useEffect that watches for changes to either category or projectData
useEffect(() => {
setFilteredData(projectData.filter((data) => data.category === category));
}, [category, projectData]);
const handleCategory = (res) => {
setCategory(res.data);
};
const { useState, useEffect } = React;
function App({ projs }) {
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
useEffect(() => {
setFilteredData(projectData.filter((data) => data.category === category));
}, [category, projectData]);
const handleCategory = (res) => {
setCategory(res.data);
};
return (
<div className="App">
{langs.map((data) => (
<button
key={data}
class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
<div>
<h3>{category}</h3>
{filteredData.length
? filteredData.map(p => (
<div>
<p>{p.value} – {p.category}</p>
</div>
))
: <p>No projects match</p>}
</div>
</div>
);
}
const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
ReactDOM.render(
<App projs={projectData} />,
document.getElementById("root")
);
.selected {
background-color: tomato;
}
<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="root"></div>
As noted in the snippets –
Setting state from props is a bit of anti-pattern, but if you must you should add the prop to the dependency array so that state will be updated should the prop change. (prop changes don't force remounting).
Also, avoid using index as key, especially when mapping arrays whose order/membership will change as React will not update as expected.
I think this is the issue
setCategory(res.data);
const filter = projectData.filter((data) => data.category === category);
You invoke setCategory. But that won't change the immediate value of category that you reference in the subsequent line.
I think you want this:
const updatedCategory = res.data;
setCategory(updatedCategory);
const filter = projectData.filter((data) => data.category === updatedCategory);
Also, is res.data actually the "category" thing you want to use? I'm just asking because you use data between so many variables, parameters, and object members, I can't help but to wonder if it's really a typo.

prevent the user from being able to add names

I need to prevent the user from being able to add names that already exist in the person list but I'm not sure where should I add it and also what method is better to use .includes or indexOf? I want issue a warning with the alert command when such an action is attempted. Any help would be much appropriated!
import React, { useState } from 'react'
const App = () => {
const [ persons, setPersons ] = useState([ { name: 'Arto Hellas' }])
const [ newName, setNewName ] = useState('')
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
}
setPersons([...persons,nameObject])
}
const handleNameChange = (event) => {
setNewName(event.target.value)
}
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addName} >
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
{persons.map(person => (
<p key={person.name}>{person.name}</p>
))}
</div>
)
}
export default App
You need to do something in the following place:
const addName = (event) => {
event.preventDefault();
const nameObject = {
name: newName
};
setPersons([...persons, nameObject]);
};
Use .find() on the persons array to find the particular name already existing and add the condition before setPersons is executed.
if (persons.find(p => p.name === newName)) {
window.alert("Name already exists!");
return false;
}
A code like above will work.
import React, { useState } from "react";
const App = () => {
const [persons, setPersons] = useState([{ name: "Arto Hellas" }]);
const [newName, setNewName] = useState("");
const addName = (event) => {
event.preventDefault();
if (persons.find((p) => p.name === newName)) {
window.alert("Name already exists!");
return false;
}
const nameObject = {
name: newName
};
setPersons([...persons, nameObject]);
};
const handleNameChange = (event) => {
setNewName(event.target.value);
};
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addName}>
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
{persons.map((person) => (
<p key={person.name}>{person.name}</p>
))}
</div>
);
};
export default App;
Demo: https://codesandbox.io/s/nervous-poitras-i3stw?file=/src/App.js:0-958
The above code shows an ugly Error Alert using the normal window alert. If you want a better error like this:
You can use the following code, by setting a state:
import React, { useState } from "react";
import "./styles.css";
const App = () => {
const [persons, setPersons] = useState([{ name: "Arto Hellas" }]);
const [newName, setNewName] = useState("");
const [error, setError] = useState(false);
const addName = (event) => {
event.preventDefault();
if (persons.find((p) => p.name === newName)) {
setError(true);
return false;
}
const nameObject = {
name: newName
};
setPersons([...persons, nameObject]);
};
const handleNameChange = (event) => {
setNewName(event.target.value);
};
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addName}>
{error && <p className="error">User already exists.</p>}
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
{persons.map((person) => (
<p key={person.name}>{person.name}</p>
))}
</div>
);
};
export default App;
And here's our style.css:
.error {
background-color: red;
color: #fff;
padding: 5px;
}
Demo: https://codesandbox.io/s/vibrant-tree-wsou2?file=/src/App.js
First add field to check name exists or not:
const nameExists = React.useMemo(() => {
return persons.some(item => item.name === newName);
}, [newName, persons])
Then disable button and show message if name exists:
<div>
{nameExists && <p>Name {newName} already exists!</p>}
<button type="submit" disabled={nameExists} >add</button>
</div>
Also, make sure you clear name when you add new name:
const addName = (event) => {
...
setNewName('')
}
const App = () => {
const [ persons, setPersons ] = React.useState([ { name: 'Arto Hellas' }])
const [ newName, setNewName ] = React.useState('')
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
}
setPersons([...persons,nameObject]);
setNewName('')
}
const handleNameChange = (event) => {
setNewName(event.target.value)
}
const nameExists = React.useMemo(() => {
return persons.some(item => item.name === newName);
}, [newName, persons])
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addName} >
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
{nameExists && <p>Name {newName} already exists!</p>}
<button type="submit" disabled={nameExists} >add</button>
</div>
</form>
<h2>Numbers</h2>
{persons.map(person => (
<p key={person.name}>{person.name}</p>
))}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
You can use the find method to search for a person with the newName. Please check the below code-snippet:
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName,
}
let personAlreadyExists = persons.find(person => person.name === newName);
if (personAlreadyExists) {
alert('Person with that name already exists');
// any other operations (like clearing user input in the form, etc..)
}
else {
setPersons([...persons, nameObject])
}
}
Code-Sandbox

How to uncheck selected radio input onChange with react to reset current showing data?

I have created multiple Radio buttons options. For each radio button based on the value date, I would display different users based on availability.
Now, when I select one or the second radio button it will display different users.
But for some reason, the "CHECKED" value will stay whenever I switch to another radio input option. It will display different data but I would like to reset these mapped (currentConsult) values to display none.
Code:
import React, { useEffect, useState } from 'react';
// import ConsultSelect from './ConsultSelect';
const Appointment = (props) => {
const { loading, data, consultSelect } = props;
const [consult, setConsult] = useState([]);
const [filteredConsult, setFilteredConsult] = useState([]);
const [currentConsult, setCurrentConsult] = useState([]);
const [checked, setChecked] = useState({
selected: '',
});
useEffect(() => {
setConsult(data);
if (consultSelect === 'consult_business') {
setFilteredConsult(consult.consult_business);
}
if (consultSelect === 'consult_strategy') {
setFilteredConsult(consult.consult_strategy);
}
if (consultSelect === '') {
setFilteredConsult(...consult);
}
});
const ShowUsers = (selectedDate) => {
const result = filteredConsult.filter(
(v) =>
v.consult__availlability &&
v.consult__users &&
v.consult__date === selectedDate
);
setCurrentConsult(result[0].consult__users);
};
const onSelectChange = (event) => {
setChecked({ selected: event.target.value });
const selectedDate = event.target.value;
consult && checked && ShowUsers(selectedDate);
};
return (
<div className="consult__meetinplanner form-group">
{loading !== true && <p>loading...</p>}
{loading && consultSelect === '' && (
<h3 style={{ marginTop: '2rem', marginBottom: '2rem' }}>
Maak een keuze aub.
</h3>
)}
{filteredConsult &&
filteredConsult.map((value, i) => {
const {
consult__date: date,
consult__time: time,
consult__availlability: is_available,
} = value;
return (
<div key={i} className="consult__results">
<label htmlFor={date}>
{is_available && (
<div>
<input
type="radio"
id={date}
name={date}
value={date}
checked=""
onChange={onSelectChange}
disabled={props.disabled}
required
/>
<span>{date}</span>
—
<span>{time}</span>
</div>
)}
</label>
</div>
);
})}
{consultSelect !== '' &&
currentConsult.map((user, i) => {
return (
<article key={user.consultation_user_item.nickname}>
<h4>{user.consultation_user_item.nickname}</h4>
</article>
);
})}
</div>
);
};
export default Appointment;
Goednmorgen Nino.
You are not binding checked prop to anything, here is an example how you can do this:
function App() {
const [checked, setChecked] = React.useState({
selected: 'one',
});
const onSelectChange = (event) => {
setChecked({ selected: event.target.value });
// const selectedDate = event.target.value;
// consult && checked && ShowUsers(selectedDate);
};
return (
<div>
{['one', 'two', 'three'].map((value, index) => (
<label key={index}>
{value}
<input
type="checkbox"
checked={checked.selected === value} //bind checked prop
value={value}
onChange={onSelectChange}
/>
</label>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<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="root"></div>

react hook how to handle mutiple checkbox

const shoopingList = [{name:'some thing', id:1},{name:'some string', id:4}]
const CurrentLists = ({ shoppingList }) => {
const arr = [...shoppingList]
arr.map((item, index) => {
item.isChecked = false
})
const [checkedItems, setCheckeditems] = useState(arr)
const handleOnChange = (e) => {
const index = e.target.name
const val = e.target.checked
checkedItems[index].isChecked = e.target.checked
setCheckeditems([...checkedItems])
}
return (
<div>
{checkedItems.map((item, index) => {
console.log('item check', item.isChecked)
return (
<CheckBox
key={index}
name={index}
checked={item.isChecked}
text={item.name}
onChange={handleOnChange}
/>
)
})}
</div>
)
}
const CheckBox = ({ checked, onChange, text, className = '', name }) => {
let css = classnames({
activebox: checked,
})
return (
<div className={'CheckBoxComponent ' + className}>
<div className={'checkbox ' + css}>
<input
name={name}
type="checkbox"
onChange={onChange}
/>
{checked && <i className="far fa-check signcheck" />}
</div>
<label>{text}</label>
</div>
)
}
I got some checkboxes. when I click the checkbox, my component doesn't re-render. What's wrong here? I might be using the hook setState wrong.
On every re-render you are basically setting isChecked property to false. Try updating your component like this:
const CurrentLists = ({ shoppingList }) => {
const [checkedItems, setCheckeditems] = useState(shoppingList)
const handleOnChange = useCallback(
(e) => {
const index = e.target.name
let items = [...checkedItems];
items[index].isChecked = e.target.checked;
setCheckeditems(items);
}, [checkedItems]
);
return (
<div>
{checkedItems.map((item, index) => {
console.log('item check', item.isChecked)
return (
<CheckBox
key={index}
name={index}
checked={item.isChecked}
text={item.name}
onChange={handleOnChange}
/>
)
})}
</div>
)
}
You may also notice usage of useCallback. It ensures that your callback is memoized and not created on every re-render - more about it.
In handleOnChange you are mutating the state directly, and because the state reference is not changed React does not re-render. To fix this change the line setCheckeditems(checkedItems) to setCheckeditems([...checkedItems]).
Also in your render, you are rendering shoppingList, but what you need to render is checkedItems

Resources