React Checkbox: Change in child cannot re-render parent node? - javascript

I'm making checkboxes for my project and sadly I spent a day on it but still couldn't solve the problem. It has mother checkbox which controls all children checkboxes and children checkboxes.
What I wanted to make is, if all checkboxes in children get checked, parent node's allChecked checkbox has to be checked.
If any of the checkboxes get unchecked, parent node's allChecked checkbox has to be unchecked.
At first time, I thought maybe it's the problem of props. I give 'allChecked' value from 'cards' to 'card' and 'Do they give only the value as prop?(like true of false)'. But after some experiments and could find they share same variable.
I cannot understand why my code doesn't change parent node's checkbox when children's checkboxes fully checked. As far as I know, react re-render node when property get changed, but after changed, the checkbox in mother does not change.
Cards.js
const Cards = ({ cards }) => {
const numberOfCards = cards.length;
const [allChecked, setAllChecked] = useState(false);
const [checkedList, setCheckedList] = useState(new Set());
const handleAllChecked = () => {
setAllChecked(!allChecked);
}
return (
<>
<>
<>
<input type="checkbox" value={allChecked} onChange={handleAllChecked} />
</>
<>
{ cards.map(el => <Card
id={el.id}
key={el.id}
checkedList={checkedList}
setCheckedList={setCheckedList}
allChecked={allChecked}
setAllChecked={setAllChecked}
numberOfCards={numberOfCards}
/>) }
</>
</>
</>
)
}
Card.js
const Card = ({ id, checkedList, setCheckedList, allChecked, setAllChecked, numberOfCards }) => {
const [checked, setChecked] = useState(false);
const controlCheckedList = () => {
if (!checked) {
checkedList.add(id);
setCheckedList(checkedList);
} else {
checkedList.delete(id);
setCheckedList(checkedList);
}
}
const handleChecked = () => {
setChecked(!checked);
controlCheckedList();
}
useEffect(() => {
if (allChecked) {
setChecked(true);
checkedList.add(id);
setCheckedList(checkedList);
}
else if (!allChecked&&checkedList.size===numberOfCards) {
setChecked(false);
setCheckedList(new Set());
};
}, [allChecked]);
useEffect(()=>{
if (checked) {
if (checkedList.size===numberOfCards){ // here is the part where I think mother's allChecked has to be changed
setAllChecked(true);
}
} else {
if (checkedList.size<numberOfCards){ // here is the part where I think mother's allChecked has to be changed
setAllChecked(false);
}
}
}, [checked]);
return (
<>
<input type="checkbox" checked={checked} onChange={handleChecked} />
</>
)
}
I can feel there would be some easy way to solve it with ContextAPI or something, but really want to solve it with useState and useEffect(I could find many other example).
What I want to know is why when all checkboxes are checked, it doesn't make visible change on mother checkbox even the value of the mother checkbox is changed? Thank you in advance.

You should use checked not value in Cards.js.
<input type="checkbox" checked={allChecked} onChange={handleAllChecked} />

Related

How to get updated state from react parent component

