Outdated react state in pusher .bind method - reactjs

I use pusher-js to receive data from the backend.
I configure it in useEffect like this:
useEffect(() => {
const pusher = new Pusher('my_app_id', {
cluster: 'us3',
});
const channel = pusher.subscribe('messages');
channel.bind('send-message', (data) => {
});
}, []);
In the callback of the .bind method, I want to access the react state. The problem is, that if it gets updated, this callback still has the old version.
channel.bind('send-message', (data) => {
// here is my outdated state
});
How can I acces new, updated state inside this callback?
Thanks in advance

Use another useEffect with the updated state in your dependency array of the useEffect,as soon as the state gets updated that useEffect will. be triggered and inside it you can access the updated state.

I was stuck on the same problem for quite a long time. The way I finally solved this is to store the channel and rebind the event every time the state (which I want to access in the bind callback) changed. Here is a code snippet to help you understand better.
VERY IMPORTANT - Do not forget to unbind the event from the channel before rebinding it. As rebinding without unbinding prior binds would just create additional listeners for the event and all the listeners will fire when the event occurs and it will be a mess. Learnt it the hard way :")
Don't know if this is the best method but worked for me.
const [pusherChannel, setPusherChannel] = useState(null);
const [data, setData] = useState(null);
// TRIGGERED ON MOUNT
useEffect(() => {
const pusher = new Pusher(APP_KEY, {
cluster: APP_CLUSTER
});
const channel = pusher.subscribe(CHANNEL_NAME);
setPusherChannel(channel);
// PREVIOUSLY
// channel.bind(EVENT_NAME, (pusherData) => {
// ...
// Accessing "data" here would give the state used
// during binding the event
//});
}, []);
// TRIGGERED ON CHANGE IN "data"
useEffect(() => {
console.log("Updated data : ", data);
if(pusherChannel && pusherChannel.bind){
console.log("Unbinding Event");
pusherChannel.unbind(EVENT_NAME);
console.log("Rebinding Event");
pusherChannel.bind(EVENT_NAME, (pusherData) => {
// USE UPDATED "data" here
}
}
}, [pusherChannel, data]);
Reference -
Binding Events
Unbinding Events

Related

useEffect with a dependency that changes too fast

Here's the problem:
function Foo(){
let [events, setEvents] = useState([])
useEffect(() => {
let subscription = client.subscribe("eventType", event => {
setEvents([...events, event])
})
return () => subscription.unsubscribe()
}, [events])
}
So here if I make events a useEffect dependency it will unsubscribe / subscribe every time an event is added, and I will miss some. I can make events a ref, but then it won't re-render.
What's the right way to do this?
Edit: From the comments / answers I've gotten so far there seems to be no good way to deal with this in React. I have an ugly setInterval solution that I will post as an answer if nobody comes up with something better within 2 days.
EDIT 2: Here's a codesandbox showing the issue with setInterval. I'm looking for a solution that doesn't require events as a dependency (removing events dependency is not a solution, obviously.)
Instead of grabbing events from outside the useEffect, you can just pass a callback to setEvents that will receive the current value for events:
function Foo(){
let [events, setEvents] = useState([])
useEffect(() => {
let subscription = client.subscribe("eventType", event => {
setEvents((previousEvents) => [...previousEvents, event])
})
return () => subscription.unsubscribe()
}, []) // no need to add `events` to dependencies because we're getting it from `setEvent`'s callback
}

How to access state inside custom event listener

I have spent time studying the details of other posts about this subject yet there is something I still do not understand. I am hoping I can receive some assistance to see this more clearly. Specifically, I would like to learn how to obtain the current state value inside a custom event handler. Here is some simplified code:
const modal_state_ref = React.useRef(modal_state);
React.useEffect(() => {
modal_state_ref.current = modal_state;
}, [modal_state]);
React.useEffect(() => {
const listener = () => {
console.log(`state in handler is ${modal_state_ref.current}`);
};
window.addEventListener('customEvent-modal-opening', listener);
return () => {
window.removeEventListener('customEvent-modal-opening', listener);
}
}, []);
I believe I understand about closures and how the value in the event handler is from a "snap shot" of the state at the time the event handler is created. I thought, however, that the ref, being an object, would always be reference to the location in memory, so always have the updated value. I'm at a lost for how to proceed.

How to update the unmount handler in a useEffect without firing it repeatedly?

Another developer came to me with a tricky question today. She proposed the following:
A.) She wants to fire an event whenever a modal closes.
B.) She does not want the event to fire at any other time.
C.) The event must be up to date with state of the component.
A basic example is like so:
const ModalComponent = () => {
const [ eventData, setEventData ] = useState({ /* default data */ });
useEffect(() => {
return () => {
// happens anytime dependency array changes
eventFire(eventdata);
};
}, [eventData]);
return (
<>
// component details omitted, setEventData used in here
</>
);
};
The intention is that when the modal is closed, the event fires. However, user interactions with the modal cause state updates that change the value of eventData. This leads to the core of the problem:
Leaving eventData out of the useEffect dependency array causes it to fire only at time of modal closing, but the value is stale. It's the value that it was at the time the component mounted.
Placing eventData in the useEffect dependency array causes the event to fire over and over, whenever the data changes and the component re-renders and updates useEffect.
What is the solution to this? How can you access up-to-date data yet only act on it at time of unmounting?
Store eventData in a ref.
const [eventData, setEventData] = useState({ /* default data */ });
const ref = useRef();
ref.current = eventData;
useEffect(() => () => eventFire(ref.current), []);
This will keep the value always up to date since it won't be locked into the function closure and will remove the need for triggering the effect every time eventData changes.
Also, you can extract this logic into a custom hook.
function useStateMonitor(state) {
const ref = useRef();
ref.current = state;
return () => ref.current;
}
And usage would be like this
const [eventData, setEventData] = useState({ /* default data */ });
const eventDataMonitor = useStateMonitor(eventData);
useEffect(() => () => eventFire(eventDataMonitor()), []);
Here's an working example

