How to change checkbox in react correctly? [closed] - javascript

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
What is the correct way to change checkbox value?
option 1
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [x, setX] = useState(false);
const soldCheckbox = ({ target: { checked } }) => {
console.log(x, checked);
setX(checked);
};
return (
<div>
<input type="checkbox" checked={x} onChange={soldCheckbox} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
option 2
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [x, setX] = useState(false);
console.log(x);
return (
<div>
<input type="checkbox" checked={x} onChange={() => setX(!x)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In fact, I think there's no difference, but I just wanted to hear a different opinion. Maybe something I do not know or there may be other solutions to this problem.

Both ways are almost the same, but the first option actually is more redundant, let's analyze why:
Both the first and second methods are implementing controlled components
This means that you are providing a value and a way to change it, so the responsibility to update and control the values are abstracted from the component.
But why the first way is redundant?
You don't actually need to read from the e.target.checked cause it always reflects the local state x, so there is no need to read from e.target.checked and reverse it by doing: setX(!e.target.checked) since x and the e.target.checked will always be the same.
Caveats
Even though is fine to do onClick={e => parentHandler(e)} in a inline expression(arrow function) you should be careful, passing it like this to an input won't cause any problems, but when you are passing to a child component that implements React.memo or PureComponent for example, this will actually re render the component everytime, cause a new instance of the function is always created (signature is the same, but the shallow comparison will always return false cause they are different instances), so for optimization reasons is always best pass props like this: <Child onClick={this.clickHandler} id={item.id} /> and on the child: onClick={() => this.props.onClick(this.props.id)} instead of just: <Child onClick={e => this.onClick(item.id)} />

In this specific case, I would have chosen option 2, cleaner code in my opinion.
setX changes the state, no need for a function calling setX and extracting the value from event if we know the value is x.

I think that it all depends on the situation.
The first option will be better if you have a lot of form and components. You can handle all with one handler.
const handler = (e) => {
const { target } = e;
const value = target.type === 'checkbox' ? target.checked : target.value;
const { name } = target;
setForm( f => ({ ...f, [name]: value }));
};
Second, if checkbox is one and the application must somehow react to its change.
there is a third way to uncontrolled inputs.

The only difference is clean coding, first way is better if you need to do something except changing state (for example to call an http request) and the second is good if you just need checkbox to work and store its value.

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [x, setX] = useState(false);
const soldCheckbox = ({ target: { checked } }) => {
console.log(x, checked);
setX(checked);
};
return (
<div>
<input type="checkbox" checked={x} onChange={soldCheckbox} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

I'm always choosing option 1 because it is a way more generic way for defining form field change events. In most of the cases, I have something in generic form components
function SomeForm() {
const [formData, setFormData] = useState({ name: "", accept: false });
const onFieldChange = ({ target: { name, value, checked } }) => {
if (typeof checked !== 'undefined') { // checking if we have a "checked" field inside target
setFormData({ [name]: checked });
}
setFormData({ [name]: value });
}
return (
<form>
<input type="text" name="name" value={formData.name} onChange={onFieldChange} />
<input type="checkbox" name="accept" checked={formData.accept} onChange={onFieldChange} />
</form>
)
}
The idea behind this is that any way we are going to receive a target DOM object which contains both checked and value, so we can make it generic.
Hope this helps

It looks like both of your options are equivalent. If we look at the documentation for the onChange event provided by React (not the change event provided by html) it states:
The onChange event behaves as you would expect it to: whenever a form field is changed, this event is fired.
We intentionally do not use the existing browser behavior because onChange is a misnomer for its behavior and React relies on this event to handle user input in real time.
DOM Elements
So, simply choose the option that you think produces cleaner code.

Related

How to render the next component in React?

I have three components First, Second and Third that need to render one after the other.
My App looks like this at the moment:
function App() {
return (
<First/>
)
}
So ideally, there's a form inside First that on submission (onSubmit probably) triggers rendering the Second component, essentially getting replaced in the DOM. The Second after some logic triggers rendering the Third component and also passes a value down to it. I'm not sure how to go on about it.
I tried using the useState hook to set a boolean state to render one of the first two components but I would need to render First, then somehow from within it change the set state in the parent which then checks the boolean and renders the second. Not sure how to do that. Something like below?
function App() {
const { isReady, setIsReady } = useState(false);
return (
isReady
? <First/> //inside this I need the state to change on form submit and propagate back up to the parent which checks the state value and renders the second?
: <Second/>
);
}
I'm mostly sure this isn't the right way to do it.
Also need to figure out how to pass the value onto another component at the time of rendering it and getting replaced in the DOM. So how does one render multiple components one after the other on interaction inside each? A button click for example?
Would greatly appreciate some guidance for this.
then somehow from within it change the set state in the parent which then checks the boolean and renders the second.
You're actually on the right track.
In React, when you're talking about UI changes, you have to manage some state.
So we got that part out of the way.
Now, what we can do in this case is manage said state in the parent component and pass functions to the children components as props in-order to allow them to control the relevant UI changes.
Example:
function App() {
const { state, setState } = useState({
isFirstVisible: true,
isSecondVisible: false,
isThirdVisible: false,
});
const onToggleSecondComponent = (status) => {
setState(prevState => ({
...prevState,
isSecondVisible: status
}))
}
const onToggleThirdComponent = (status) => {
setState(prevState => ({
...prevState,
isThirdVisible: status
}))
}
return (
{state.isFirstVisible && <First onToggleSecondComponent={onToggleSecondComponent} /> }
{state.isSecondVisible && <Second onToggleThirdComponent={onToggleThirdComponent} /> }
{state.isThirdVisible && <Third/> }
);
}
Then you can use the props in the child components.
Example usage:
function First({ onToggleSecondComponent }) {
return (
<form onSubmit={onToggleSecondComponent}
...
</form
)
}
Note that there are other ways to pass these arguments.
For example, you can have one function in the parent comp that handles them all, or you can just pass setState to the children and have them do the logic.
Either way, that's a solid way of achieving your desired outcome.
Seen as your saying there are stages, rather than having a state for each stage, just have a state for the current stage, you can then just increment the stage state to move onto the next form.
Below is a simple example, I've also used a useRef to handle parent / child state, basically just pass the state to the children and the children can update the state. On the final submit I'm just JSON.stringify the state for debug..
const FormContext = React.createContext();
const useForm = () => React.useContext(FormContext);
function FormStage1({state}) {
const [name, setName] = React.useState('');
state.name = name;
return <div>
Stage1:<br/>
name: <input value={name} onChange={e => setName(e.target.value)}/>
</div>
}
function FormStage2({state}) {
const [address, setAddress] = React.useState('');
state.address = address;
return <div>
Stage2:<br/>
address: <input value={address} onChange={e => setAddress(e.target.value)}/>
</div>
}
function FormStage3({state}) {
const [hobbies, setHobbies] = React.useState('');
state.hobbies = hobbies;
return <div>
Stage3:<br/>
hobbies: <input value={hobbies} onChange={e => setHobbies(e.target.value)}/>
</div>
}
function Form() {
const [stage, setStage] = React.useState(1);
const state = React.useRef({}).current;
let Stage;
if (stage === 1) Stage = FormStage1
else if (stage === 2) Stage = FormStage2
else if (stage === 3) Stage = FormStage3
else Stage = null;
return <form onSubmit={e => {
e.preventDefault();
setStage(s => s + 1);
}}>
{Stage
? <React.Fragment>
<Stage state={state}/>
<div>
<button>Submit</button>
</div>
</React.Fragment>
: <div>
{JSON.stringify(state)}
</div>
}
</form>
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Form/>);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>

Is there any way to trigger React.useEffect from different component?

Imagine two components like this in React:
import MyComponent2 from "./components/MyComponent2";
import React from "react";
export default function App() {
const [myState, setMyState] = React.useState([]);
React.useEffect(() => {
console.log("useEffect triggered");
}, [myState]);
return <MyComponent2 myState={myState} setMyState={setMyState} />;
}
import React from "react";
export default function MyComponent2(props) {
const [inputValue, setInputValue] = React.useState("");
function handleChange(e) {
setInputValue(e.target.value);
let list = props.myState;
list.push(`${e.target.value}`);
props.setMyState(list);
console.log(props.myState);
}
return (
<div>
<input
type="text"
value={inputValue}
name="text"
onChange={handleChange}
/>
</div>
);
}
As you can see I am making changes with props.setMyState line in second component. State is changing but Somehow I could not trigger React.useEffect in first component even tough It is connected with [myState]. Why ?
In short form of my question : I can not get "useEffect triggered" on my console when i make changes in input
Instead of providing myState and setMyState to MyComponent2, you should only provide setMyState and use the functional update argument in order to access the current state.
In your handleChange function you are currently mutating the React state (modifying it directly):
let list = props.myState; // This is an array that is state managed by React
list.push(`${e.target.value}`); // Here, you mutate it by appending a new element
props.setMyState(list);
// ^ You update the state with the same array here,
// and since they have the same object identity (they are the same array),
// no update occurs in the parent component
Instead, you should set the state to a new array (whose object identity differs from the current array):
props.setMyState(list => {
const newList = [...list];
newList.push(e.target.value);
return newList;
});
// A concise way to write the above is like this:
// props.setMyState(list => [...list, e.target.value]);

How to prevent re-rendering of components that have not changed?

I have a component that consists of several other components such as text fields, and when an input is made to the text field, all other components are re-rendered.
I would like to prevent the re-rendering and only re-render the component that actually changes.
I have seen that useCallback is the right way to do this and I have already seen how to use it. However, I'm having some trouble getting useCallBack to work correctly for my case.
Even if I set it up in a simple manner as below, each new character entered into the text field causes the button to be rendered again.
I don't see my mistake.
See working example in sandbox.
const Button = () => {
console.log("Button Rendered!");
window.alert("Button Rendered");
return <button onClick="">Press me</button>;
};
export default function App() {
const [textInput, setTextInput] = useState("Hallo");
const onChangeInput = useCallback(
(e) => {
setTextInput(e.target.value);
},
[textInput]
);
return (
<div>
<input
type="text"
onChange={onChangeInput}
value={textInput}
/>
<Button />
</div>
);
}
I am happy for any calrification.
Personally I would avoid React.memo / React.useRef / React.useCallback.
The simplest solution to your example is just create another component, and store the state with this.
eg.
const Button = () => {
console.log("Button Rendered!");
window.alert("Button Rendered");
return <button onClick="">Press me</button>;
};
const TextInput = () => {
const [textInput, setTextInput] = useState("Hallo");
const onChangeInput = useCallback(
(e) => {
setTextInput(e.target.value);
},
[textInput]
);
return (
<input
type="text"
onChange={onChangeInput}
value={textInput}
/>
);
}
export default function App() {
return (
<div>
<TextInput/>
<Button />
</div>
);
}
In the above if you change Text, there is not State change in App, so Button doesn't get re-rendered, no need for useMemo etc..
You will find React works really well, the more you divide your components up, not only does it solve issues of re-render, but potentially makes it a lot easier to re-use components later.
IOW: keep state as close to the component as possible, and performance will follow.
Of course your example is simple, and in a real app you will have HOC's etc to cope with, but that's another question.. :)
useCallback does not prevent rerenders. React.memo is what prevents renders. It does a shallow comparison of the previous props with the new props, and if they're the same, it skips rendering:
const Button = React.memo(() => {
console.log("Button Rendered!");
window.alert("Button Rendered");
return <button onClick="">Press me</button>;
});
The only role that useCallback plays in this is that sometimes you want to pass a function as a prop to a memoized component. For the memoization to work, props need to not change, and useCallback can help the props to not change.
changing the state causes re-render component along with all his heirs, to prevent re-render some sections, you can use useMemo to prevent unwanted re-rendering...
NOTE: useMemo has some costs... so don't overuse it (In this small example, it is not recommended at all).
in this case, if you do not need to re-rendering, you can use the useRef to save the input reference to get that value whenever you need it.
e.g:
const BlahBlah = () => {
const inputRef = React.useRef(undefined);
return (
<div>
<input ref={inputRef} />
<button onClick={() => console.log(inputRef.current.value)}
</div>
);
};
I had similar problem. I wanted to avoid rendering a component which dependent on part2 of state, when only part1 has changed.
I used shouldComponentUpdate(nextProps, nextState) as described here https://reactjs.org/docs/optimizing-performance.html to avoid rendering it.
I'm using it this way:
const [state_data, set_state_data] = useState([
true, // loading.
{}, // vehicle.
{} // user.
]);
This allows rendering only 1x instead of 3x and React keeps track of the changes. For some reason it won't track the change of objects, but arrays yes.

Mat ui FormControlLabel not using correct state

I am using FormControlLabel for my custom checkboxes but they don't seem to be checked/unchecked based on the state being passed to them.
The onchange handler updates everything in redux/BE but on initial render or after reload they are all unchecked again.
I have tried so many methods that I have lost track and ended up in a bit of a spiral.
I have also tried controlling the checkbox using both value and checked.
when the checkboxes are rendered via the .map they have the correct boolean value being received from the state but the checkbox doesn't reflect that.
Any ideas where I might be going wrong?
I've put the file in a gist here: https://gist.github.com/SerdarMustafa1/052bffd6958858eb5770991f59d1e187
styled checkbox is :
export const CustomCheckbox = withStyles({
root: {
color: `${COLORS.MAIN_ORANGE}`,
"&$checked": {
color: `${COLORS.MAIN_ORANGE}`
}
},
checked: {}
})(props => <Checkbox color="default" {...props} />);
and the redux action/reducer:
export const SET_IS_PIN_CODE_CHECK_IN_CHECKED =
"venueTemplates/SET_IS_PIN_CODE_CHECK_IN_CHECKED";
export const setIsPinCodeCheckInChecked = createAction(
SET_IS_PIN_CODE_CHECK_IN_CHECKED
);
export const handlers = {
[SET_IS_PIN_CODE_CHECK_IN_CHECKED]: (state, { payload: value }) => ({
...state,
isPinCodeCheckInChecked: value
}),
Been going around in circles for ~10hrs, any ideas or help appreciated.
Maybe not the best solution but what worked for me was to use defaultChecked
example:
defaultChecked={item?.isPinCodeCheckInChecked}
You need to use checked (boolean) instead of value (string):
<CustomCheckbox
id={id || ""}
onChange={event => handlePinCodeChecked(event)}
checked={!!isPinCodeCheckInChecked[id]}
name={name || ""}
/>

Use dynamically created react components and fill with state values

Below is a proof of concept pen. I'm trying to show a lot of input fields and try to collect their inputs when they change in one big object. As you can see, the input's won't change their value, which is what I expect, since they're created once with the useEffect() and filled that in that instance.
I think that the only way to solve this is to use React.cloneElement when values change and inject the new value into a cloned element. This is why I created 2000 elements in this pen, it would be a major performance hog because every element is rerendered when the state changes. I tried to use React.memo to only make the inputs with the changed value rerender, but I think cloneElement simply rerenders it anyways, which sounds like it should since it's cloned.
How can I achieve a performant update for a single field in this setup?
https://codepen.io/10uur/pen/LYPrZdg
Edit: a working pen with the cloneElement solution that I mentioned before, the noticeable performance problems and that all inputs rerender.
https://codepen.io/10uur/pen/OJLEJqM
Here is one way to achieve the desired behavior :
https://codesandbox.io/s/elastic-glade-73ivx
Some tips :
I would not recommend putting React elements in the state, prefer putting plain data (array, objects, ...) in the state that will be mapped to React elements in the return/render method.
Don't forget to use a key prop when rendering an array of elements
Use React.memo to avoid re-rendering components when the props are the same
Use React.useCallback to memoize callback (this will help when using React.memo on children)
Use the functional form of the state setter to access the old state and update it (this also helps when using React.useCallback and avoid recreating the callback when the state change)
Here is the complete code :
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const INPUTS_COUNT = 2000;
const getInitialState = () => {
const state = [];
for (var i = 0; i < INPUTS_COUNT; i++) {
// Only put plain data in the state
state.push({
value: Math.random(),
id: "valueContainer" + i
});
}
return state;
};
const Root = () => {
const [state, setState] = React.useState([]);
useEffect(() => {
setState(getInitialState());
}, []);
// Use React.useCallback to memoize the onChangeValue callback, notice the empty array as second parameter
const onChangeValue = React.useCallback((id, value) => {
// Use the functional form of the state setter, to update the old state
// if we don't use the functional form, we will be forced to put [state] in the second parameter of React.useCallback
// in that case React.useCallback will not be very useful, because it will recreate the callback whenever the state changes
setState(state => {
return state.map(item => {
if (item.id === id) {
return { ...item, value };
}
return item;
});
});
}, []);
return (
<>
{state.map(({ id, value }) => {
// Use a key for performance boost
return (
<ValueContainer
id={id}
key={id}
onChangeValue={onChangeValue}
value={value}
/>
);
})}
</>
);
};
// Use React.memo to avoid re-rendering the component when the props are the same
const ValueContainer = React.memo(({ id, onChangeValue, value }) => {
const onChange = e => {
onChangeValue(id, e.target.value);
};
return (
<>
<br />
Rerendered: {Math.random()}
<br />
<input type="text" value={value} onChange={onChange} />
<br />
</>
);
});
ReactDOM.render(<Root />, document.getElementById("root"));

Categories

Resources