I am new to react and I am experimenting with incrementing a value and storing it into local storage. I was able to write down the following code bellow, however, the last instance of the incremented number is not updated the local storage value. For example, if I press "+1" twice and the number is 10, the dom is updated twice and shows the number 12, but the value stored on local storage is 11. Why is this happening?
import { useState } from 'react'
function About () {
const localStorageValue = parseInt(localStorage.getItem('value'))
const [value, setValue] = useState(localStorageValue);
function remove() {
setValue((add) => add - 1)
localStorage.setItem('value', parseInt(value))
}
function add() {
setValue((add) => add + 1)
localStorage.setItem('value', parseInt(value))
}
return (
<>
<h1>About page</h1>
<button onClick={remove}>-1</button>
{value}
<button onClick={add}>+1</button>
</>
)
}
export default About;
Use the refresh button and it will reflect the changes
Issue here is asynchronous behaviour of setState i.e. it will update the state value with a bit of delay and without stopping for complete execution of setState it will proceed to next line of code i.e. localStorage.setItem().
In your case setState is setValue
So to overcome this you can need some kind of callback function behaviour to achieve this with useState hook so you can use useEffect for the same as follows:
import { useState, useEffect } from "react";
function About() {
const localStorageValue = parseInt(localStorage.getItem("value"));
const [value, setValue] = useState(localStorageValue);
useEffect(() => {
localStorage.setItem("value", parseInt(value));
}, [value]); // only rerun if value changes
function remove() {
setValue((add) => add - 1);
}
function add() {
setValue((add) => add + 1);
}
return (
<>
<h1>About page</h1>
<button onClick={remove}>-1</button>
{value}
<button onClick={add}>+1</button>
</>
);
}
export default About;
This will ensure that it will run everytime after successive state update.
PS. There's no need to use parseInt while storing as it will be stored as string only.
Related
When I click the MultipleComponent button, all logs in the function return null.
The second time I click it, it returns the previous values.
How can I get the current status in each log within the map function?
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { observer } from "mobx-react-lite";
function App() {
const [component, setComponent] = useState([]);
useEffect(() => {});
const newArray = [1, 2, 3];
const Test = observer(() => {
return (
<div>
<p>Test</p>
</div>
);
});
const Test2 = observer(() => {
return (
<div>
<p>Test2</p>
</div>
);
});
const Test3 = observer(() => {
return (
<div>
<p>Test3</p>
</div>
);
});
function MultipleComponent() {
newArray.map(async (x) => {
if (x === 1) {
setComponent((ps) => [...ps, Test]);
console.log(component);
} else if (x === 2) {
setComponent((ps) => [...ps, Test2]);
console.log(component);
} else {
setComponent((ps) => [...ps, Test3]);
console.log(component);
}
});
}
return (
<div>
{component.map((Input, index) => (
<Input components={component} key={index} />
))}
<button onClick={() => setComponent([...component, Test])}>
Single Component
</button>
<button onClick={() => MultipleComponent()}>Multiple Component</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
codensadbox: https://codesandbox.io/s/react-hooks-useeffect-forked-shxvl6
When I click the MultipleComponent button, all logs in the function return null.
The second time I click it, it returns the previous values.
React state updates are asynchronous and do not update any values in the current scope. They trigger a render, and then your component executes again with that new state.
See this for more info: The useState set method is not reflecting a change immediately
How can I get the current status in each log within the map function?
You can't get the state that has been changed until the next render, but you don't need to because you have the value that you set. Just use that if you need to. For example:
setComponent((ps) => {
const newState = [...ps, Test];
console.log(newState);
return newState;
});
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
Your effect has no dependencies, which means that it will run after every render. Just pass an array of dependencies to the effect in order to only execute it when those change. If you pass an empty array, then it will only every execute once.
useEffect(() => console.log('I run only once'), []);
See the docs on useEffect for more: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Hi as per the documentation "calling the set function does not change state in the running code". If you need to use the next state, you can save it in a variable before passing it to the set function.
Here is a link of your updated code.
https://codesandbox.io/s/react-hooks-useeffect-forked-m7ipwb?file=/src/index.js
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
By default, Effects run after every render. You can tell React to skip unnecessarily re-running the Effect by specifying an array of dependencies as the second argument to the useEffect call. Start by adding an empty [] array
From the React Docs, what I have learnt is that the component will re-render only if there is a change in the value of a state.
For instance
import React, { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
console.log("I am rendering");
const handleButtonClick = () => {
setCount(0);
};
return (
<>
<button onClick={handleButtonClick}>Increment</button>
Count value is: {count}
</>
);
}
The message I am rendering is printed only once even if we click the button because the setCount function is setting the value to 0 which is the present value of count
Since there is no change in the present and future value therefore, the Component does not re-render.
Unexpected Behaviour
However, the similar behaviour is not observed when we add an extra line setCount(1) before setCount(0)
import React, { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
console.log("I am rendering");
const handleButtonClick = () => {
setCount(1); //this line has been added extra
setCount(0);
};
return (
<>
<button onClick={handleButtonClick}>Increment</button>
Count value is: {count}
</>
);
}
In principle, there is no change in the output of the final count value. However, if we click the button, the component re-renders and prints the message I am rendering
I could not find an explanation for this behaviour. Is this behaviour on expected lines?.
Shouldn't the component re-render only when the final value of the state is different from the current value ?
Sometimes, Reacts needs another render phase to decide if it needs a bailout. By the way, when we saying "bailout" meaning bailing out the Reconciliation process.
Notice the documentation on Bailing out a state update:
Note that React may still need to render that specific component again before bailing out.
Here is another example of such case demonstrating the idea:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [state, setState] = React.useState(0);
useEffect(() => {
console.log("B");
}, [state]);
console.log("A");
return (
<>
<h1>{state}</h1>
<button onClick={() => setState(42)}>Click</button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
You notice the next logs and their explanations:
A // First render
B // Mount
A // State change from 0 -> 42, triggers render
B // useEffect dep array change, triggers callback
A // **Our issue**, React needs another render
The value does change when you press the button. First, it changes to 1 then to 0 but this runs very fast that you don't get to see it.
to see this, you could add a setTimeout
const handleButtonClick = () => {
setCount(1); //this line has been added extra
setTimeout(() => {
setCount(0);
}, 500);
};
i got a issue with my code. My function "getNames" rerender may times, but i want it to render once? have you got any clue ?
import grounds from './../../UballersGroundsData.json';
export default function Groundlist() {
function getNames(jsonObj){
for(let item in jsonObj){
console.log("item = " + item);
for(let property in jsonObj[item] ){
console.log(jsonObj[item]);
// if (property === "groundName"){
// console.log(jsonObj[item][property]);
// }
}
}
}
return(
<div>
<h1>Hello world!</h1>
<ul>
{getNames(grounds)}
</ul>
</div>
)
}
Thank you !
You should put your function inside of a useEffect hook, then set it to a state hook with useState. Then, map out the list items for your list (assuming you are returning an array from your function). If you want it to only run getNames on the first render, you would set it up the useEffect hook with an empty dependency array. Code should look something like this:
import React, { useEffect, useState } from 'react'
import grounds from './../../UballersGroundsData.json';
export default function Groundlist() {
const [names, setNames] = useState([]) // Initial state with empty array
useEffect(() => {
function getNames(jsonObj){
// your function logic here...
}
const result = getNames(grounds) // Call your function
setNames(result) // set it to names state hook
}, []) // Empty array here means it will only use the useEffect on the first render.
return(
<div>
<h1>Hello world!</h1>
<ul>
{Array.from(names).map(name => <li>{name}</li>)}
</ul>
</div>
)
}
You can use useMemo react hook to memoize the returned value i.e. skip unnecessary / heavy calculations due to change in other state, props or context variables.
Example:
import { useMemo } from "react"
export default function Groundlist(props) {
const grounds = props.data // if grounds is passed as props from Parent component
const groundsMemo = useMemo(() => {
// do all the heavy calculations here
// (e.g. do the work of getNames function)
// and return some JSX or Array (data)
// returned value will be memoized;
// means it will be re-calculated only if "grounds" changes
// Hence, no unnecessary calls to getNames (heavy calculations)
}, [grounds])
return (
<div>
{/* Use this if groundsMemo is JSX */}
<ul>{groundsMemo}</ul>
{/* Use this if groundsMemo is an Array (data) */}
<ul>{groundsMemo.map(item => <li key={some_key}>
{item.property}
</li>)}</ul>
</div>
)
}
Try using useMemo and useCallBack you want to optimize your react app.
React Official docs clearly described how to use it: useMemo
useCallBack
You should never call a function inside return scope of render.
It's normal for a component to re-render without proper treatment.
Taking in mind the other 2 answers
You can use the full power of useEffect, useCallback and React.memo to prevent anything from re-render.
import React from 'react';
import grounds from './../../UballersGroundsData.json';
function Groundlist() {
// initiate state
const [names, setNames] = React.useState([]);
// This will prevent the Function from recalculate - useCallback
const getNames = React.useCallback(function (jsonObj) {
for(let item in jsonObj){
console.log("item = " + item);
for(let property in jsonObj[item] ){
console.log(jsonObj[item]);
// if (property === "groundName"){
// console.log(jsonObj[item][property]);
// }
}
}
}, []);
// Will make function run only once and nevermore - useEffect
React.useEffect(() => {
setNames(getNames());
}, [])
return(
<div>
<h1>Hello world!</h1>
<ul>
{names.map(a => <li>{a}</li>)}
</ul>
</div>
)
}
// Will prevent React from try to re-render without changing in props, so as your component has no props, will never re-render without yourself unmounting first
export default React.memo(Groundlist);
In another cases you can control exact when the component should recalculate your names using the last argument of functions
useCallback(() => {}, []) //<---
For example
useCallback(() => {}, [updateState]);
when updateState change the function will be recreated.
Sandbox
import React, { useEffect, useState } from "react";
export default function App() {
let [button, setButton] = useState(null);
let [num, setNum] = useState(5);
function revealState() {
console.log(num);
}
function changeState() {
setNum(Math.random());
}
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, []);
return (
<>
{button}
<button onClick={changeState}>Change state</button>
</>
);
}
Clicking on the 'Log state button' successfully logs num state. Clicking on the 'Change state button' successfully changes num state. Re-clicking the 'Log state button' doesn't log the updated value of state - it logs the old one.
Why is this? My guess is that, since useEffect runs only the once, it references only the first revealState function which references only the first num variable. Because it's not in the component's return statement it doesn't get 'refreshed'.
Whatever the cause of the problem, what're some work-arounds? Some of the requirements are:
the tag can't be rendered directly in the return statement.
we have to have the useEffect that's there and it needs to have a dep array of some sort (its undesirable for it to fire every-time the function component is re-executed).
In the real project, some important changes to the tags useEffect's callback renders might get made - therefore it's impractical to re-run the useEffect by putting something like num in its dep array.
IMO, the neatest solution is to simply add the updated event listener, every time the page is rendered:
useEffect(() => {
el.onclick = onClickHandler
});
The event listener always has access to the latest state (and props). IMO, this solution is more scalable than previously-mentioned solutions - if my event listener has to track the latest versions of multiple state & props, this could get messy. With this, all I need to do is add extra listeners in into this one useEffect callback. Thoughts?
import React, { useEffect, useState, useCallback } from "react";
export default function App() {
let [button, setButton] = useState(null);
let [num, setNum] = useState(5);
const revealState = useCallback(() => {
console.log(num);
}, [num])
function changeState() {
setNum(Math.random());
}
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, [revealState]);
return (
<>
{button}
<button onClick={changeState}>Change state</button>
</>
);
}
you can listen to the revealState in useEffect. which gets initialized only when num is changed achieved using useCallback. so whenever you click the button the num is changed which initializes the revealState function and not initialized on other rerenders
you have to add num as dependency to useEffect:
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, [num]);
After more clarification on your problem it seems you need to watch over both the num and the HTML state. Combining both Alan and kishore's code together is the solution. Since the useEffect is only watching the num in Alan's answer so if any changes to the tag will not cause it to rerun. kishore also mentioned another fix which is to use the useCallback but what needs to be watch is the button and not the num. Like this:
const updateButton = useCallback(function (newButton) {
setButton(newButton);
}, [button])
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
updateButton(el)
}, [num]);
This will tell useEffect to watch num and will return a new button only when button state is changed.
I needed a hook to get the previous distinct value of a specific state. It looks like this and it seems to work:
function usePreviousDistinct(state) {
const prevRef = useRef();
useEffect(() => {
prevRef.current = state;
}, [state]);
return prevRef.current;
}
I've also seen there is a usePreviousDistinct hook in the react-use package but the approach is different than mine.
import { useRef } from 'react';
import { useFirstMountState } from './useFirstMountState';
export type Predicate<T> = (prev: T | undefined, next: T) => boolean;
const strictEquals = <T>(prev: T | undefined, next: T) => prev === next;
export default function usePreviousDistinct<T>(value: T, compare: Predicate<T> = strictEquals): T | undefined {
const prevRef = useRef<T>();
const curRef = useRef<T>(value);
const isFirstMount = useFirstMountState();
if (!isFirstMount && !compare(curRef.current, value)) {
prevRef.current = curRef.current;
curRef.current = value;
}
return prevRef.current;
}
I wonder if I have not understood something or am missing something. Is my version also correct?
In my test I could not find a difference:
https://codesandbox.io/s/distracted-mayer-zpym8?file=/src/App.js
useEffect() together with useRef() (your version) does not show the latest value.
useRef() without useEffect() gives you the correct value, because it runs synchronously, but doesn't have the advantages that come with asynchronicity.
useEffect() together with useState() gives you the correct value, but might trigger unnecessary renders (adds potentially unnecessary overhead).
Your version looks like it works as expected, because the old value that is shown is the one that you expect to see as the new value. But the actual new value is not the one you want.
Example:
import React, { useState, useEffect, useRef } from 'react';
export const MyComponent = function(props){
const [state, setState ] = useState(0);
return <React.Fragment>
state: { state },<br />
with useEffect: { usePreviousDistinctUE( state ) },<br />
w/o useEffect: { usePreviousDistinctR( state ) },<br />
<button onClick={ function(){
setState( state + 1 );
} }>
increment
</button>
</React.Fragment>;
};
const usePreviousDistinctUE = function( value ){
const prevRef = useRef();
useEffect(() => {
prevRef.current = value;
console.log('with useEffect, prev:', prevRef.current, ', current:', value);
}, [value]);
return prevRef.current;
};
const usePreviousDistinctR = function( value ){
const prevRef = useRef();
const curRef = useRef( value );
if( curRef.current !== value ){
prevRef.current = curRef.current;
curRef.current = value;
}
console.log('w/o useEffect, prev:', prevRef.current, ', current:', curRef.current);
return prevRef.current;
};
The values shown on the page are the same, but in the console they are different. That means the value in the useEffect() version is changed, it is only not yet shown on the page.
If you just add another hook that updates anything unrelated (leaving everything else unchanged), then the page (might*) magically show the updated value again, because the page is re-rendered and the previously already changed value is shown. The value is now wrong in your eyes, but it is not changed, only shown:
// ...
with useEffect: { usePreviousDistinctUE( state ) },<br />
w/o useEffect: { usePreviousDistinctR( state ) },<br />
anything updated: { useAnythingUpdating( state ) },<br />
// ...
const useAnythingUpdating = function(state){
const [result, setResult ] = useState(0);
useEffect(() => {
setResult( state );
console.log('anything updated');
});
return result;
};
*But you shouldn't rely on something else triggering a re-render. I'm not even sure this would update as expected under all circumstances.
more Details:
useEffect() is triggered at some time when react decides that the prop must have been changed. The ref is changed then, but react will not 'get informed' about a ref change, so it doesn't find it necessary to re-render the page to show the changed value.
In the example without useEffect() the change happens synchronously.
React doesn't 'know' about this change either, but (if everything else runs as expected) there will be always a re-render when necessary anyway (you will have called that function from another function that is rendered at the end).
(Not informing react about the change is basically the point in using useRef(): sometimes you want just a value, under your own control, without react doing magic things with it.)