Updating context on one child resetting all the states - javascript

I have multiple components with checkboxes. This function is called on onChange on checkboxes.
const { selectedDocuments, setSelectedDocuments } = useContext(Context);
const [isSelected, setIsSelected] = useState(false);
const onSelect: React.ChangeEventHandler<HTMLInputElement> | undefined = (
e
) => {
if (isSelected) {
setSelectedDocuments((docs) =>
docs.filter((doc) => doc.data.id !== card.data.id)
);
setIsSelected(false);
} else {
setSelectedDocuments((docs) => [...docs, card]);
setIsSelected(true);
}
};
I am trying to update the context and flip the check state. But every time context is updating the state of component isSelected is getting reset (on very other children which is using the same context).
I am not sure why all the states are getting reset.

I think you have multiple components which are instances of a single component.
for example, you could have a <Checkboxes/> component and then you are simply reusing it everywhere.
If you are doing this then I think you will run into trouble with Context API.
You can define separate and unique state variables and then pass these state variables as props to each component to solve this issue.
For example say you have state variables selectedOne, selectedTwo,selectedThree ....
then you can do something like this
<Checkboxes selected = {selectedOne} />
<Checkboxes selected = {selectedTwo} />
<Checkboxes selected = {selectedThree} />

Related

React use effect causing infinite loop

Am new to react and am creating a custom select component where its supposed to set an array selected state and also trigger an onchange event and pass it to the parent with the selected items and also get initial value as prop and set some data.
let firstTime = true;
const CustomSelect = (props)=>{
const [selected, setSelected] = useState([]);
const onSelectedHandler = (event)=>{
// remove if already included in the selected items remove
//otherwise add
setSelected((prev)=>{
if (selected.includes(value)) {
values = prevState.filter(item => item !== event.target.value);
}else {
values = [...prevState, event.target.value];
}
return values;
})
// tried calling props.onSelection(selected) but its not latest value
}
//watch when the value of selected is updated and pass onchange to parent
//with the newest value
useEffect(()=>{
if(!firstTime && props.onSelection){
props.onSelection(selected);
}
firstTime = false;
},[selected])
return (<select onChange={onSelectedHandler}>
<option value="1"></option>
</select>);
};
Am using it on a parent like
const ParentComponent = ()=>{
const onSelectionHandler = (val)=>{
//do stuff with the value passed
}
return (
<CustomSelect initialValue={[1,2]} onSelection={onSelectionHandler} />
);
}
export default ParentComponent
The above works well but now the issue comes in when i want to set the initialValue passed from the parent onto the customSelect by updating the selected state. I have added the followin on the CustomSelect, but it causes an infinite loop
const {initialValue} = props
useEffect(()=>{
//check if the value of initialValue is an array
//and other checks
setSelected(initialValue)
},[initialValue]);
I understand that i could have passed the initialValue in the useState but i would like to do a couple of checks before setting the selected state.
How can i resolve this, am still new to react.
In your //do stuff with the value passed you are most likely update the states of your component parent component and it causes to rerender parent component. When passing the prop initialValue={[1,2]} creates a new instance of [1,2] array on each render and causes the infinite render on useEffect. In order to solve this, you can move the initialValue prop to somewhere else as const value like this:
const INITIAL_VALUE_PROP = [1,2];
const ParentComponent = ()=>{
const onSelectionHandler = (val)=>{
//do stuff with the value passed
}
return (
<CustomSelect initialValue={INITIAL_VALUE_PROP} onSelection={onSelectionHandler}
/>
);
}
export default ParentComponent
Okay, so the reason this is happening is that you're passing the initialValue prop as an array, and since this is a reference value in JavaScript, it means that each time it's passed(updated), it's passed with a different reference value/address, and so the effect will continue to re-run infinitely. One way to solve this is to use React.useMemo, documentation here to store/preserve the reference value of the array passed, not to cause unnecessary side effects running.

React dispatch state not updating in the same method

