How to useState() properly in React - reactjs

I have a simple increment app where you hit a button and the count increments by 1.
My question is: how do I update the state properly?
Here are the two ways I am wondering between and of course if there is any other "better" option please do tell me.
import React, {useState} from "react"
const App = () => {
const [count, setCount] = useState(0)
const increment = () => {
setCount(prevCount => prevCount + 1)
}
return (
<div>
<h1>The count is {count}</h1>
<button onClick={increment}>Add 1</button>
</div>
)
}
export default App
or
import React, {useState} from "react"
const App = () => {
const [count, setCount] = useState(0)
const increment = () => {
setCount(count + 1)
}
return (
<div>
<h1>The count is {count}</h1>
<button onClick={increment}>Add 1</button>
</div>
)
}
export default App
Could you tell me which one is the best way of updating the state and why? Thank you!

Imagine a case as follows, and you'll mean the difference:
const increment = () => {
setCount(prevCount => prevCount + 1)
setCount(prevCount => prevCount + 1)
}
Or:
const increment = () => {
setCount(count + 1)
setCount(count + 1)
}
Not the same behavior.

usually when use useCallback, it's better to use setCount(x=> x + 1);
const onIncr = React.useCallback(()=> {
setCount(x=> x + 1)
}, [])
vs
const onIncr = React.useCallback(()=> {
setCount(count + 1)
}, [count])
tips , this example can transform into
const [count, increment] = React.useReducer((x)=> x + 1, 0);
return <button onClick={increment}>{count}</button>
This technique is usually used in toggle value
const [isOpen, toggle] = React.useReducer(x=> !x, false);
return (
<>
<button onClick={toggle}>open dialog</button>
<Dialog open={isOpen} onClose={toggle}></Dialog>
<>
)

Related

Why is the min state getting updated 2 times instead of only once?

Why is the min state getting updated in the multiples of two instead of just updating by one after every 59 seconds? How do I fix it?
import { useRef, useState } from "react";
export const Timer = () => {
const [second, setSecond] = useState(0);
const [min, setMin] = useState(0);
const watch = useRef(null);
const startTimer = () => {
watch.current = setInterval(() => {
setSecond((value) => {
if (value === 59) {
setSecond(0);
setMin((v) => v + 1);
}
return value + 1;
});
}, 1000);
};
return (
<div>
<h1>
{min}:{second}{" "}
</h1>
<button onClick={startTimer}>Start</button>
<button onClick={() => clearInterval(watch.current)}>Pause</button>
<button
onClick={() => {
setSecond(0);
return clearInterval(watch.current);
}}
>
Reset
</button>
</div>
);
};
This is the component as a whole. I am new to react so please help.

What is the best way to create variable that depends on the state in react?

I want to create 2 variables that depend on state. What i want to achieve is self-explanatory in my code below.
const evenodd = () => {
const [counter, setCounter] = useState(0)
const handleClick = () => {
setCounter(counter +1)
}
// initiate with var, let, const, or useRef?
(?) even = counter*2
(?) odd = counter*2+1
return (
<div>
counter: {counter}
even: {even}
odd: {odd}
<button onClick={handleClick}>add</button>
</div>
)
}
export default evenodd
between var, let, const, or useRef which one should i pick for my 2 variables that depend on state? and for what reason?
Try to use useMemo hooks
const evenodd = () => {
const [counter, setCounter] = useState(0)
const handleClick = () => {
setCounter(counter +1)
}
const even = React.useMemo(()=> (counter * 2), [counter]);
const odd = React.useMemo(()=> (counter * 2 + 1), [counter]);
return (
<div>
counter: {counter}
even: {even}
odd: {odd}
<button onClick={handleClick}>add</button>
</div>
)
}

How to move react event handlers to separate file ,export and then import for reuse in multiple different functional components?

