I'm using mobx-state-tree's onSnapshot() feature to listen to state changes, I then persist the state with every change in local storage. This is how I do it:
import { onSnapshot } from "mobx-state-tree";
onSnapshot(store, newSnapshot => {
saveLocalSnapshot(newSnapshot);
});
My question is, how do I unsubscribe from onSnapshot() when my app needs to stop persisting every snapshot?
Helper functions like onSpanshot or onPatch (and basically every other subscribe method) return IDisposer, which is basically a function that you can call to literally dispose the subscription.
Related
I have a timer in my component, which is running with setInterval that starts in mounted() component.
Suppose this component is at http://localhost:3000/some-route.
Now how do I do clearInterval() whenever I go to another route like http://localhost:3000/ as I want to stop the timer.
I've used unmounted() but when you go to different route, the component doesn't unmounts but if I go to same route (/some-route), setInterval runs again as the component is mounted again.
So, how do I clear the interval every time I go to different route?
I had to do it once, it's a bit tricky because of the scope and the way it interacts with this (arrow functions basically). At the end, I also needed it to be available globally so I used it in pair with Vuex but you can totally use it in a component with some upper scope for the variable.
Here is a sample of code I've used
actionSetPolling({ state, dispatch }) {
try {
const myPolling = setInterval(async function () {
if (someVariable !== 'COMPLETED') {
// do stuff
} else if (conditionToStop) {
// this is facultative, but can be done here so far too
window.clearInterval(myPolling)
}
}, 2000)
dispatch('setPollingId', myPolling)
} catch (error) {
console.warn('error during polling', error.response)
window.clearInterval(state.pollingId)
}
}
setPollingId is an action that set's a pollingId mutation, that way I can track it all along globally.
You could use the same while unmounting your component with this
beforeDestroy() {
window.clearInterval(state.pollingId)
},
Not sure if it's the best way of doing things but setInterval is by it's nature, clunky and hacky out of the box IMO, especially in an SPA.
So basically this is i what i want to do. I have a button which calls the handleAdd event, when that event triggers i want to call the useEffect function. The useEffect(); calls a function which returns a different api key depending on the value in input. But i have realized useEffect doesen't work that way and can only be called in the "top level" of the code. Is there some way i can work around this issue? See example below:
input = "example";
const handleAdd = () => {
//Call useEffect
};
useEffect(() => {
getCoinMarketDataApi.request("example");
}, []);
Thanks in advance:) any input is appreciated, i'm having quite the hard time trying to figure out how to work with useEffect and async events so the answer might be obvious.
It sounds like the function that you have in useEffect should really be part of the function that gets passed to the <button>s onClick handler.
There are use cases when you do need to retrigger a useEffect in response to a button click (e.g. a project I’m working on needs to fetch data from a web service when the component is mounted and again if user input changes a value from the default) so:
useEffect functions get called when:
The component is initially mounted
When a dependency changes
So you can do what you are asking for by:
Creating a state with useState
Passing the state variable as a dependency to useEffect
Setting that state variable in your click event handler
setState updates state asynchronously. It's my understanding that, when using a class component, you can do something like this to ensure certain code is executed after one setState updates state:
setState({color: red}, callbackThatExecutesAfterStateIsChanged);
I'm using a functional component & hooks. I'm aware, here, useEffect()'s callback will execute everytime after color state changes and on initial execution.
useEffect(callback, [color]);
How can I replicate similar behaviour as the class component example - that is, to execute a chunk of code once after one setState() successfully changes state and not on initial execution?
If you ask me, there is no safe way to do this with hooks.
The problem is that you both have to read and set an initialized state in order to ignore the first update:
const takeFirstUpdate = (callback, deps) => {
const [initialized, setInitialized] = useState(false);
const [wasTriggered, setWasTriggered] = useState(false);
useEffect(() => {
if (!initialized) {
setInitialized(true);
return;
}
if (wasTriggered) {
return;
}
callback();
setWasTriggered(true);
}, [initialized, wasTriggered]);
};
While the hook looks like it works, it will trigger itself again by calling setInitialized(true) in the beginning, thus also triggering the callback.
You could remove the initialized value from the deps array and the hook would work for now - however this would cause an exhaustive-deps linting error. The hook might break in the future as it is not an "official" usage of the hooks api, e.g. with updates on the concurrent rendering feature that the React team is working on.
The solution below feels hacky. If there's no better alternative, I'm tempted to refactor my component into a class component to make use of the easy way class components allow you to execute code once state has been updated.
Anyway, my current solution is:
The useRef(arg) hook returns an object who's .current property is set to the value of arg. This object persists throughout the React lifecycle. (Docs). With this, we can record how many times the useEffect's callback has executed and use this info to stop code inside the callback from executing on initial execution and for a second time. For example:
initialExecution = useRef(true);
[color, setColor] = useState("red");
useEffect(() => {
setColor("blue");
});
useEffect(() => {
if (initialExecution.current) {
initialExecution.current = false;
return;
}
//code that executes when color is updated.
}, [color]);
I am building an online boardgame usin create-react-app, react hooks, and am using sockets.io to transmit data (player location, active player, etc.) between connected users. The flow of the logic is that a user makes a choice, that choice gets added to an array in state, and then the updated state is pushed via sockets to all connected users. The problem is that the useEffect listener that is in charge of receiving the socket data from the back end and updating the user data on each connected user is firing too many times instead of just once.
Code:
Send call to the back end:
try {
console.log(currentCard, typeof(currentCard.title), tech)
setUser1Data({
...user1Data,
userTech: [...user1Data.userTech, currentCard.title]
});
} finally {
console.log(user1Data)
socket.emit("p1state", user1Data);
pass();
}
The back end receiver/emitter:
socket.on("p1state", function(state) {
console.log(state)
io.emit("p1state", state)
})
The client listener:
useEffect(() => {
socket.on("p1state", state => {
console.log("1")
setUser1Data({...user1Data, state});
});
}, [user1Data]);
Some "interesting" things I noticed: this useEffect is being fired too many times. The first time it fires it sets everything the way it should, but then each subsequent time it overwrites the previous setting, reverting to the original user1Data state object.
Also, on the back end, I have a console.log firing when a client connects. Even though I am testing only locally with one browser tab at the moment, it is still logging several user connected events.
The useEffect is currently using the state in the dependency array and is setting the same state in the updater function. As you can see, this leads to an infinite loop.
useEffect(() => {
socket.on("p1state", state => {
console.log("1")
setUser1Data(userData => ({...userData, state}));
});
}, []);
Instead you can use the function version of state setter so that it gives you the accurate prevState instead of relying on state representation in closure.
I had a similar problem. I solved it making the useEffect close the socket every time it gets unmounted (and open/reopen after every mount/update. This was my code:
useEffect(()=>{
const socket = io("http://localhost:3000")
socket.on(userId, (arg) => {
//stuff
});
return () => socket.emit('end'); //close socket on unmount
})
For example, when I do the following, the callback gets called as soon as I call .listen():
let unlistener = ReactRouter.browserHistory.listen(route => {
// do something when the path changes
});
How do I prevent this from happening? Or, how can I get the current route's path so I can compare route parameter with the current route?
I don't want to make this function just ignore the first execution because that's too hacky. There could be times where the function isn't called immediately.
If you use redux, you can use react-router-redux (https://github.com/reactjs/react-router-redux). When you set it up, on each changes to the history there is a LOCATION_CHANGE action being dispatched.
The main advantage of adding a reducer to handle the LOCATION_CHANGE is that you do get the new path in the action's payload, and you have a reference to your current route in the store's state.