I have a dispatch updating the state correctly when i was in this scenario with 2 methods ( onClick )
const sizesMeasureChoise = useSelector(getSizesMeasureChoise);
const chooseMeasure = (measure) => {
dispatch(setSizeMeasureChoise(measure));
};
const chooseType = (type) => {
const sizeChoise = sizesMeasureChoise.description;
}
Then i have changed and i have put everything in one method ( because i have removed the chooseType step ) and the state is not updated after the dispatch
const sizesMeasureChoise = useSelector(getSizesMeasureChoise);
const chooseMeasure = (measure) => {
dispatch(setSizeMeasureChoise(measure));
const sizeChoise = sizesMeasureChoise.description;
}
From what is depending, how can i resolve it?
Once an action has been dispatched, updated values from the store will become available only on the next render.
This behavior is the same for both functional components with hooks as well as with classes that use the connect HOC.
You'll just have to modify your code so as not to expect changes immediately.
If you could describe what you want to get (rather than "how") with a minimal sample with only the relevant hooks & how data is rendered then it will be
easier to suggest a solution.

Prop passed to child component does not change its value in child upon updating prop in parent

I have two functional components called Crucials and Timer, where Crucials is the parent and Timer is the Child.
Crucials has the user time data that needs to be passed down to the Timer for it to start the timer from the time provided by user.
My parent component is as follows:
export default function Crucials() {
const [Hdata, setHData] = useState(0);
const [Mdata, setMData] = useState(0);
let data = {
Hour: 0,
Min: 0,
Sec: 0
};
function timerStart(){
if(Hdata === 0 && Mdata === 0){
console.log("Minimum Timer Initiated");
setHData((minH)=> minH = 0);
setMData((minM)=> minM = 15);
}
data.Hour = 0;
data.Min = Mdata;
data.Secy = Hdata;
}
return (<div>
<div><Timer timerdata={data}/></div>
<Button variant="contained" color="primary" onClick={timerStart}> Start Timer</Button>
<form className={styles.HTM} onSubmit={timerStart}>
<Input type="number" onChange={e => setHData(e.target.value) } id="outlined-basic" placeholder="Hours(H)" ></Input>
<Input type="number" id="outlined-basic" onChange={e => setMData(e.target.value)} placeholder="Minutes(M)" ></Input>
</form>
</div>
)}
My child is as follows:
export default function Timer(timerdata) {
let Seconds = timerdata.Sec;
let Minutes = timerdata.Min;
let Hours = timerdata.Hour;
....
...
}
All I want to do is for me to be able to pass the data object to the child and for it to only update when timerStart is invoked via the Start Timer button.
However, what is happening is that the data object passed to the child only ever holds the initialization values (0,0,0) and invoking timerStart function, which changes the values within data, is not reflected in the Child.
Also, for some odd reason, just inputting values into the two <Input> fields causes child to update data, but it still holds the initial values rather than what the user input.
I'm probably doing something wrong, but I'm unable to figure it out. Any help is appreciated.
You need to make data a state value, currently:
your updates to your object won't persist between re-renders. Using setHData/setMData to update your state causes your component function to be called/executed again, redefining local variables such as the data object to the initial values
updating your data alone using data.Hour = 0; etc. won't cause your component/children components to re-render with the updated values. As a result, you need to use react's state setter function, setX to signal to react that your data object has changed and that your component needs to re-render using the new state values.
Instead, create a new state value that holds your data object, and use setData() to update it. This will cause your update to re-render your component + its children:
const {useState} = React;
const App = () => {
const [data, setData] = useState({hour:1,min:2});
const clickHandler = () => {
setData({hour: 10, min: 20}); // use input values instead of hard-coding values
};
return <div>
<Child data={data} />
<button onClick={clickHandler}>Update data</button>
</div>
}
const Child = (props) => <div>
<p>H: {props.data.hour}</p>
<p>M: {props.data.min}</p>
</div>;
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
Side note:
Your code setHData((minH)=> minH = 0); can be written as setHData(0);, and same with setMData(15). To update the state value, all you need to do is pass the new value to the state setter function. You don't need to pass an arrow function here, as your new value (0, and 15) doesn't rely on the previous state value.

React Hooks - using useState vs just variables

