Why the function useState does not update the value? - javascript

I am trying on React.js but what I would like does not work. Here is my code :
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
setCount(count + 1);
return (
<div>
<p>value of count : {count} fois</p>
</div>
);
}
export default App;
I got :
Too many re-renders. React limits the number of renders to prevent an infinite loop.
You can see the error there : https://codesandbox.io/s/nostalgic-engelbart-tzbxv?file=/src/App.js:0-244
Thank you for your help !

You're creating an infinite loop. Every time state updates, the component is re-rendered. The component is rendering, then it's calling setCount the state updates, then the component renders, and on and on.
You can use useEffect to update the state after it's rendered the first time.
useEffect(() => {
setCount(count + 1)
},[])
I'm not sure what the point of this would be though, since you can just pass the initial value into useState

You are calling setCount in the body of the component, which is essentially the render function if thinking in a class based component. Therefore it is causing an infinite loop of setting a new value, rerender, setting a new value, rerender, and so on.
You should only be calling setCount from some sort of event, e.g. a button click or an effect, e.g. using useEffect

Whenever you change the state then rerender is triggered.
In your case, when you are calling setCount on the count and the count state is changing and it is triggering rerender and this cycle is continued. That's why this error.
Try this example, where the setCount is called when only once and the value is being changed as it supposed to be.
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return (
<div>
<p>value of count : {count} fois</p>
</div>
);
}
export default App;
Here is another example where count is increased every time the button is pressed:
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return (
<div>
<p>value of count : {count} fois</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Increase
</button>
</div>
);
}
export default App;
Output:
Codesandbox Example

The reason you are getting the error is that calling setCount(count + 1); will increase the state count forever. Try putting it on a condition, for example in this codesandbox/code below, the state count is increased by 1 every time you click the button. You could also increase state if (count < 10), inside of a setInterval or under a wide variety of conditions. Just not infinitely! ;)
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount(count + 1);
};
return (
<div>
<p>value of count : {count} fois</p>
<button onClick={increaseCount}>start count</button>
</div>
);
}

Related

useEffect() cleanup functions not updating state in the expected order

I'm looking to have child components update a state value through the useContext hook when they are unmounted from the DOM, but am unable to make sense of the order in which their clean up functions (the functions returned from a useEffect hook) are called.
What I'm trying to achieve in the example code is to have the switches all individually able to modify the count of switches in an "on" state so that it's accurate, but also when they are removed from the DOM by the "Remove all switches" button they do the same. It seems like what is happening is that those updates when they are removed from the DOM are not happening in the order I'd expect, and the final state of that counter is not accurate.
If you add several of the switches, turn several of them "on", and then remove all of them, you'll see that the counter in the parent component is not set correctly. I've added a console.log call in the returned function from the useEffect hook, what's especially confusing here to me is that it looks like the counter is being set correctly, but somehow not in the correct order?
(Here is the example code running on StackBlitz.)
import React, { useState, createContext, useContext, useEffect } from 'react';
import './style.css';
const SwitchContext = createContext();
const ChildComponent = () => {
const [switchOn, setSwitchOn] = useState(false);
const { numberOfSwitchesOn, setNumberOfSwitchesOn } =
useContext(SwitchContext);
useEffect(() => {
return () => {
if (switchOn) {
console.log(
`Setting number of switches on to: ${numberOfSwitchesOn - 1}`
);
setNumberOfSwitchesOn(numberOfSwitchesOn - 1);
}
};
}, [switchOn]);
const toggleSwitch = () => {
if (switchOn) {
// We are turning off this switch...
setNumberOfSwitchesOn(numberOfSwitchesOn - 1);
} else {
// We are turning it on...
setNumberOfSwitchesOn(numberOfSwitchesOn + 1);
}
setSwitchOn(!switchOn);
};
return (
<button onClick={() => toggleSwitch()}>{switchOn ? 'on' : 'off'}</button>
);
};
export default function App() {
const [numberOfSwitches, setNumberOfSwitches] = useState(1);
const [numberOfSwitchesOn, setNumberOfSwitchesOn] = useState(0);
let switches = [];
for (let i = 0; i < numberOfSwitches; i++) {
switches.push(<ChildComponent key={`switch-${i}`} />);
}
return (
<SwitchContext.Provider
value={{
numberOfSwitchesOn,
setNumberOfSwitchesOn,
}}
>
<div>
<button onClick={() => setNumberOfSwitches(numberOfSwitches + 1)}>
Add switch
</button>
<button onClick={() => setNumberOfSwitches(0)}>
Remove all switches
</button>
<p>Number of switches on: {numberOfSwitchesOn}</p>
{switches}
</div>
</SwitchContext.Provider>
);
}
This is because they are all updating the same value simultaneously which react does not like. If you want to make sure each ChildComponent is working with the actual latest value you could use functional updates as described in the react docs
useEffect(() => {
return () => {
if (switchOn) {
console.log(
`Setting number of switches on to: ${numberOfSwitchesOn - 1}`
);
setNumberOfSwitchesOn((prev) => prev - 1);
}
};
}, [switchOn]);
Have a look at Your stackblitz sample edited

