Form check box not updating state on uncheck - javascript

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>
);
}

Related

Adding data to array using UseState onChange

I am having some issues figuring out how I can get the state of an inputfield, and add it to an useState array.
The way this code is set up, using onChange, it will add every character I type in the textField as a new part of the array, but I dont want to set the value until the user is done typing.
What would be a simple solution to this?
My code:
const [subject, setSubject] = useState([]);`
<input type="text" placeholder={"Eks. 'some example'"} onChange={(e) => setSubject(oldArray => [...oldArray, e.target.value])}/>
Well, I am not confident with react yet, but unless you don't want to do some validation, why don't you use useRef hook and onBlur combination. UseRef hook basically set a reference on element and then you can use value from that reference which in your case would be textField value. OnBlur will trigger when user clicks outside of input (input lose focus) Code should look like this:
import react, {useRef, useState} from "react";
const someComponent = (props) => {
const [subject, setSubject] = useState([]);
const textAreaRef = useRef();
const onBlurHandler = () => {
setSubject((prevSubject) => [...prevSubject, textAreaRef.current.value]);
}
return <input type="text" placeholder={"Eks. 'some example'"} ref={textAreaRef} onBlur={onBlurHandler}/>
}
Other way would be to use debouncing with useEffet.
this is a little something i cooked up for you... it watches the change of the input, and 1 second after the person stops typing, it will add the input value.
The main things to look at here are the useEffect() and the <input /> with the new state i made [input, setInput]. or you can play around with this here
export default function App() {
const [subjects,setSubjects] = useState([]);
const [input,setInput] = useState("")
useEffect(() => {
const timer = setTimeout(() => {
setSubjects(old => [...old, input])
}, 1000)
return () => clearTimeout(timer)
}, [input])
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<input placeholder="type here"
value={input}
type="text"
onChange={e => setInput(e.target.value)}
/>
{subjects.length === 0 ?
<h3>Nothing yet...</h3>
:
<h3>{subjects}</h3>
}
</div>
);
}

Putting redux state into local state with useEffect() doesn't change the local state completeley

I know that setState is asynchronous, but I have a weird problem with it.
My shortened code:
// My imports including my own component for an input switch (Switchbox)
function EditDoc(props) {
const [name, setName] = useState("");
const [privateDoc, setPrivateDoc] = useState(true);
const {
editDoc,
createDoc,
data: { docs },
} = props;
useEffect(() => {
let id = props.match.params.docId;
if (id in docs) {
setPrivateDoc(docs[id].privateDoc);
setName(docs[id].name);
}
}, []);
function handleSubmit(event) {
if (id in docs) {
editDoc(name, privateDoc);
} else {
createDoc(name, privateDoc);
}
}
return (
<React.Fragment>
<form onSubmit={handleSubmit}>
<label>Name</label>
<input
autoFocus="autoFocus"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Switchbox
title="Public document"
checked={!privateDoc}
function={() => setPrivateDoc(!privateDoc)}
/>
<button
type="reset"
onClick={props.history.goBack}
/>
<button
type="submit"
/>
</div>
</form>
</React.Fragment>
);
}
CreateLearningUnit.propTypes = {
editDoc: PropTypes.func.isRequired,
createDoc: PropTypes.func.isRequired,
data: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
data: state.data,
});
const mapActionsToProps = {
editDoc,
createDoc,
};
export default connect(mapStateToProps, mapActionsToProps)(EditDoc);
The Switchbox is always showing true after the first rendering, which is set in const [private, setPrivate] = useState(true);. My Switchbox element is not the problem, it works fine in other cases, and in this case it also works fine after toggling the switch once, but it seems like useState() is overwriting the things set in useEffect().
Now the weird thing: With name for example, everything works great, name gets set and shows up as the value in the input. SetPrivate() in useEffect is doing something too, but private is only displayed correctly after changing it one time. And yes, I know, that I use the negative of private, it shows true in both cases: docs[id].private true and false.
Could you please help me with this problem? 😃
I already tried using local variables and using useRef() with a constant but that doesn't work either.
Edit: I changed private to privateDoc, in my original code it is a longer name 😁

React prevent child update

I have a simple for with some fields in it, the fields being child components to that form. Each field validates its own value, and if it changes it should report back to the parent, which causes the field to re-render and lose focus. I want a behavior in which the child components do not update. Here's my code:
Parent (form):
function Form() {
const [validFields, setValidFields] = useState({});
const validateField = (field, isValid) => {
setValidFields(prevValidFields => ({ ...prevValidFields, [field]: isValid }))
}
const handleSubmit = (event) => {
event.preventDefault();
//will do something if all fields are valid
return false;
}
return (
<div>
<Title />
<StyledForm onSubmit={handleSubmit}>
<InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />
<Button type="submit" content="Enviar" maxWidth="none" />
</StyledForm>
</div>
);
}
export default Form;
Child (field):
function InputField(props) {
const [isValid, setValid] = useState(true);
const [content, setContent] = useState("");
const StyledInput = isValid ? Input : ErrorInput;
const validate = (event) => {
setContent(event.target.value);
setValid(stringValidator.validateField(event.target.value, props.isValidCondition))
props.reportState(props.name, isValid);
}
return (
<Field>
<Label htmlFor={props.name}>{props.name + ":"}</Label>
<StyledInput
key={"form-input-field"}
value={content}
name={props.name}
onChange={validate}>
</StyledInput>
</Field>
);
}
export default InputField;
By setting a key for my child element I was able to prevent it to lose focus when content changed. I guess I want to implement the shouldComponentUpdate as stated in React documentation, and I tried to implement it by doing the following:
Attempt 1: surround child with React.memo
const InputField = React.memo((props) {
//didn't change component content
})
export { InputField };
Attempt 2: intanciate child with useMemo on parent
const fooField = useMemo(<InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />, [fooRegex]);
return (
<div>
<Title />
<StyledForm onSubmit={handleSubmit}>
{fooField}
<Button type="submit" content="Enviar" maxWidth="none" />
</StyledForm>
</div>
);
Both didn't work. How can I make it so that when the child component isValid state changes, it doesn't re-render?
The problem is not that the component is re-rendering, it is that the component is unmounting given by this line:
const StyledInput = isValid ? Input : ErrorInput;
When react unmounts a component, react-dom will destroy the subtree for that component which is why the input is losing focus.
The correct fix is to always render the same component. What that means to you is based on how your code is structured, but I would hazard a guess that the code would end up looking a bit more like this:
function InputField(props) {
const [isValid, setValid] = useState(true);
const [content, setContent] = useState("");
const validate = (event) => {
setContent(event.target.value);
setValid(stringValidator.validateField(event.target.value, props.isValidCondition))
props.reportState(props.name, isValid);
}
return (
<Field>
<Label htmlFor={props.name}>{props.name + ":"}</Label>
<Input
valid={isValid} <-- always render an Input, and do the work of displaying error messages/styling based on the `valid` prop passed to it
value={content}
name={props.name}
onChange={validate}>
</Input>
</Field>
);
}
The canonical solution to avoiding rerendering with function components is React.useMemo:
const InputField = React.memo(function (props) {
// as above
})
However, because validateField is one of the props passed to the child component, you need to make sure it doesn't change between parent renders. Use useCallback to do that:
const validateField = useCallback((field, isValid) => {
setValidFields(prevValidFields => ({ ...prevValidFields, [field]: isValid }))
}, []);
Your useMemo solution should also work, but you need to wrap the computation in a function (see the documentation):
const fooField = useMemo(() => <InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />, [fooRegex]);

Can I track multiple checkboxes with React Hooks or do I need to use a Class component?

I'm learning React Hooks and trying to understand how I can manage multiple checkboxes with state. If I only have one checkbox, my code below works - using check as state - but when I have multiple boxes, this doesn't work because onChange = {() => setCheck(!check)} will change the check state for ALL of the boxes at once.
I think it's doable if I use a React class component (something like this.handleCheckboxChange to only change state for the particular checkbox) but I'm trying to see if it's possible to do this with hooks.
import React, { useState } from 'react';
const Search = ({ options }) => {
const [check, setCheck] = useState(false);
const renderedOptions = options.map((option) => {
return (
<div key={option}>
<label>
<input
checked={check ? 'checked' : ''}
type="checkbox"
name={option}
value={option}
onChange={() => {
setCheck(!check);
}}></input>
{option}
</label>
</div>
);
});
return (
<form>
<label>Search Engines (check all that apply)</label>
{renderedOptions}
</form>
);
};
export default Search;
You can use an object as the state, with each propname as the name of the checkbox and can hold the checked information as value.
const [values, setValues] = useState({});
handleChange = (e) => setValues({ ...values, [e.target.name]: e.target.checked });
You can bind the options as initial or default values to the state. And <input value={values[options.name]

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

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} />

Categories

Resources