I have a React functional component which prints a counter number being incremented or decremented on button clicks..Below is my function
I am using react version : ^16.13.1
export default function Counter(){
const [count, setCount] = useState(0);
const increment = () => {
//more logic here ..
setCount(count + 1);
}
const decrement = () => {
//more logic here ..
setCount(count !== 0 ? count - 1 : 0);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => increment()}>Increment SO</button>
<button onClick={() => decrement()}>Decrement SO</button>
</div>
);
}
I want to separate event handlers logic that includes setting state in to a separate file and export it.. like below
import React, { useState } from 'react';
const Increment = () => {
//more logic here ..
setCount(count + 1);
}
const Decrement = () => {
//more logic here ..
setCount(count !== 0 ? count - 1 : 0);
}
export default { Increment, Decrement };
I wanted to use these exported functions in the Counter function like below
import CounterHelper from './CounterHelper';
<button onClick={() => CounterHelper.Increment()}>Increment SO</button>
I have run in to several error's while running this ,so i am sure i am missing some thing fundamental here. Can somebody please tell me if this is possible at all ? are there any alternate options to achieve the above said with functional component and react hooks only.
Thanks in advance.
Just pass the variable references.
CounterHelper.js
export const Increment = (count, setCount) => {
//more logic here ..
setCount(count + 1);
};
export const Decrement = (count, setCount) => {
//more logic here ..
setCount(count !== 0 ? count - 1 : 0);
};
Counter.js
import React, { useState } from 'react';
import { Increment, Decrement } from './CounterHelper';
export default function Counter(){
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => Increment(count, setCount )}>Increment SO</button>
<button onClick={() => Decrement(count, setCount)}>Decrement SO</button>
</div>
);
}

State is not reflecting immediately in react hooks

I am learning hooks. I am trying to update state but it's not reflecting immediately.
Here is my code.
const Test = (props) => {
const [score , setScore] = useState(0);
const [count , setCount] = useState(0);
const [total, setTotal] = useState(0);
const playerBallClick= ()=> {
setCount(count+1);
setScore(Math.floor(Math.random() * 10));
setTotal(total + score);
}
return (
<div>
<button onClick={playerBallClick}>Ball</button>
{/* <p>Total score is - {totalscore}</p> */}
</div>
)
}
How can I update Total immediately onclick on Ball button.
You can use useEffect hook like so,
useEffect(() => {
setTotal(total + score);
}, [count]);
So everytime count state changes, this hook will be called updating your total state.
Score is a stale closure try the following instead:
const playerBallClick = () => {
setCount(count + 1);
const newScore = Math.floor(Math.random() * 10);
setScore(newScore);
setTotal(total => total + newScore);
};

useEffect when all dependencies have changed?