React, passing state from a Child component to the Parent

I'm just learning React and ran into a problem. I'm making a bunch of counter components that look like this :
The problem is that I have defined the state in each of these counters which is 3 of them and I'd like to pass the value (number) into the parent so I can add it up and display the total count.
Here is my child counter component:
import React, { useState } from "react";
const Counter = () => {
const [count, setcount] = useState(0)
const handleIncrement=()=>{
setcount(count+1);
}
const handleDecrement=()=>{
setcount(count+-1);
}
return (
<div>
<button onClick={()=>{handleIncrement()}}>+</button>
<span>{count}</span>
<button onClick={()=>{handleDecrement()}}>-</button>
</div>
);
};
export default Counter;
And here is the parent which I want to pass my values to so I can add them up and show the total :
import React from 'react'
import Counter from './Counter'
const Counters = () => {
return (
<>
<h3>totalcount:</h3>
<Counter/>
<Counter/>
<Counter/>
</>
)
}
export default Counters
What I tried was to make multiple states but I can't get a good way to make this. I know there's an easy answer for this and I'm making it too complicated. If you guys have other optimization for my code please share.
In React state goes top to bottom. A nested component can update the state of a parent if a function defined in the parent has been passed to it as props. Which means, what you wanna do is not possible as your code is set up. A way to achieve what you are looking for is:
Pass setCounter down to each Counter instance as props, like so:
import React, { useState } from 'react'
import Counter from './Counter'
const Counters = () => {
const [countOne, setCountOne] = useState(0)
const [countTwo, setCountTwo] = useState(0)
const [countThree, setCountThree] = useState(0)
return (
<>
<h3>totalcount: {countOne + countTwo countThree} </h3>
<Counter count = {countOne} setCount = {setCountOne} />
<Counter count = {countTwo} setCount = {setCount} />
<Counter count = {countThree} setCount = {setCountThree} />
</>
)
}
export default Counters
Get setCounter from the props inside Counter and use it where you want:
import React, { useState } from "react";
const Counter = ({count, setCount}) => {
const handleIncrement=()=>{
setCount(count+1);
}
const handleDecrement=()=>{
setCount(count+-1);
}
return (
<div>
<button onClick={()=>{handleIncrement()}}>+</button>
<span>{count}</span>
<button onClick={()=>{handleDecrement()}}>-</button>
</div>
);
};
export default Counter;

Updating Parent component state from multiple child components not taking updated state value into account