React Hooks give us useState option, and I always see Hooks vs Class-State comparisons. But what about Hooks and some regular variables?
For example,
function Foo() {
let a = 0;
a = 1;
return <div>{a}</div>;
}
I didn't use Hooks, and it will give me the same results as:
function Foo() {
const [a, setA] = useState(0);
if (a != 1) setA(1); // to avoid infinite-loop
return <div>{a}</div>;
}
So what is the diffrence? Using Hooks even more complex for that case...So why start using it?
The reason is if you useState it re-renders the view. Variables by themselves only change bits in memory and the state of your app can get out of sync with the view.
Compare this examples:
function Foo() {
const [a, setA] = useState(0);
return <div onClick={() => setA(a + 1)}>{a}</div>;
}
function Foo() {
let a = 0;
return <div onClick={() => a = a + 1}>{a}</div>;
}
In both cases a changes on click but only when using useState the view correctly shows a's current value.
Local variables will get reset every render upon mutation whereas state will update:
function App() {
let a = 0; // reset to 0 on render/re-render
const [b, setB] = useState(0);
return (
<div className="App">
<div>
{a}
<button onClick={() => a++}>local variable a++</button>
</div>
<div>
{b}
<button onClick={() => setB(prevB => prevB + 1)}>
state variable b++
</button>
</div>
</div>
);
}
function Foo() {
const [a, setA] = useState(0);
if (a != 1) setA(1); // to avoid infinite-loop
return <div>{a}</div>;
}
is equivalent to
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
a: 0
};
}
// ...
}
What useState returns are two things:
new state variable
setter for that variable
if you call setA(1) you would call this.setState({ a: 1 }) and trigger a re-render.
Your first example only works because the data essentially never changes. The enter point of using setState is to rerender your entire component when the state hanges. So if your example required some sort of state change or management you will quickly realize change values will be necessary and to do update the view with the variable value, you will need the state and rerendering.
Updating state will make the component to re-render again, but local values are not.
In your case, you rendered that value in your component.
That means, when the value is changed, the component should be re-rendered to show the updated value.
So it will be better to use useState than normal local value.
function Foo() {
let a = 0;
a = 1; // there will be no re-render.
return <div>{a}</div>;
}
function Foo() {
const [a, setA] = useState(0);
if (a != 1) setA(1); // re-render required
return <div>{a}</div>;
}
It is perfectly acceptable to use standard variables. One thing I don't see mentioned in other answers is that if those variables use state-variables, their value will seemingly update on a re-render event.
Consider:
import {useState} from 'react';
function NameForm() {
// State-managed Variables
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// State-derived Variables
const fullName = `${firstName} ${lastName}`;
return (
<input value={firstName} onChange={e => setFirstName(e.target.value)} />
<input value={lastName} onChange={e => setLastName(e.target.value)} />
{fullName}
);
}
/*
Description:
This component displays three items:
- (2) inputs for _firstName_ and _lastName_
- (1) string of their concatenated values (i.e. _lastName_)
If either input is changed, the string is also changed.
*/
Updating firstName or lastName sets the state and causes a re-render. As part of that process fullName is assigned the new value of firstName or lastName. There is no reason to place fullName in a state variable.
In this case it is considered poor design to have a setFullName state-setter because updating the firstName or lastName would cause a re-render and then updating fullName would cause another re-render with no perceived change of value.
In other cases, where the view is not dependent on the variable, it is encouraged to use local variables; for instance when formatting props values or looping; regardless if whether the value is displayed.

How does React.useState triggers re-render?