I have the following structure
=Parent Component (with a list as a state)
== Sub Comp list={list})
=== Sub Comp2 list={list}
==== Node list={list}
export const Node = (props) => {
// some state setup
const [checked, setChecked] = useState(false)
const handleCheckbox = () => {
if(!checked){
//Checkbox checked, add this Node to list in Parent Component
props.updateList(someLabel)
}
else{
props.removeFromList(someLabel)
}
}
return(
<TreeItem
icon = {<Checkbox checked={checked} onChange={handleCheckbox}}
>
{expanded && !fetching ? childNodes : <TreeItem label="reached end" />
</TreeItem>
)
}
Now this work the way that I intended, but the problem is since it's a TreeView if I collapse and expand one of the parent nodes, I lose the checked value.
To fix this I put
useEffect(() => {
var inList = props.list.find(function(item) { return item.name === label} ) !=== undefined
if(inList){ setChecked(true)} else{setChecked(false)}
}, [])
Now the above works as intended, but only if I go to the next page on my form and then come back. The problem is that when the list is updated, the useEffect is still using the old state of the list.
How do I force useEffect to use the most updated state, or force the state to update since it's asynchronous?
You have a few options depending on your circumstance. If you just want the latest parent state in the child component you can pass the parent's state as well as the updater as a prop to the child component as below.
Parent:
const ParentComponent = () => {
const [checked, setChecked] = React.useState(false);
return (
<ChildComponent checked={checked} setChecked={setChecked} />
)
}
Child:
const ChildComponent = ({checked, setChecked}) => {
return (
<>
<p>{checked}</p>
<button onClick={() =>setChecked(!checked)}>Button!</button>
</>
)
}
If you are trying to use the state information in parallel components or if you are passing state more than one or two levels down, consider using a context.

Form check box not updating state on uncheck

I have form check box where the values of checked boxed are returned as json, if i uncheck the json is still showing the value of the checked value
onChange function
const setApproveDeclineValues = (e) => {
setChecked(!checked);
setIsChecked({ ...isChecked, [e.target.name]: e.target.value });
console.log(isChecked);
}
Form
<Form.Check
type="checkbox"
id={data.schudule_number}
defaultChecked={checked}
name={data.schudule_number}
value={data.schudule_number}
onChange={setApproveDeclineValues}
/>
useState
const [checked, setChecked] = useState(true);
const [isChecked, setIsChecked] = useState({});
output when checked
{11010: "11010", 11040: "11040"}
expected output if unchecked
{11010: "11010"}
And also how do i update the checked values on useEffect(); ? By defualt all the checkbox are selected, how do i get this values ? i am getting empty json
{}
on page load
I don't think it fully solves it, I mean, works, but it's now scoped for each input. You would need some hight order state to gather this data I guess... But maybe would give some idea.
The pieces of code that looks like setState(s => !s) is, what I think its called, a dispatch function. useState receives the current state as a argument that you can use, rather than access the state itself (would cause a react warning or a infinite loop. I broke my chrome.)
here's a codesandbox with the code:
https://codesandbox.io/s/stack-form-check-box-not-updating-state-on-uncheck-muudh [edited to handle multiples inputs]
ps. I never used delete before, not sure how it works, if its safe, etc.
(and also pasted for posterity)
import { useEffect, useRef, useState } from "react";
import "./styles.css";
function Checkbox({ name, setChecks }) {
const [checked, setChecked] = useState(true);
const inputRef = useRef(null);
useEffect(() => {
if (checked) {
setChecks((c) => ({
...c,
[inputRef.current.name]: inputRef.current.value
}));
} else {
setChecks((c) => {
const newC = { ...c };
delete newC[inputRef.current.name]; // but i think that set the value to false may be better. otherwise the problem that you had here may propagate in database or whatever
return newC;
});
}
}, [checked, setChecks]);
return (
<input
ref={inputRef}
type="checkbox"
defaultChecked={checked} //this could be a prop too
id={name}
name={name}
value={name}
onChange={() => setChecked((c) => !c)}
/>
);
}
export default function App() {
const [checks, setChecks] = useState({});
useEffect(() => {
console.log(checks);
}, [checks]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Checkbox name="one" setChecks={setChecks} />
<Checkbox name="two" setChecks={setChecks} />
<Checkbox name="three" setChecks={setChecks} />
</div>
);
}

React Hooks: State is resetting to empty array even if I use the spread operator, prevState, etc

Simplified Code Sample right here
WORDS:
In short: My items state is resetting to [] with each NEW checkbox clicked and I dont understand why. But instead I want to use the spread operator and useState hooks to push an new item into the array so it's an array of objects.
Current behavior in detail: I'm creating an object and setting it in state using all (and I mean ALL) manner of React useState hooks like this: setItems((prevState) => [...prevState, { [evt.target.value]: evt.target.checked }]); As I check one item it's added and items becomes an array of objects (it being added over and over again is not the problem; I'll add a check for that later). BUT Here's the problem: when I click a NEW checkbox the items array is set back to [] and isnt concatenated with the prev items—even though I'm using prevState, spread operator, an arrow func as a wrapper, and all that jazz.
Desired behavior: Every time I check a checkbox, I want to update items [] to push a new object into it, which represents all items that have ever been checked. Before you say anything about duplicating: I'll add the check to see if an item is already in the array, and just update it if so. And before I add all items to cart, I'll strip all objects with checked = false states.
Can you help me understand what react lifecycle fundamentals I'm missing here; why is this happening? And how can I fix it?
CODE:
Where this is happening:
Simplified version of InputComponent
const InputComponent = ({ type, itemId, handleSearchQuery, onSubmit }) => {
const [items, setItems] = useState([]);
const captureInput = (evt) => {
if (evt.target.type === 'checkbox') {
setItems((prevState) => [...prevState, { [evt.target.value]: evt.target.checked }]);
}
};
const renderCheckbox = () => {
return (
<form>
<input type={type} name={itemId} value={itemId} onChange={setItem} />
<input name={itemId} type='submit' value='Add to Cart' />
</form>
);
};
return (
<div className='input-bar'>
{renderCheckbox()}
</div>
);
};
export default InputComponent;
Where this component is used:
import React from 'react';
import InputComponent from './InputComponent';
import './ResultsRenderer.css';
function ResultsRenderer({ data }) {
const renderListings = () => {
let listings = data ? data.Search : null;
return listings
? listings.map((item) => {
return (
<div className='cart-row'>
<InputComponent type='checkbox' className='cart-checkbox' itemId={item.imdbID} />
<div key={item.imdbID} className={item.imdbID}>
<img src={`${item.Poster}`} alt={item.Title} />
<div>
Title<em>{item.Title}</em>
</div>
<div>{item.Year}</div>
<em>{item.imdbID}</em>
</div>
</div>
);
})
: null;
};
return <>{renderListings()}</>;
}
export default ResultsRenderer;
items state is doing its job perfectly fine, you misunderstood the situation.
you're using items state inside InputComponent and for each listings item there is one InputComponent and each one have their own items, I think you meant to use items state inside ResultsRenderer Component to chase all selected items.
here is the changes you need to do:
const InputComponent = ({ type, itemId, setItems }) => {
const captureInput = (evt) => {
if (evt.target.type === "checkbox") {
setItems((prevState) => [
...prevState,
{ [evt.target.value]: evt.target.checked }
]);
}
};
return (
<div className="input-bar">
<form>
<input
type={type}
name={itemId}
value={itemId}
onChange={captureInput}
/>
<input name={itemId} type="submit" value="Add to Cart" />
</form>
</div>
);
};
export default InputComponent;
function ResultsRenderer() {
const [items, setItems] = useState([]);
useEffect(() => {
console.log(items);
}, [items]);
const renderListings = () => {
let listings = [
{ itemId: 1, title: "Hello" },
{ itemId: 2, title: "World" }
];
return listings
? listings.map((item) => {
return (
<div className="cart-row">
<InputComponent
type="checkbox"
className="cart-checkbox"
itemId={item.itemId}
setItems={setItems}
/>
<div key={item.itemId} className={item.itemId}>
<div>
Title<em>{item.Title}</em>
</div>
</div>
</div>
);
})
: null;
};
return <>{renderListings()}</>;
}
and here is the working demo: https://codesandbox.io/s/boring-cookies-t0g4e?file=/src/InputComponent.jsx

How to control defaultChecked in input tag at react?

How to control initial defaultChecked in input tag at react?
I am korean wanna be front-end engineer. I am not goot at using English.
I have a question at react.
I make the survey page and there are some options to choose one. And then I want to make modify function at survey page. So, I can render another input tags value but I can not render options value that will be defaultChecked value.
I set up like this defaultChecked={checkedValue}
and const [checkedValue, setCheckedValue] = useState(false)
and then I can receive value from database. Here is from below,
value is options value before surveied data from database.
SO I want to say
when i click edit button, i want to appear option value from received database data and then i want to control defaultChecked value is true or false.
Please, help me.
thank you for reading this article that is not good English.
const EditSurveyForm = ({ data, handleClick, name }) => {
const getToken = window.sessionStorage.getItem("token");
const [value, getValue] = useState("");
const [checkValue1, getCheckValue1] = useState(false);
const [checkValue2, getCheckValue2] = useState(false);
useEffect(() => {
const ac = new AbortController();
axios
.get(`http://localhost:5000/content/${contentId}`, {
headers: { "x-access-token": getToken },
})
.then(res => {
if (res.data.Content) {
getValue(res.data.Content[name]);
handleOption();
}
});
}, [value]);
const handleOption = () => {
if (value === 1) {
getCheckValue1(true);
}
if (value === 2) {
getCheckValue2(true);
}
};
return (
<>
<Container>
<div className={data.name}>{data.data}</div>
<SurveyContainer>
<span>
약1
<div>
<input
defaultChecked={checkValue1}
type="radio"
name={`question${data.id}`}
value="1"
onClick={e => handleClick(data.name, e.target.value)}
className="option1"
/>
</div>
</span>
<span>
<br></br>
<div>
<input
defaultChecked={checkValue2}
type="radio"
name={`question${data.id}`}
value="2"
onClick={e => handleClick(data.name, e.target.value)}
className="option2"
/>
</div>
</span>
</SurveyContainer>
</Container>
</>
);
};
export default EditSurveyForm;
Please, help me. Thank you.

How to make my checkbox to be clicked only once and other are unchecked in React.js

I followed a tutorial and created a form with as many checkboxes to be clicked. But, in another case, I need only one box to be checked. The values of checkboxes are dynamic and you never know, how many checkboxes will be created. But, only one can be clicked. Can you please help me in finding the solution thankyou.
import React, { Component } from 'react';
import Checkbox from "./Checkbox.component";
class PatientSelectTiming extends Component {
state = {
options: [...this.props.props],
checkboxes: [...this.props.props].reduce(
(options, option) => ({
...options,
[option]: false
}),
{}
),
appointmentSlots: null
};
handleCheckboxChange = e => {
const { name } = e.target;
this.setState(prevState => ({
checkboxes: {
...prevState.checkboxes,
[name]: !prevState.checkboxes[name]
}
}))
}
handleFormSubmit = formSubmitEvent => {
formSubmitEvent.preventDefault();
Object.keys(this.state.checkboxes)
.filter(checkbox => this.state.checkboxes[checkbox])
.forEach(checkbox => {
let appointmentSlot = [];
appointmentSlot.push(checkbox);
console.log(appointmentSlot);
this.setState({appointmentSlots: appointmentSlot})
localStorage.setItem('appointmentSlots', JSON.stringify(appointmentSlot))
});
};
createCheckbox = option => (
<Checkbox
label={option}
isSelected={this.state.checkboxes[option]}
onCheckboxChange={this.handleCheckboxChange}
key={option}
/>
);
createCheckboxes = () => this.state.options.map(this.createCheckbox);
render() {
return (
<div>
<p>Only select one item and only first date clicked will be your time</p>
<form onSubmit={this.handleFormSubmit}>
{this.createCheckboxes()}
<button type="submit">
Save
</button>
</form>
{this.state.appointmentSlots === null ? <p>Click on any slot to get your time.</p> : <p>Your time is {JSON.parse(localStorage.getItem("appointmentSlots"))}</p>}
</div>
)
}
}
export default PatientSelectTiming;
You can use a radio button
https://www.w3schools.com/tags/att_input_type_radio.asp
Radio button is the same as checkbox but only allows users to check only 1 option.

Categories

Resources