I have a question related to react js.
I have a scenario where we have a state in the parent component. It has to be updated by child component. So that, based on the state value, I'll do something else in the parent component.
But, If I render multiple child components, the parent's state is getting updated only once.
Could you please help me understand why the state is updating only once and what would be the possible workaround.
Below is the code sandbox link with the example.
https://codesandbox.io/s/heuristic-cori-8793u?file=/src/App.js
Thanks.
The problem is caused by the fact that updateCounter has closed over the value of count, so when you call it twice with two child components the 'old' value of count is used both times. You can avoid it by using the 'update' form of useState. This ensures that you will increment the 'current' value of count:
const updateCounter = (num) => {
setCount(c => c + 1);
console.log(count);
};
Refer to the doc:
Unlike the setState method found in class components, useState does not automatically merge update objects.
doc Link.
So you can't pass the old state in a setState() like setCount(count + 1);.
Because React will "change" your count value during a re-render and in your case you are calling setCount() many time before React have the time to render again.
And this is also why your console.log(count) displays the previous value.
One solution is this syntax setState(prevState=> prevState+1).
try this:
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const updateCounter = () => {
setCount((prevCount) => prevCount + 1);
};
useEffect(() => {
console.log("Do Something Based On Count Value.");
}, [count]);
return (
<div className="App">
<h1>Hello CodeSandbox {count}</h1>
<ChildApp onLoad={updateCounter} />
<ChildApp onLoad={updateCounter} />
<ChildApp onLoad={updateCounter} />
</div>
);
}
export function ChildApp({ onLoad }) {
console.log("Child Loaded");
useEffect(() => {
onLoad();
}, []);
return (
<div className="App">
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
codeSanBox

useEffect hook not triggered when state is updated somewhere else

I`m having some problems trying to listen to state changes in this application. Basically I was expecting a useEffect hook to be fired after some state changed, but nothing at all is happening.
This is what I got
index.jsx
// this is a simplification.
// I actually have a react-router-dom's Router wrapping everything
// and App is a Switch with multiple Route components
ReactDOM.render(
<Provider>
<App>
</Provider>
, document.getElementById('root'));
useSession.jsx
export const useSession = () => {
const [session, setSession] = useState(null)
const login = useCallback(() => {
// do something
setSession(newSession)
})
return {
session,
setSession,
login
}
}
Provider.jsx
const { session } = useSession();
useEffect(() => {
console.log('Print this')
}, [session])
// more code ...
App.jsx
export function Login() {
const { login } = useSession();
return <button type="button" onClick={() => { login() }}>Login</button>
}
Well I have this Parent component Provider watching the session state, but when it is updated the useEffect of the provider is never called.
The useEffect is fired only if the setSession is called in the same hook/method. For example, if I import the setSession in the Provider and use it there, the useEffect will be fired; Or if I add a useEffect in the useSession method, it is gonna be fired when login updates the state.
The callback of useEffect is called but only once, when the component is mounted, but not when the state is changed.
How can I achieve this behavior? Having the Provider's useEffect fired whenever session is updated?
Thanks in advance!
I think this is just a bit of misunderstanding of how custom hooks work.Every instance of the component has its own state. Let me just show a simple example illustrating this.
function App () {
return (
<div>
<ComponentA/>
<ComponentB/>
<ComponentC/>
<ComponentD/>
</div>
)
}
function useCounter() {
const [counter, setCounter] = React.useState(0);
function increment() {
setCounter(counter+1)
}
return {
increment, counter, setCounter
}
}
function ComponentA() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button A</button>
ComponentA Counter: {counter}
</div>
)
}
function ComponentB() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button B</button>
ComponentB Counter: {counter}
</div>
)
}
function ComponentC() {
const { counter }= useCounter();
return (
<div>
ComponentC Counter: {counter}
</div>
)
}
function ComponentD() {
const [toggle, setToggle] = React.UseState(false);
const { counter }= useCounter();
React.useEffect(() => {
setInterval(()=>{
setToggle(prev => !prev);
}, 1000)
})
return (
<div>
ComponentD Counter: {counter}
</div>
)
}
From the above code if you can see that incrementing count by clicking Button Awill not affect the count instance of ComponentB.This is because every instance of the component has its own state. You can also see that clicking either buttons won't trigger ComponentC to rerender since they don't share the same instance. Even if i trigger rerender every one second like in Component D thus invoking useCounter the counter in ComponentD remains 0.
Solution
However there are multiple ways of making components share/listen to same state changes
You can shift all your state i.e [session state] to the Provider component and make it visible to other components by passing it via props.
You can move state to a global container Redux or simply use Context Api + UseReducer Hook here is an example
But since you are dealing with auth and session management, I suggest you persist the session state in local storage or session storage, and retrieve it whenever you need it. Hope that helped

Using Context API with useState in React.js, any downsides?

I create a context and a provider as below. As you can see, I use useState() within my provider (for state) along with functions (all passed within an object as the value prop, allows for easy destructuring whatever I need in child components).
import React, { useState, createContext } from "react";
const CountContext = createContext(null);
export const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
};
return (
<CountContext.Provider value={{ count, incrementCount, decrementCount }}>
{children}
</CountContext.Provider>
);
};
export default CountContext;
I wrap my app within such a provider(s) at a higher location such as at index.js.
And consume the state using useContext() as below.
import React, { useContext } from "react";
import CountContext from "../contexts/CountContext";
import Incrementer from "./Incrementer";
import Decrementer from "./Decrementer";
const Counter = () => {
const { count } = useContext(CountContext);
return (
<div className="counter">
<div className="count">{count}</div>
<div className="controls">
<Decrementer />
<Incrementer />
</div>
</div>
);
};
export default Counter;
Everything is working just fine, and I find it easier to maintain things this way as compared to some of the other methods of (shared) state management.
CodeSandbox: https://codesandbox.io/s/react-usecontext-simplified-consumption-hhfz6
I am wondering if there is a fault or flaw here that I haven't noticed yet?
One of the key differences with other state management tools like Redux is performance.
Any child that uses a Context needs to be nested inside the ContextProvider component. Every time the ContextProvider state changes it will render, and all its (non-memoized) children will render too.
In contrast, when using Redux we connect each Component to the store, so each component will render only if the part of the state it is connect to changes.

Categories