I have a React app that displays stock quotes, the user can add and remove stocks from the list etc. I have an effect to fetch the prices, since it depends on the quotes array, when I add a new quote it'll run the fetch manually. But I also want the prices to be continually updated after a short interval....roughly like below :
function StockApp() {
const [quotes, setQuotes] = useState([]);
// an entry in the quotes array is an object like below
// They come like this straight from the backend which returns
// a simple JSON format
// {
// symbol: 'MSFT',
// price: 298.12
// dayChange: 1.2
// }
useEffect(() => {
const timer = setTimeout(async () => {
const newQuotes = await fetchPrices(quotes);
// this will be a new array
setQuotes(newQuotes);
}, TICKER_REFRESH_INTERVAL);
return () => clearTimeout(timer);
}, [quotes]);
... render function etc
Although the app works fine, my query is about the recursive nature of my useEffect function....as I understand it I'm saying here "Whenever quotes changes, run the function" but the effect function is also changing quotes, it seems like the setTimeout prevents it recursing. I feel like I am not thinking about the problem in the correct way, how can I avoid this cyclic dependency ? Do I need to ?
Related
I have a question about useTracker from meteor and react.
const sample = useTracker(
() => func1(),
[slug],
);
Does this mean every time sulg gets changed, fun1 gets running and sample will get a new value? like a dependency array with useEffect?
Thank you
You are partially right, if slug updates your function re-runs but it would also re-run if the function func1 is using a reactive data source.
The documentation for useTracker has a pretty good example:
// This computation uses no value from the outer scope,
// and thus does not needs to pass a 'deps' argument.
// However, we can optimize the use of the computation
// by providing an empty deps array. With it, the
// computation will be retained instead of torn down and
// rebuilt on every render. useTracker will produce the
// same results either way.
const currentUser = useTracker(() => Meteor.user(), []);
// The following two computations both depend on the
// listId prop. When deps are specified, the computation
// will be retained.
const listLoading = useTracker(() => {
// Note that this subscription will get cleaned up
// when your component is unmounted or deps change.
const handle = Meteor.subscribe('todoList', listId);
return !handle.ready();
}, [listId]);
// my personal note: this function updates either if
// lsitId changes OR if Tasks get updated by pub/sub
// for example because documents have been inserted/updated/removed
// on the server
const tasks = useTracker(() => Tasks.find({ listId }).fetch(), [listId]);
see: https://github.com/meteor/react-packages/tree/master/packages/react-meteor-data#usetrackerreactivefn-deps-hook-with-deps
I'm getting API data from the backend using axios and in .then().
What I'm trying to do is when another client is added and I get it to the list, I want to make a statement that notices the new client on the list, and after it to complete the statement.
My idea is to compare the length of the list, and if in the list is added a client, the length would be added + 1.
But the thing is that every time the page is rendered, the list is re-updated as well since I'm doing it with useEffect.
So my idea is to make a variable that remembers the previous length on a list in a state, and if a new client comes to the list the condition might be clientList.length > prev.clinetListlength
useEffect(() => {
const clientParams =
"userName=" +
currentUser
"clientId=" +
currentClient
setClientList([]);
axios
.get(API + clientParams)
.then((response) => {
let newListClient = response.data.map(function (client) {
return {
...client,
id: client.cleint_id,
name: client.client_name,
created: client.created,
};
});
setClientList(newListClient);
//the logic...:
if(newListClient.length > prev.newListClient) {
console.log("test")
})
.catch((error) => console.log(error));
}, []);
What makes it hard, on my behalf is the useEffect that re-renders the list every time, so it might be hard to remember a previous length bc it is updated as a new one every time, which in this case the console.log("test") is shown every time.
You can use window.localStorage to store previous values. here is an example of some code
const prevLen = window.localStorage.getItem('prevLen')
window.localStorage.setItem('prevLen' , newListClient.length)
// you can make you're logic like so
Probably, LocalStorage will be enough for your issue. The idea is to put your length to browser level and update it, write additional useEffect
useEffect(() => {
const getSavedList = localStorage.getItem("myList");
setClientList(getSavedList);
}, [])
So, in your useEffect you should
get value from local store
compare with value from backend
set new list to strage
I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}
In my project, for sending a request for getting my user data and show them. I wrote the above code but i realised that if i pass the "people" to useEffect's dependency (second parameter) react sends infinite request to my firebase but if i delete and keep the second parameter empty the useEffect works correct what is the difference between these two?
Here is the code that goes to infinite loop:
const [people, setPeople]=useState([])
useEffect(() => {
const unsubscribe=database.collection("people").onSnapshot(snapshot=>
setPeople(snapshot.docs.map(doc=>doc.data()))
)
return () => {
unsubscribe()
}
}, [people]) // if i change the second parameter with an empty list this problem solved.
return (
<div>
<h1>TinderCards</h1>
<div className="tinderCards_cardContainer">
{people.map(person =>
<TinderCard
className="swipe"
key={person.name}
preventSwipe={["up","down"]}
>
<div style={{backgroundImage: `url(${person.url})`}} className="card">
<h3>{person.name}</h3>
</div>
</TinderCard>
)}
</div>
</div>
)
Essentially, the useEffect hook runs the inner function code every time any of the dependencies in the dependency array (second parameter) change.
Since setPeople changes people, the effect keeps running in an infinite loop:
useEffect(() => {
... setPeople() ... // <- people changed
}, [people]); // <- run every time people changes
If you needed somehow the value of people and you need to have it in the dependency array, one way to check is if people is not defined:
useEffect(() => {
if (!people) {
// ... do something
setPeople(something);
}
}, [people]);
As you correctly pointed out, simply taking off the people dependency tells the effect to only run once, when the component is "mounted".
On an extra note, you may be wondering why people is changing if you are fetching the same exact results. This is because the comparison is shallow, and every time an array is created, it's a different object:
const a = [1,2,3];
const b = [1,2,3];
console.log(a === b); // <- false
You would need to do deep equality checks for that.
The issue is after you set state in useEffect, the people value will be changed which will trigger another useEffect call hence an infinite loop.
You can modify it to this:-
useEffect(() => {
const unsubscribe=database.collection("people").onSnapshot(snapshot=>
setPeople(snapshot.docs.map(doc=>doc.data()))
)
return () => {
unsubscribe()
}
}, [])
PROBLEM
useEffect runs every time when any one of values given to dependency array changes. Since, you're updating your people after the db call. The reference to array people changes, hence triggering an infinite loop on useEffect
SOLUTION
You do not need to put people in the dependency array.
Your useEffect function doesn't depend on people.
useEffect(() => {
const unsubscribe=database.collection("people").onSnapshot(snapshot=>
setPeople(snapshot.docs.map(doc=>doc.data()))
)
return () => {
unsubscribe()
}
}, [])
main problem is that the people array which is being created at every set-call is not the same. The object is completely different.
I also had this trouble as i want to display the contents as soon as the some new "people" is added to the database from the admin panel, but it turns out that without refreshing this thing cannot be solved otherwise u can make your own hook with PROPER comparisons .
Maybe u can try by comparing the length of the PEOPLE array. I haven't tried it yet but i think it will work.
I'd like to know how does React "freezes" the closure while using the useCallback hook (and with others as well), and then only updates variables used inside the hook when you pass them into the inputs parameter.
I understand that the "freeze" may not be very clear, so I created a REPL.it that shows what I mean: https://repl.it/repls/RudeMintcreamShoutcast. Once you open the code, open your web browser console and start clicking on the count button.
How come the value outside compared to the one inside, for the same variable, is different, if they're under the same closure and referencing the same thing? I'm not familiar with React codebase and so I suppose I'm missing an under the hood implementation detail here, but I tried to think how that could work for several minutes but couldn't come up with a good understanding on how React is achieving that.
The first time the component is rendered, the useCallback hook will take the function that is passed as its argument and stores it behind the scenes. When you call the callback, it will call your function. So far, so good.
The second time that the component is rendered, the useCallback hook will check the dependencies you passed in. If they have not changed, the function you pass in is totally ignored! When you call the callback, it will call the function you passed in on the first render, which still references the same values from that point in time. This has nothing to do with the values you passed in as dependencies - it's just normal JavaScript closures!
When the dependencies change, the useCallback hook will take the function you pass in and replace the function it has stored. When you call the callback, it will call the new version of the function.
So in other words, there's no "frozen"/conditionally updated variables - it's just storing a function and then re-using it, nothing more fancy than that :)
EDIT: Here's an example that demonstrates what's going on in pure JavaScript:
// React has some component-local storage that it tracks behind the scenes.
// useState and useCallback both hook into this.
//
// Imagine there's a 'storage' variable for every instance of your
// component.
const storage = {};
function useState(init) {
if (storage.data === undefined) {
storage.data = init;
}
return [storage.data, (value) => storage.data = value];
}
function useCallback(fn) {
// The real version would check dependencies here, but since our callback
// should only update on the first render, this will suffice.
if (storage.callback === undefined) {
storage.callback = fn;
}
return storage.callback;
}
function MyComponent() {
const [data, setData] = useState(0);
const callback = useCallback(() => data);
// Rather than outputting DOM, we'll just log.
console.log("data:", data);
console.log("callback:", callback());
return {
increase: () => setData(data + 1)
}
}
let instance = MyComponent(); // Let's 'render' our component...
instance.increase(); // This would trigger a re-render, so we call our component again...
instance = MyComponent();
instance.increase(); // and again...
instance = MyComponent();
I came here with a similar, rather vague uncertainty about the way useCallback works, its interaction with closures, and the way they are "frozen" by it. I'd like to expand a bit on the accepted answer by proposing to look at the following setup, which shows the working of useCallback (the important aspect is to ignore the linter's warning, for pedagogical reasons):
function App() {
const [a, setA] = useState(0)
const incrementWithUseCallback = useCallback(() => {
// As it closes on the first time `App` is called, the closure is "frozen" in an environment where a=0, forever
console.log(a)
setA(a + 1)
}, []) // but.. the linter should complain about this, saying that `a` should be included!
const incrementWithoutUseCallback = () => {
// This will see every value of a, as a new closure is created at every render (i.e. every time `App` is called)
console.log(a)
setA(a + 1)
}
return (
<div>
<button onClick={incrementWithUseCallback}>Increment with useCallback</button>
<button onClick={incrementWithoutUseCallback}>Increment without useCallback</button>
</div>
)
}
So we clearly see that useCallback effectively "freezes" its closure at a certain moment in time, which is a concept that must be understood clearly, in order to avoid confusing problems, which are sometimes also referred as "stale closures". This article probably does a better job of explaining it than me: https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures
Here's a slightly another view on example code provided by Joe Clay, which emphasizes closure context in which callback is called.
//internal store for states and callbacks
let Store = { data: "+", callback: null };
function functionalComponent(uniqClosureName) {
const data = Store.data;//save value from store to closure variable
const callback = Store.callback = Store.callback || (() => {
console.log('Callback executed in ' + uniqClosureName + ' context');
return data;
});
console.log("data:", data, "callback():", callback());
return {
increase: () => Store.data = Store.data + "+"
}
}
let instance = functionalComponent('First render');
instance.increase();
instance = functionalComponent('Second render');
instance.increase();
instance = functionalComponent('Third render');
As you see, callback without dependencies will be always executed in the closure where it was memorized by useCallback, thus 'freezing' closure.
It happens because when function for callback is created, it is created only once, during first 'render'. Later this function is re-used, and use value of data which was recorded from Store.data during first call.
In the next example you can see the closure 'freezing' logic "in essence".
let globalX = 1;
const f = (() => {
let localX = globalX; return () => console.log(localX); }
)();
globalX = 2;//does not affect localX, it is already saved in the closure
f();//prints 1