React Hooks Firebase - useEffect hook not returning any data

I am trying to use the useEffect hook in React to listen for changes in a location in firestore.
Initially I didn't have an empty array as the second prop in the useEffect method and I didn't unsubscribe from the onSnapshot listener. I received the correct data in the projects variable after a short delay.
However, when I experienced extreme performance issues, I added in the unsubscribe and empty array which I should have put in earlier. Strangely, now no data is returned but the performance issues are gone.
What might be preventing the variable updating to reflect the data in firestore?
function useProjects(organisation) {
const [projects, setProjects] = useState({
docs: []
});
useEffect(() => {
if (!organisation.docs[0]) return;
const unsubscribe = firebase.firestore().collection('organisations').doc(organisation.docs[0].id).collection("projects").onSnapshot(snapshot => {
setProjects(snapshot);
});
return () => unsubscribe()
}, []);
return projects
};
const projects = useProjects(organisation);
You'll need a value in the dependency array for the useEffect hook. I'd probably suggest the values you are using in the useEffectHook. Otherwise with [] as the dependency array, the effect will only trigger once (on mount) and never again. The point of the dependency array is to tell react to re run the hook whenever a dependency changes.
Here's an example I'd suggest based on what's in the hook currently (using the id that you send to firebase in the call). I'm using optional chaining here as it makes the logic less verbose.
function useProjects(organisation) {
const [projects, setProjects] = useState({
docs: []
});
useEffect(() => {
if (!organisation.docs[0]) return;
const unsubscribe = firebase.firestore().collection('organisations').doc(organisation.docs[0].id).collection("projects").onSnapshot(snapshot => {
setProjects(snapshot);
});
return () => unsubscribe()
}, [organization.docs[0]?.id]);
return projects
};

Listener does not trigger useEffect with dependency array

The goal here is to change table data when data is modified in Firebase
The issue is that for some reason, within the scope of firebase listener, setState(...) does not trigger the useEffect(()=> {}, [...]) with the appropriate dependencies.
Any idea why, and is there a way to force the change?
const [actions, setActions] = useState([]);
....
firebase.
.firestore()
.collection("collection_name")
.onSnapshot(s => {
if (!s.empty) {
s.docChanges().forEach(change => {
if (change.type === "modified") {
const newActions = [...actions, change.doc.data()]
setActions(newActions) //The change
console.log("arrived here")
}...
...
useEffect(() => {
console.log("change in actions"); // Does not triggered on when modified
}, [actions]);
This does work without the dependency array:
useEffect(() => {
console.log("This does work")
});
I think you should look other your references.
The thing with react hook is that if the reference of the object before and after the setter is the same, the useEffect listening to this object wont trigger.
Meaning that if in newActions = ... you use the reference leading to actions to update the value of action and then setActions() the reference of the object stay the same, instead you should try to make a copy of actions and then modify this independent copy. You can then setActions(modifiedCopy) and this will normally trigger the useEffect.
note: that's only a guess cause i can't know what you putted behind newActions = ...

Resources