Currently useEffect is fired when just one of the dependencies have changed.
How could I update it / use it to fire back when both ( or all ) of the dependencies have changed?
You'll need to add some logic to call your effect when all dependencies have changed. Here's useEffectAllDepsChange that should achieve your desired behavior.
The strategy here is to compare the previous deps with the current. If they aren't all different, we keep the previous deps in a ref an don't update it until they are. This allows you to change the deps multiple times before the the effect is called.
import React, { useEffect, useState, useRef } from "react";
// taken from https://usehooks.com/usePrevious/
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function useEffectAllDepsChange(fn, deps) {
const prevDeps = usePrevious(deps);
const changeTarget = useRef();
useEffect(() => {
// nothing to compare to yet
if (changeTarget.current === undefined) {
changeTarget.current = prevDeps;
}
// we're mounting, so call the callback
if (changeTarget.current === undefined) {
return fn();
}
// make sure every dependency has changed
if (changeTarget.current.every((dep, i) => dep !== deps[i])) {
changeTarget.current = deps;
return fn();
}
}, [fn, prevDeps, deps]);
}
export default function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
useEffectAllDepsChange(() => {
console.log("running effect", [a, b]);
}, [a, b]);
return (
<div>
<button onClick={() => setA((prev) => prev + 1)}>A: {a}</button>
<button onClick={() => setB((prev) => prev + 1)}>B: {b}</button>
</div>
);
}
An alternate approach inspired by Richard is cleaner, but with the downside of more renders across updates.
function useEffectAllDepsChange(fn, deps) {
const [changeTarget, setChangeTarget] = useState(deps);
useEffect(() => {
setChangeTarget(prev => {
if (prev.every((dep, i) => dep !== deps[i])) {
return deps;
}
return prev;
});
}, [deps]);
useEffect(fn, changeTarget);
}
You'll have to track the previous values of your dependencies and check if only one of them changed, or both/all. Basic implementation could look like this:
import React from "react";
const usePrev = value => {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
const App = () => {
const [foo, setFoo] = React.useState(0);
const [bar, setBar] = React.useState(0);
const prevFoo = usePrev(foo);
const prevBar = usePrev(bar);
React.useEffect(() => {
if (prevFoo !== foo && prevBar !== bar) {
console.log("both foo and bar changed!");
}
}, [prevFoo, prevBar, foo, bar]);
return (
<div className="App">
<h2>foo: {foo}</h2>
<h2>bar: {bar}</h2>
<button onClick={() => setFoo(v => v + 1)}>Increment foo</button>
<button onClick={() => setBar(v => v + 1)}>Increment bar</button>
<button
onClick={() => {
setFoo(v => v + 1);
setBar(v => v + 1);
}}
>
Increment both
</button>
</div>
);
};
export default App;
Here is also a CodeSandbox link to play around.
You can check how the usePrev hook works elsewhere, e.g here.
I like #AustinBrunkhorst's soultion, but you can do it with less code.
Use a state object that is only updated when your criteria is met, and set it within a 2nd useEffect.
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
const [ab, setAB] = useState({a, b});
useEffect(() => {
setAB(prev => {
console.log('prev AB', prev)
return (a !== prev.a && b !== prev.b)
? {a,b}
: prev; // do nothing
})
}, [a, b])
useEffect(() => {
console.log('both have changed')
}, [ab])
return (
<div className="App">
<div>Click on a button to increment its value.</div>
<button onClick={() => setA((prev) => prev + 1)}>A: {a}</button>
<button onClick={() => setB((prev) => prev + 1)}>B: {b}</button>
</div>
);
}
FWIW, react-use is a nice library of additional hooks for react that has ~30k stars on GitHub:
https://github.com/streamich/react-use
And one of those custom hooks is the useCustomCompareEffect:
https://github.com/streamich/react-use/blob/master/docs/useCustomCompareEffect.md
Which could be easily used to handle this kind of custom comparison
To demonstrate how you can compose hooks in various manners, here's my approach. This one doesn't invoke the effect in the initial attribution.
import React, { useEffect, useRef, useState } from "react";
import "./styles.css";
function usePrevious(state) {
const ref = useRef();
useEffect(() => {
ref.current = state;
});
return ref.current;
}
function useAllChanged(callback, array) {
const previousArray = usePrevious(array);
console.log("useAllChanged", array, previousArray);
if (previousArray === undefined) return;
const allChanged = array.every((state, index) => {
const previous = previousArray[index];
return previous !== state;
});
if (allChanged) {
callback(array, previousArray);
}
}
const randomIncrement = () => Math.floor(Math.random() * 4);
export default function App() {
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(0);
const [state3, setState3] = useState(0);
useAllChanged(
(state, prev) => {
alert("Everything changed!");
console.info(state, prev);
},
[state1, state2, state3]
);
const onClick = () => {
console.info("onClick");
setState1(state => state + randomIncrement());
setState2(state => state + randomIncrement());
setState3(state => state + randomIncrement());
};
return (
<div className="App">
<p>State 1: {state1}</p>
<p>State 2: {state2}</p>
<p>State 3: {state3}</p>
<button onClick={onClick}>Randomly increment</button>
</div>
);
}

Resources