I have come across a weird problem where changing the order of a clone inside a setState() hook function changes the expected behavior.
I am trying to add one second to the value each second. However doing this directly causes the seconds to increase by two instead of one.
This works
const [value, setValue] = useState(new Date());
useEffect(() => {
const interval = setInterval(
() =>
setValue((value) => {
const clonedDate = new Date(value.getTime());
clonedDate.setSeconds(clonedDate.getSeconds() + 1); // Add one second to the time
return clonedDate;
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
This adds two seconds instead of one
const [value, setValue] = useState(new Date());
useEffect(() => {
const interval = setInterval(
() =>
setValue((value) => {
value.setSeconds(value.getSeconds() + 1);
const clonedDate = new Date(value.getTime());
return clonedDate;
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
The only thing that is clear to me as that in the second version a state mutation is obviously occurring, but to be honest it isn't clear to me exactly where. It seems as though even though you are creating a new javascript Date object that it is still referencing properties of the previous data object.
Consider the following examples that exhibit identical behavior:
function App() {
const [value1, setValue1] = useState({ c: 0 });
const [value2, setValue2] = useState({ c: 0 });
useEffect(() => {
const interval = setInterval(
() =>
setValue1((value) => {
const clonedValue = { ...value }; // shallow copy first
clonedValue.c = clonedValue.c + 1; // update copy
return clonedValue; // return copy
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
useEffect(() => {
const interval = setInterval(
() =>
setValue2((value) => {
value.c = value.c + 1; // mutate current
const clonedValue = { ...value }; // shallow copy
return clonedValue; // return copy
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
return (
<div className="App">
<h1>Non-mutaiton Version</h1>
{value1.c}
<h1>Mutation Version</h1>
{value2.c}
</div>
);
}
Interestingly though, if you remove the React.StrictMode from around App the two perform identically.
StrictMode currently helps with:
Identifying components with unsafe lifecycles
Warning about legacy string ref API usage
Warning about deprecated findDOMNode usage
Detecting unexpected side effects
Detecting legacy context API
Detecting unexpected side effects
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
Demo with your original date objects running in both StrictMode and non-StrictMode:
The issue is with below line it is mutating the value object. You don't need to call value.setSeconds, value.getSeconds()+1 itSelf is enough to increment seconds by 1.
Replace below line of code -
const interval = setInterval(
() =>
setValue2((value2) => {
let second = value2.getSeconds() +1;
//value2.setSeconds(second);
let newTime = new Date();
newTime.setSeconds(second);
const clonedDate = new Date(newTime.getTime());
return clonedDate;
}),
1000
);
Related
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();
I have a webpage where I fetch the data with async axios and then make calculations with them.
Here is the code snippet:
const FetchData = async () =>{
console.log("FETCH CALLED");
await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
.then(resp => {
setStockData(resp.data);
calculateTrend();
calculateTrendDirection();
})
}
Here, I get the error at calculateTrend() function. My question is, that this .then() should run when the response has arrived, but it seems that it runs before. Because both calculateTrend and calculateTrendDirection works with this fetched data
Edit: The error I am getting is Cannot read property 'previousClosePrice' of undefined. I am sure this exist in the object so mispelling is not a problem
Edit2: I edited my Component according to your solutions and one happens to work, the only thing is that the fetching gets to an infinite loop and fetches multiple times a second. My suspect is the dependencies in useEffect, but I am not sure what to set there.
Here is my full component:
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 () =>{
console.log("FETCH CALLED");
const resp = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
setStockData(resp.data);
}
const calculateTrendDirection = () => {
console.log(StockData.lastPrice);
if(StockData.lastPrice.currentPrice > StockData.lastPrice.previousClosePrice){
setTrendDirection(1);
} else if (StockData.lastPrice.currentPrice < StockData.lastPrice.previousClosePrice){
setTrendDirection(-1);
} else {
setTrendDirection(0);
}
}
const calculateTrend = () => {
console.log(StockData.lastPrice);
var result = 100 * Math.abs( ( StockData.lastPrice.previousClosePrice - StockData.lastPrice.currentPrice ) / ( (StockData.lastPrice.previousClosePrice + StockData.lastPrice.currentPrice)/2 ) );
setTrend(result.toFixed(2));
}
useEffect(() => {
FetchData();
if(StockData.lastPrice){
console.log("LÉTEZIK A LAST PRICE")
calculateTrend();
calculateTrendDirection();
}
const interval = setInterval(() => {
FetchData();
}, FetchInterval)
return() => clearInterval(interval);
},[StockData, FetchData, FetchInterval, calculateTrend, calculateTrendDirection]);
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>
)
The then block is called only after the promise is fulfilled, so the data is available at that point.
From what I can see, the problem is setStockData tries to set the stockData state variable with the response, but calculateTrend and calculateTrendDirection are called before the state is set because updating state values is batched.
There are several solutions to the problem.
Solution 1:
You can call the two functions after the state is set:
setStockData(resp.data, () => {
calculateTrend();
calculateTrendDirection();
});
Solution 2:
You can use useEffect to call the functions again after the state is updated:
useEffect(() => {
if (stockData) { // or whatever validation needed
calculateTrend();
calculateTrendDirection();
}
}, [stockData]);
Solution 3:
You can pass the parameters to the method:
calculateTrend(resp.data);
calculateTrendDirection(resp.data);
The best option? I think #2, because it also makes sure that the trend and trend direction are re-calculated whenever stock data is updated (from whatever other causes).
I guess in calculateTrend you are using the data which setStockData sets to the state, if that is the case
setState is not happening right after you call the setState, if you want something to execute after correctly update the State then should look at something like this
setStockData(resp.data, () => {
calculateTrend();// this will call once the state gets changed
});
or you could use useEffect
useEffect(() => {
calculateTrend(); // this will call every time when stockData gets changed
}, [stockData])
If you are using stockData inside calculateTrend function and setStockData is an async function, move calculateTrend function to useEffect using stockData as dependency, so every time stockData is updated, calculateTrend and calculateTrendDirection will be called:
useEffect(() => {
const interval = setInterval(() => {
FetchData();
}, FetchInterval);
return() => clearInterval(interval);
}, [FetchInterval]);
useEffect(() => {
if(StockData.lastPrice){
console.log("LÉTEZIK A LAST PRICE")
calculateTrend();
calculateTrendDirection();
}
}, [StockData]);
const FetchData = async () =>{
console.log("FETCH CALLED");
const res = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`);
setStockData(resp.data);
}
React state value not updated in the console but it is updated in the view.
This is my entire code
import React, { useEffect, useState } from 'react';
const Add = (props) => {
console.log("a = ", props.a)
console.log("b = ", props.b)
const c = props.a+props.b;
return (
<div>
<p><b>{props.a} + {props.b} = <span style={{'color': 'green'}}>{c}</span></b></p>
</div>
)
}
// export default React.memo(Add);
const AddMemo = React.memo(Add);
const MemoDemo = (props) => {
const [a, setA] = useState(10)
const [b, setB] = useState(10)
const [i, setI] = useState(0);
useEffect(() => {
init()
return () => {
console.log("unmounting...")
}
}, [])
const init = () => {
console.log("init", i)
setInterval(()=>{
console.log("i = ", i)
if(i == 3){
setA(5)
setB(5)
}else{
setA(10)
setB(10)
}
setI(prevI => prevI+1)
}, 2000)
}
return (
<div>
<h2>React Memo - demo</h2>
<p>Function returns previously stored output or cached output. if inputs are same and output should same then no need to recalculation</p>
<b>I= {i}</b>
<AddMemo a={a} b={b}/>
</div>
);
}
export default MemoDemo;
Please check this image
Anyone please explain why this working like this and how to fix this
The problem is as you initialized the setInterval once so it would reference to the initial value i all the time. Meanwhile, React always reference to the latest one which always reflect the latest value on the UI while your interval is always referencing the old one. So the solution is quite simple, just kill the interval each time your i has changed so it will reference the updated value:
React.useEffect(() => {
// re-create the interval to ref the updated value
const id = init();
return () => {
// kill this after value changed
clearInterval(id);
};
// watch the `i` to create the interval
}, [i]);
const init = () => {
console.log("init", i);
// return intervalID to kill
return setInterval(() => {
// ...
});
};
In callback passed to setInterval you have a closure on the value of i=0.
For fixing it you can use a reference, log the value in the functional update or use useEffect:
// Recommended
useEffect(() => {
console.log(i);
}, [i])
const counterRef = useRef(i);
setInterval(()=> {
// or
setI(prevI => {
console.log(prevI+1);
return prevI+1;
})
// or
conosole.log(counterRef.current);
}, 2000);
See demo here
I'm connecting to sockets (modelled by setTimeouts!) and getting an array. On mount I get an initial array. Then I keep listening for updates to the array. An update is sent as just the change and not a whole array.
I need to access the current state, but it's empty. Even though it looks fine in the render.
I think this might be a scoping or closure bug caused by numbers being empty at the time of calling addLater(), but I'm not sure what the solution is.
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [numbers, setNumbers] = useState([]);
useEffect(() => {
// initial connection to socket
setNumbers(["first", "second", "third"]);
// incoming messages from socket
addLater();
addMuchLater();
}, []);
const addLater = () => {
window.setTimeout(() => {
console.log("Why is the state empty? ", numbers);
const changedNumbers = [...numbers];
changedNumbers.splice(1, 1, "fourth");
setNumbers(changedNumbers);
}, 5000);
};
const addMuchLater = () => {
window.setTimeout(() => {
const changedNumbers = [...numbers];
changedNumbers.splice(2, 1, "fifth");
setNumbers(changedNumbers);
}, 10000);
};
return (
<div className="App">
{numbers.map((r, i) => (
<p>
{i}: {r}
</p>
))}
</div>
);
}
When the next value depends on the previous one it's best to write the code as a functional update so the code will always be acting on the latest value. As you ran into, your current code closes over the original value of numbers, which isn't what you want:
const addLater = () => {
window.setTimeout(() => {
setNumbers(prevNumbers => {
const changedNumbers = [...prevNumbers];
changedNumbers.splice(1, 1, "fourth");
return changedNumbers;
});
}, 5000);
};
const addMuchLater = () => {
window.setTimeout(() => {
setNumbers(prevNumbers => {
const changedNumbers = [...prevNumbers];
changedNumbers.splice(2, 1, "fifth");
return changedNumbers;
});
}, 10000);
};
setState is asynchronous so the calls to state that happen in the functions later on get the state as it was when the component rendered, not as it is after the new state was set. You can use a callback function as the second argument:
useEffect(() => {
// initial connection to socket
setNumbers(["first", "second", "third"],
()=>{
// incoming messages from socket
addLater();
addMuchLater();
}), []);
See:
https://upmostly.com/tutorials/how-to-use-the-setstate-callback-in-react
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>