React Effect dependecy with two useState values and Effect Refiring - reactjs

Following is my code
const [accountUsers, setAccountUsers] = useState([]);
const [listLoading, setListLoading] = useState(true);
const [totalAccountUsers, setTotalAccountUsers] = useState(0);
const getAccountUserList = useCallback(
async (page = 0, pageSize = 10) => {
console.log('Kitne users hain??==', accountUsers.length);
if (
// TODO: Move this logic to action for reusability
accountUsers.length < (page + 1) * pageSize &&
(accountUsers.length < totalAccountUsers || accountUsers.length === 0)
) {
console.log('inside if condition');
console.log(' Total Account users=', totalAccountUsers);
console.log(
'first condiont=',
' accountUsers.length < (page + 1) * pageSize ',
accountUsers.length < (page + 1) * pageSize,
' AND'
);
console.log(
'second one condition',
' accountUsers.length < totalAccountUsers =',
accountUsers.length < totalAccountUsers,
' OR ',
'second two condition',
' accountUsers.length === 0',
accountUsers.length === 0
);
setListLoading(true);
const data = await getAccountUsers(id, page + 1, pageSize);
setAccountUsers((users) => users.concat(data.data.data.results));
setTotalAccountUsers(data.data.data.total);
setListLoading(false);
}
},
[accountUsers, totalAccountUsers, id]
);
const refetchUsers = useCallback(() => {
setAccountUsers([]);
setTotalAccountUsers(0);
}, []);
useEffect(() => {
getAccountUserList();
}, [getAccountUserList]);
return (
<>
<Button onClick={() => refetchUsers()}>Refetch</Button>
<Button onClick={async ()=> await delete(id); refetchUsers()> Delete </Button>
</>
)
My refetch function works fine when called individually. But when an async function is involved the callback is called twice. I feel in the refetch functions two setStates are causing it to call the callback twices with the help of effect.
Why does that happen and what's the work around?
IF refetch is called before any async operation it works perfectly fine though. Is it some issue with react batching the state updates before firing effects.

Your code is effectively as below by condensing your dependency tree (and ignoring some unrelated stuff):
const Home = () => {
const [accountUsers, setAccountUsers] = useState<number[]>([]);
const [totalAccountUsers, setTotalAccountUsers] = useState(0);
React.useEffect(() => {
const getAccountUserList = async () => {
const data = await Promise.resolve({results: [1, 2, 3]});
setAccountUsers((users) => users.concat(data.results));
setTotalAccountUsers(data.results.length);
};
getAccountUserList();
},
[accountUsers, totalAccountUsers]);
const refetchUsers = () => {
setAccountUsers([]);
setTotalAccountUsers(0);
};
return (
<>
<button onClick={() => refetchUsers()}>Refetch</button>
</>
);
};
We can easily spot the issue here - infinite rerendering caused by the useEffect hook. You are seeing the api is called twice likely because (and luckily) it has finished reading all data and the if condition in your code stops further state updating.
Some personal suggestions:
Avoid useCallback from the beginning. It's an optimization, and we should start with correct core functionality not optimization.
For user events, try to avoid triggering side effects using useEffect - it'll likely require additional state update and leads to unnecessary rerendering. Do that in the event handler directly.
Use useEffect without dependency (one time) for initial data loading.
Try to avoid dependency "hierarchy" - For example in your original case useEffect depends on getAccountUserList which depends on other state objects. This kind of hierarchy makes it harder to read the code and can lead to bugs hard to spot. Instead try to structure your code so that they depend on state directly.
The below code (again, simplified) should work for your scenario:
const Home = () => {
const [accountUsers, setAccountUsers] = useState<number[]>([]);
const [totalAccountUsers, setTotalAccountUsers] = useState(0);
const getAccountUserList = async () => {
const data = await Promise.resolve({results: [1, 2, 3]});
setAccountUsers((users) => users.concat(data.results));
setTotalAccountUsers(data.results.length);
};
React.useEffect(() => {
getAccountUserList();
}, []);
const refetchUsers = () => {
setAccountUsers([]);
setTotalAccountUsers(0);
getAccountUserList();
};
return (
<>
<button onClick={() => refetchUsers()}>Refetch</button>
</>
);
};

Related

Switching image src with images from an array on an interval in React

This should be fairly simple, but I keep getting a weird behaviour from the result.
Basically, I have an array of images:
const images = [img1, img2, img3, img4, img5, img6];
I also have an image index:
const [imageIndex, setImageIndex] = useState(0);
Then I do a little incrementation of the index:
const switchImage = () => {
if (imageIndex === images.length - 1) {
setImageIndex(0);
} else {
setImageIndex(imageIndex + 1);
}
return imageIndex;
}
Then I call this function from a useEffect:
useEffect(() => {
setInterval(() => {
switchImage();
}, 1000);
}, []);
And finally I add the html:
<img src={images[imageIndex]} />
The result is usually it gets stuck on the second image and stops incrementing, so I thought the issue might be with the useEffect and the way the component is rendering.
You need to use the second method signature of the useState setter function which gives you the previous state value to avoid the stale closure captured value.
const root = ReactDOM.createRoot(document.getElementById('root'));
const images = ['1','2','3','4','5','6'];
const Thing =()=>{
const [imageIndex, setImageIndex] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setImageIndex(prev => (
prev === images.length - 1 ? 0 : prev + 1
));
}, 1000);
},[])
console.log(imageIndex)
return (
<div>
<h1>{images[imageIndex]}</h1>
</div>
);
}
root.render(<Thing />);
See here https://codepen.io/drGreen/pen/JjpmQrV
Also worth seeing this link which is virtually identical.
In your case the useEffect which you have created it is only being triggered once; when the component is loading - that is because you did not define when this logic should be triggered by adding dependencies to the useEffect.
Now, since the component renders once, 'switchImage'()' is only being triggered once, hence, it iterates once, display the img and stops.
Here is some good documentation on useEffect if you would like to read more about it Using the Effect Hook - React
๐Ÿ’กHere is a slightly altered solution where we are using the debounce technique for the timer. SOLUTION๐Ÿ’ก
const root = ReactDOM.createRoot(document.getElementById('root'));
const images = ['๐Ÿ’ก','๐Ÿ˜Š','๐Ÿ˜','๐Ÿ˜','๐ŸŽฏ','๐Ÿ‘Œ'];
const DemoComponent = () =>{
const [imageIndex, setImageIndex] = React.useState(0);
//debounce set default 0.3s
const debounce = (func, timeout = 300) =>{
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
// switch img fn.
const switchImage = () => {
setImageIndex(imageIndex === images.length - 1 ? 0 : imageIndex + 1)
return imageIndex;
}
//debounce switchImage and set timer to 1s
const switchImageDebounce = debounce(() => switchImage(),1000);
//useEffect
React.useEffect(() => {
switchImageDebounce()
}, [imageIndex]);
return (
<div>
<h1>{images[imageIndex]}</h1>
</div>
);
}
root.render();

React useState - How should I use the setter function?

Basic Situation
Let's say I have a basic useState of a number:
const [valueOne, setValueOne] = useState(0);
I can write an increase function in two ways:
First way:
// for one value
const increaseOneFirstWay = useCallback(() => {
setValueOne((prev) => prev + 1);
}, []); // doesnt have dependency
Since the setter function of a useState doesn't change (source), I don't have to add any dependencies to my callback function.
Second way
const increaseOneSecondWay = useCallback(() => {
setValueOne(valueOne + 1);
}, [valueOne]); // has one dependency
Here, since I am using valueOne, I have to add a dependency, so the callback updates accordingly.
For a basic callback like this, using both ways seems fine. But what if it gets more complicated?
Complicated Situation
Now, instead of having one state, we will have three:
const [valueTwo, setValueTwo] = useState(0);
const [valueThree, setValueThree] = useState(0);
const [valueFour, setValueFour] = useState(0);
This time, the callback will need to use all three values. And some of them together.
First way:
// for several values:
const increaseSeveralFirstWay = useCallback(() => {
setValueTwo((valueTwoPrev) => {
setValueThree((valueThreePrev) => {
setValueFour((valueFourPrev) => {
return valueFourPrev + valueThreePrev + valueTwoPrev + 1;
});
return valueThreePrev + valueTwoPrev + 1;
});
return valueTwoPrev + 1;
});
}, []); // doesnt have dependency
Second way:
const increaseSeveralSecondWay = useCallback(() => {
setValueTwo(valueTwo + 1);
setValueThree(valueThree + valueTwo + 1);
setValueFour(valueFour + valueThree + valueTwo + 1);
}, [valueTwo, valueThree, valueFour]); // has several dependency
Let's say that valueTwo, valueThree, and valueFour also change independently, wouldn't the first way be a better choice? Or is there a reason why someone would use the second way (Not opinion-based, but maybe performance? maybe it's not recommended at all to use the first way?)
Codesandbox
In the case you have multiple states depending on each other the solution is often to use a reducer. However sometimes the use of a reducer is not necessary since the state can be simplified.
I will here demonstrate the 2 solutions with 2 examples:
Solution 1: Using a reducer
useReducer is usually preferable to useState when you have complex
state logic that involves multiple sub-values or when the next state
depends on the previous one. -- React Docs
import { useReducer } from 'react';
const initialNumberState = {
valueOne: 0,
valueTwo: 0,
valueThree: 0,
};
const numberReducer = (prevState, action) => {
if (action.type === 'INCREASE_NUMBER') {
const { valueOne, valueTwo, valueThree } = prevState;
const newValueOne = valueOne + 1;
const newValueTwo = valueOne + valueTwo + 1;
const newValueThree = valueOne + valueTwo + valueThree + 1;
return {
valueOne: newValueOne,
valueTwo: newValueTwo,
valueThree: newValueThree,
};
}
return prevState;
};
const CustomComponent = (props) => {
const [numberState, dispatch] = useReducer(
numberReducer,
initialNumberState
);
const { valueOne, valueTwo, valueThree } = numberState;
const handleClick = () => {
dispatch({ type: 'INCREASE_NUMBER', value: 'not_used_in_this_case' });
};
return (
<div>
<ul>
<li>Number 1: {valueOne}</li>
<li>Number 2: {valueTwo}</li>
<li>Number 3: {valueThree}</li>
</ul>
<button onClick={handleClick}>Click Me!</button>
</div>
);
};
export default CustomComponent;
Solution 2: Simplifying the state
This is the case when we can derive all the data we need from independent states.
For example imagine we are validating a form with separate states:
const [isEmailValid, setIsEmailValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
const [isFormValid, setIsFormValid] = useState(false);
Here setting the state for the email and password validation is easy. However we start encountering issues when we want to set the state for the form.
handlePasswordChange = (event) =>{
passwordValue = event.currentTarget.value;
const isValid = validatePassword(passwordValue);
setIsPasswordValid(isValid);
const formValid = isPasswordValid && isEmailValid;
setIsFormValid(formValid);
/* Here we will encounter issues since we are updating
the form validity on a stale password validity value; */
}
Here the solution could have been : const formValid = isValid && isEmailValid;
But the optimal solution is simplifying the state:
const [isEmailValid, setIsEmailValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
const isFormValid = isEmailValid && isPasswordValid;
This is a simplistic example and you might think this never happens. But we often over complicate things.

Render array in React one-by-one, having pauses between

I'm having trouble rendering multiple Snackbars, I know it's not allowed to render multiple at once, so I tried notistack,but when I put maxSnack={1} autoHideDuration={3000} and I enqueue 2 Snackbars, then the first one disappears immediately, and I would need to make it stay for those 3 seconds. Therefore I've decided to create own solution. I have an array of messages, and I want to render Snackbars with those messages like render 1st one for 3 seconds, then 2nd one etc.
I've tried to accomplish this with setTimeout() but didn't succeed. I am not sure how to approach this problem. Here's what I got so far:
export const NotificationsInterval = (props: INotificationsProps) => {
const [notifications, setNotifications] = React.useState<IListItem[]>([]);
const [currentIndex, setCurrentIndex] = React.useState<number>(0);
const [currentItem, setCurrentItem] = React.useState<IListItem>();
React.useEffect(() => {
const timer = setTimeout(() => {
setCurrentItem(props.notifications[currentIndex]);
setCurrentIndex(currentIndex => currentIndex + 1);
}, 1000);
return () => clearTimeout(timer);
}, []);
return (
<>
{notifications.length !== 0 && notifications.map(n => {
{currentItem.Title}
})}
</>
);
}

My components are not updated when data arrives from fetch

Here is my code:
function StockCard(props) {
const [FetchInterval, setFetchInterval] = useState(300000);
const [StockData, setStockData] = useState({});
const [TrendDirection, setTrendDirection] = useState(0);
const [Trend, setTrend] = useState(0);
const FetchData = async () =>{
const resp = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
setStockData(resp.data);
}
const calculateTrendDirection = () => {
if(StockData.lastPrice.currentPrice > StockData.lastPrice.previousClosePrice){
setTrendDirection(1);
} else if (StockData.lastPrice.currentPrice < StockData.lastPrice.previousClosePrice){
setTrendDirection(-1);
} else {
setTrendDirection(0);
}
}
const calculateTrend = () => {
var result = 100 * Math.abs( ( StockData.lastPrice.previousClosePrice - StockData.lastPrice.currentPrice ) / ( (StockData.lastPrice.previousClosePrice + StockData.lastPrice.currentPrice)/2 ) );
setTrend(result.toFixed(2));
}
useEffect(() => {
FetchData();
const interval = setInterval(async () => {
await FetchData();
}, FetchInterval)
return() => clearInterval(interval);
},[FetchInterval]);
useEffect(()=>{
if(StockData.lastPrice){
console.log("Trends calculated", StockData.name);
calculateTrend();
calculateTrendDirection();
}
},[StockData])
return(
<div>
<CryptoCard
currencyName={StockData.lastPrice? StockData.name : "Name"}
currencyPrice={StockData.lastPrice? `$ ${StockData.lastPrice.currentPrice}` : 0}
icon={<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/Bitcoin.svg/2000px-Bitcoin.svg.png"/>}
currencyShortName={StockData.lastPrice? StockData.symbol : "Symbol"}
trend={StockData.lastPrice? `${Trend} %` : 0}
trendDirection={StockData.lastPrice? TrendDirection : 0}
chartData={[9200, 5720, 8100, 6734, 7054, 7832, 6421, 7383, 8697, 8850]}
/>
</div>
)
}
export default StockCard;
The basic idea is. I have a backend from which I fetch data let's say every minute(this is why i need setInterval) and I have cards which are showing off the data i fetched. I have an expression so it says generic things like "Name" until the data has arrived, then it should re-render with the real data.
But this doesn't happen. It fetches all the data, I can log it out but it doesn't get updated.
And error number 2 is it says that in the useEffects i should include the functions into dependencies.
So for example in the second useEffect where I call the function calculateTrend() and calculateTrendDirection, it says I should include not only the StockData but the two functions too.
I tried #Ozgur Sar 's fix and it worked, so it turned out the problem was "timing" with my api calls

React Hook useEffect has a missing dependency: 'dispatch'

This is my first time working with react js , im trying to remove the alert when leaving this view cause i don't want to show it on the other view but in case that there is no error i want to keep the success alert to show it when i'm gonna redirect to the other view
but im getting this wearning on google chrome
Line 97:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
if i did include dispatch i get infinite loop
const [state, dispatch] = useUserStore();
useEffect(() => {
let token = params.params.token;
checktoken(token, dispatch);
}, [params.params.token]);
useEffect(() => {
return () => {
if (state.alert.msg === "Error") {
dispatch({
type: REMOVE_ALERT
});
}
};
}, [state.alert.msg]);
//response from the api
if (!token_valide || token_valide_message === "done") {
return <Redirect to="/login" />;
}
this is useUserStore
const globalReducers = useCombinedReducers({
alert: useReducer(alertReducer, alertInitState),
auth: useReducer(authReducer, authInitState),
register: useReducer(registerReducer, registerInitState),
token: useReducer(passeditReducer, tokenvalidationInitState)
});
return (
<appStore.Provider value={globalReducers}>{children}</appStore.Provider>
);
};
export const useUserStore = () => useContext(appStore);
UPDATE 09/11/2020
This solution is no longer needed on eslint-plugin-react-hooks#4.1.0 and above.
Now useMemo and useCallback can safely receive referential types as dependencies.#19590
function MyComponent() {
const foo = ['a', 'b', 'c']; // <== This array is reconstructed each render
const normalizedFoo = useMemo(() => foo.map(expensiveMapper), [foo]);
return <OtherComponent foo={normalizedFoo} />
}
Here is another example of how to safely stabilize(normalize) a callback
const Parent = () => {
const [message, setMessage] = useState('Greetings!')
return (
<h3>
{ message }
</h3>
<Child setter={setMessage} />
)
}
const Child = ({
setter
}) => {
const stableSetter = useCallback(args => {
console.log('Only firing on mount!')
return setter(args)
}, [setter])
useEffect(() => {
stableSetter('Greetings from child\'s mount cycle')
}, [stableSetter]) //now shut up eslint
const [count, setCount] = useState(0)
const add = () => setCount(c => c + 1)
return (
<button onClick={add}>
Rerender {count}
</button>
)
}
Now referential types with stable signature such as those provenients from useState or useDispatch can safely be used inside an effect without triggering exhaustive-deps even when coming from props
---
Old answer
dispatch comes from a custom hook so it doesn't have an stable signature therefore will change on each render (reference equality). Add an aditional layer of dependencies by wrapping the handler inside an useCallback hook
const [foo, dispatch] = myCustomHook()
const stableDispatch = useCallback(dispatch, []) //assuming that it doesn't need to change
useEffect(() =>{
stableDispatch(foo)
},[stableDispatch])
useCallback and useMemo are helper hooks with the main purpose off adding an extra layer of dependency check to ensure synchronicity. Usually you want to work with useCallback to ensure a stable signature to a prop that you know how will change and React doesn't.
A function(reference type) passed via props for example
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [])
}
Lets assume you have a child component which uppon mounting must set some state in the parent (not usual), the above code will generate a warning of undeclared dependency in useEffect, so let's declare setParentState as a dependency to be checked by React
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [setParentState])
}
Now this effect runs on each render, not only on mounting, but on each update. This happens because setParentState is a function which is recreated every time the function Component gets called. You know that setParentState won't change it's signature overtime so it's safe to tell React that. By wrapping the original helper inside an useCallback you're doing exactly that (adding another dependency check layer).
const Component = ({ setParentState }) =>{
const stableSetter = useCallback(() => setParentState(), [])
useEffect(() => setParentState('mounted'), [stableSetter])
}
There you go. Now React knows that stableSetter won't change it's signature inside the lifecycle therefore the effect do not need too run unecessarily.
On a side note useCallback it's also used like useMemo, to optmize expensive function calls (memoization).
The two mai/n purposes of useCallback are
Optimize child components that rely on reference equality to prevent unnecessary
renders. Font
Memoize expensive calculations
I think you can solve the problem at the root but that means changing useCombinedReducers, I forked the repo and created a pull request because I don't think useCombinedReducers should return a new reference for dispatch every time you call it.
function memoize(fn) {
let lastResult,
//initial last arguments is not going to be the same
// as anything you will pass to the function the first time
lastArguments = [{}];
return (...currentArgs) => {
//returning memoized function
//check if currently passed arguments are the same as
// arguments passed last time
const sameArgs =
currentArgs.length === lastArguments.length &&
lastArguments.reduce(
(result, lastArg, index) =>
result && Object.is(lastArg, currentArgs[index]),
true,
);
if (sameArgs) {
//current arguments are same as last so just
// return the last result and don't execute function
return lastResult;
}
//current arguments are not the same as last time
// or function called for the first time, execute the
// function and set last result
lastResult = fn.apply(null, currentArgs);
//set last args to current args
lastArguments = currentArgs;
//return result
return lastResult;
};
}
const createDispatch = memoize((...dispatchers) => action =>
dispatchers.forEach(fn => fn(action)),
);
const createState = memoize(combinedReducers =>
Object.keys(combinedReducers).reduce(
(acc, key) => ({ ...acc, [key]: combinedReducers[key][0] }),
{},
),
);
const useCombinedReducers = combinedReducers => {
// Global State
const state = createState(combinedReducers);
const dispatchers = Object.values(combinedReducers).map(
([, dispatch]) => dispatch,
);
// Global Dispatch Function
const dispatch = createDispatch(...dispatchers);
return [state, dispatch];
};
export default useCombinedReducers;
Here is a working example:
const reduceA = (state, { type }) =>
type === 'a' ? { count: state.count + 1 } : state;
const reduceC = (state, { type }) =>
type === 'c' ? { count: state.count + 1 } : state;
const state = { count: 1 };
function App() {
const [a, b] = React.useReducer(reduceA, state);
const [c, d] = React.useReducer(reduceC, state);
//memoize what is passed to useCombineReducers
const obj = React.useMemo(
() => ({ a: [a, b], c: [c, d] }),
[a, b, c, d]
);
//does not do anything with reduced state
const [, reRender] = React.useState();
const [s, dispatch] = useCombinedReducers(obj);
const rendered = React.useRef(0);
const [sc, setSc] = React.useState(0);
const [dc, setDc] = React.useState(0);
rendered.current++;//display how many times this is rendered
React.useEffect(() => {//how many times state changed
setSc(x => x + 1);
}, [s]);
React.useEffect(() => {//how many times dispatch changed
setDc(x => x + 1);
}, [dispatch]);
return (
<div>
<div>rendered {rendered.current} times</div>
<div>state changed {sc} times</div>
<div>dispatch changed {dc} times</div>
<button type="button" onClick={() => reRender({})}>
re render
</button>
<button
type="button"
onClick={() => dispatch({ type: 'a' })}
>
change a
</button>
<button
type="button"
onClick={() => dispatch({ type: 'c' })}
>
change c
</button>
<pre>{JSON.stringify(s, undefined, 2)}</pre>
</div>
);
}
function memoize(fn) {
let lastResult,
//initial last arguments is not going to be the same
// as anything you will pass to the function the first time
lastArguments = [{}];
return (...currentArgs) => {
//returning memoized function
//check if currently passed arguments are the same as
// arguments passed last time
const sameArgs =
currentArgs.length === lastArguments.length &&
lastArguments.reduce(
(result, lastArg, index) =>
result && Object.is(lastArg, currentArgs[index]),
true
);
if (sameArgs) {
//current arguments are same as last so just
// return the last result and don't execute function
return lastResult;
}
//current arguments are not the same as last time
// or function called for the first time, execute the
// function and set last result
lastResult = fn.apply(null, currentArgs);
//set last args to current args
lastArguments = currentArgs;
//return result
return lastResult;
};
}
const createDispatch = memoize((...dispatchers) => action =>
dispatchers.forEach(fn => fn(action))
);
const createState = memoize(combinedReducers =>
Object.keys(combinedReducers).reduce(
(acc, key) => ({
...acc,
[key]: combinedReducers[key][0],
}),
{}
)
);
const useCombinedReducers = combinedReducers => {
// Global State
const state = createState(combinedReducers);
const dispatchers = Object.values(combinedReducers).map(
([, dispatch]) => dispatch
);
// Global Dispatch Function
const dispatch = createDispatch(...dispatchers);
return [state, dispatch];
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Resources