import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In the above example whenever setCount(count + 1) is invoked a re-render happens. I am curious to learn the flow.
I tried looking into the source code. I could not find any reference of useState or other hooks at github.com/facebook/react.
I installed react#next via npm i react#next and found the following at node_modules/react/cjs/react.development.js
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
On tracing back for dispatcher.useState(), I could only find the following ...
function resolveDispatcher() {
var dispatcher = ReactCurrentOwner.currentDispatcher;
!(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component.') : void 0;
return dispatcher;
}
var ReactCurrentOwner = {
/**
* #internal
* #type {ReactComponent}
*/
current: null,
currentDispatcher: null
};
I wonder where can I find dispatcher.useState() implementation and learn how it triggers re-render when setState setCount is invoked.
Any pointer would be helpful.
Thanks!
The key in understanding this is the following paragraph from the Hooks FAQ
How does React associate Hook calls with components?
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
(This also explains the Rules of Hooks. Hooks need to be called unconditionally in the same order, otherwise the association of memory cell and hook is messed up.)
Let's walk through your counter example, and see what happens. For simplicity I will refer to the compiled development React source code and React DOM source code, both version 16.13.1.
The example starts when the component mounts and useState() (defined on line 1581) is called for the first time.
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
As you have noticed, this calls resolveDispatcher() (defined on line 1546). The dispatcher refers internally to the component that's currently being rendered. Within a component you can (if you dare to get fired), have a look at the dispatcher, e.g. via
console.log(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current)
If you apply this in case of the counter example, you will notice that the dispatcher.useState() refers to the react-dom code. When the component is first mounted, useState refers to the one defined on line 15986 which calls mountState(). Upon re-rendering, the dispatcher has changed and the function useState() on line 16077 is triggered, which calls updateState(). Both methods, mountState() on line 15352 and updateState() on line 15371, return the count, setCount pair.
Tracing ReactCurrentDispatcher gets quite messy. However, the fact of its existence is already enough to understand how the re-rendering happens. The magic happens behind the scene. As the FAQ states, React keeps track of the currently rendered component. This means, useState() knows which component it is attached to, how to find the state information and how to trigger the re-rendering.
setState is a method on the Component/PureComponent class, so it will do whatever is implemented in the Component class (including calling the render method).
setState offloads the state update to enqueueSetState so the fact that it's bound to this is really only a consequence of using classes and extending from Component. Once, you realize that the state update isn't actually being handled by the component itself and the this is just a convenient way to access the state update functionality, then useState not being explicitly bound to your component makes much more sense.
I also tried to understand the logic behind useState in a very simplified and basic manner, if we just look into its basic functionalities, excluding optimizations and async behavior, then we found that it is basically doing 4 things in common,
maintaining of State, primary work to do
re-rendering of the component through which it get called so that caller component can get the latest value for state
as it caused the re-rendering of the caller component it means it must maintain the instance or context of that component too, which also allows us to use useState for multiple component at once.
as we are free to use as many useState as we want inside our component that means it must maintain some identity for each useState inside the same component.
keeping these things in mind I come up with the below snippet
const Demo = (function React() {
let workInProgress = false;
let context = null;
const internalRendering = (callingContext) => {
context = callingContext;
context();
};
const intialRender = (component) => {
context = component;
workInProgress = true;
context.state = [];
context.TotalcallerId = -1; // to store the count of total number of useState within a component
context.count = -1; // counter to keep track of useStates within component
internalRendering(context);
workInProgress = false;
context.TotalcallerId = context.count;
context = null;
};
const useState = (initState) => {
if (!context) throw new Error("Can only be called inside function");
// resetting the count so that it can maintain the order of useState being called
context.count =
context.count === context.TotalcallerId ? -1 : context.count;
let callId = ++context.count;
// will only initialize the value of setState on initial render
const setState =
!workInProgress ||
(() => {
const instanceCallerId = callId;
const memoizedContext = context;
return (updatedState) => {
memoizedContext.state[instanceCallerId].value = updatedState;
internalRendering(memoizedContext);
};
})();
context.state[callId] = context.state[callId] || {
value: initState,
setValue: setState,
};
return [context.state[callId].value, context.state[callId].setValue];
};
return { useState, intialRender };
})();
const { useState, intialRender } = Demo;
const Component = () => {
const [count, setCount] = useState(1);
const [greeting, setGreeting] = useState("hello");
const changeCount = () => setCount(100);
const changeGreeting = () => setGreeting("hi");
setTimeout(() => {
changeCount();
changeGreeting();
}, 5000);
return console.log(`count ${count} name ${greeting}`);
};
const anotherComponent = () => {
const [count, setCount] = useState(50);
const [value, setValue] = useState("World");
const changeCount = () => setCount(500);
const changeValue = () => setValue("React");
setTimeout(() => {
changeCount();
changeValue();
}, 10000);
return console.log(`count ${count} name ${value}`);
};
intialRender(Component);
intialRender(anotherComponent);
here useState and initialRender are taken from Demo. intialRender is use to call the components initially, it will initialize the context first and then on that context set the state as an empty array (there are multiple useState on each component so we need array to maintain it) and also we need counter to make count for each useState, and TotalCounter to store total number of useState being called for each component.
FunctionComponent is different. In the past, they are pure, simple. But now they have their own state.
It's easy to forget that react use createElement wrap all the JSX node, also includes FunctionComponent.
function FunctionComponent(){
return <div>123</div>;
}
const a=<FunctionComponent/>
//after babel transform
function FunctionComponent() {
return React.createElement("div", null, "123");
}
var a = React.createElement(FunctionComponent, null);
The FunctionComponent was passed to react. When setState is called, it's easy to re-render;

Categories

Resources