HowTo: Unsubscribe from Apollo (subscribeToMore) subscription? - reactjs

So I have a subscription as follows that I wish to unsubscription cleanup on:
<DeleteItem
id={item.id}
urlReferer={urlReferer}
subscribeToDeleteItems={() =>
subscribeToMore({
....
}
})
}
>Delete This Item</DeleteItem>
DeleteItem.js
const DeleteItem = props => {
const {urlReferer} = props;
useEffect(() => {
props.subscribeToDeleteItems();
},[urlReferer]);
...
}
...
}
How would I do this?

So I achieved cleanup by calling an abortController:
useEffect(() => {
const abortController = new AbortController();
props.subscribeToDeleteItems();
return () => {
abortController.abort();
};
},[urlReferer]);

If your effect returns a function, React will run it when it is time to clean up. SubscribeToMore returns the unsubscribe function.
const subscribeToNewComments = () =>
subscribeToMore({
document: MESSAGES_SUBSCRIPTION,
variables: { channelId },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData) return prev;
return Object.assign({}, prev, {
channelMessages: [
subscriptionData.data.messageAdded,
...prev.channelMessages,
],
});
},
});
React.useEffect(() => {
let unsub = subscribeToNewComments();
return () => {
unsub();
};
}, [channelId]);

Related

react state not updating inside callabck

I'm not understanding why the following code, the callback onSocketMessage is not using the new acquisition state. inside the useEffect the state is correctly updated, but the function is not evaluated again...i've also tryed using useCallback with acquisition as dependency but nothing changed.
const Ac = () => {
const [acquisition, setAcquisition] = useState({ data: {} })
const [loading, setLoading] = useState(true)
const socket = useRef(null);
const onSocketMessage = (message) => {
console.log(acquisition) // this is always initial state
let { data } = acquisition
data.input[message.index] = message.input
setAcquisition(prevState => ({ ...prevState, data }));
}
useEffect(() => {
fetchCurrentAcquisition(acquisition => {
setAcquisition(acquisition)
setLoading(false)
socket.current = newSocket('/acquisition', () => console.log('connected'), onSocketMessage);
})
return () => socket.current.disconnect()
}, [])
console.log(acquisition)
You are logging a stale closure you should try the following instead:
const onSocketMessage = useCallback((message) => {
setAcquisition((acquisition) => {
//use acquisition in the callback
console.log(acquisition);
//you were mutating state here before
return {
...acquisition,
data: {
...acquisition.data,
input: {
//not sure if this is an array or not
//assimung it is an object
...acquisition.data.input,
[message.index]: message.input,
},
},
};
});
}, []); //only created on mount
useEffect(() => {
fetchCurrentAcquisition((acquisition) => {
setAcquisition(acquisition);
setLoading(false);
socket.current = newSocket(
'/acquisition',
() => console.log('connected'),
onSocketMessage
);
});
return () => socket.current.disconnect();
//onSocketMessage is a dependency of the effect
}, [onSocketMessage]);

Registering back button events not working in react hooks

Not getting console when the event happens.i tried a lot, not able to find the issue.Able to get console when i comment the code for remove listner .
const MyComponent = props => {
const onPopState = event =>{
console.log(event)
}
useEffect(() => {
window.addEventListener('popstate', onPopState);
return () => {
window.removeEventListener('popstate', onPopState);
};
}, []);
}
You can try this
const MyComponent = props => {
const onPopState = event =>{
console.log(event)
}
useEffect(() => {
window.addEventListener('popstate', () => {
onPopState()
});
return () => {
window.removeEventListener('popstate', () => {
onPopState()
});
};
}, []);
} ```

Update redux store with useEffect

I have an application required to run API calls every 3 seconds. I used useInterval to call API, every 3 seconds I received the API result. When I update from redux, something went wrong with useInterval.
UseInterval
export default function useInterval(callback, delay, immediate = true) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
(async() => {
async function tick() {
await savedCallback.current();
}
if (delay !== null) {
if (immediate) {
await tick();
}
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
})();
}, [delay]);
}
Main
const enhance = connect(
(state, ownProps) => ({
modal: state.modal[ownProps.id]
}),
{ updateFromRedux }
);
const container = ({ id, modal, updateFromRedux }) => {
useInterval(() => {
# -----> This scope of codes went wrong when updateFromRedux is called <-----
let modalId = modal["id"]
return fetch(`https://api-url.com/${modalId}`)
.then(res => res.json())
.then(
(result) => {
updateFromRedux(id, result)
}
)
}, 3000)
})
export default enhance(container);
Redux
export const updateFromRedux = (id, details) => ({
type: UPDATE_DETAILS,
payload: { id, details }
});
Problem
The modalId produces an inconsistent output such as undefined inside useInterval after updateFromRedux redux method is called.

How to translate componentDidMound and componentWillUnmount to UseEffect (React Hooks) with Firebase/Firestore?

How would one go about using the useEffect hook to replace both componentDidMount and componentWillUnmount while working with Firebase? I can't find a solution to this 'unsubscribe' function.
unsubscribe = null;
componentDidMount = async () => {
this.unsubscribe = firestore.collection('posts').onSnapshot(snapshot => {
const posts = snapshot.docs.map(...)
this.setState({ posts })
})
}
componentWillUnmount = () => {
this.unsubscribe()
}
Here's what I tried:
useEffect(() => {
async function getSnapshot() {
const unsubscribe = firestore.collection('posts').onSnapshot(snapshot => {
const posts = snapshot.docs.map(...)
setPosts(posts)
}
getSnapshot()
//return something to clear it? I don't have access to 'unsubscribe'
}, [])
You are actually pretty close with your answer. You weren't using await in your function, so there was no point in using it.
useEffect(() => {
const unsubscribe = firestore.collection('posts').onSnapshot((snapshot) => {
const posts = snapshot.docs.map(...)
setPosts(posts);
});
return () => {
unsubscribe();
};
}, []);
If you did need to use async, you can just utilize the closure to get unsubscribe out of the async function.
useEffect(() => {
let unsubscribe;
async function getSnapshot() {
unsubscribe = firestore.collection('posts').onSnapshot((snapshot) => {
const posts = snapshot.docs.map(...)
setPosts(posts);
});
}
getSnapshot();
return () => {
unsubscribe();
};
}, []);
you're probably going to run into trouble using async inside useEffect, check out https://www.npmjs.com/package/use-async-effect
useAsyncEffect( async() => {
const unsubscribe = await firestore.collection('posts').onSnapshot(snapshot => {
const posts = snapshot.docs.map(...)
setPosts(posts)
}
return () => {
console.log("unmount")
unsubscribe()
};
}, [])
EDIT: actually it seems from the docs that you don't need async at all there:
have you tried this format?
useEffect(
() => {
const unsubscribe = firebase
.firestore()
.collection('recipes')
.doc(id)
.collection('ingredients')
.onSnapshot( snapshot => { const ingredients = [] snapshot.forEach(doc => { ingredients.push(doc) }) setLoading(false) setIngredients(ingredients) }, err => { setError(err) } )
return () => unsubscribe()
},
[id]
)

React Hooks: best practice to get user authenticated

I am changing a React app from class based to function based. In the based class the declaration of listeners are in the lifecycle method componentDidMount():
componentDidMount() {
this.getNotes()
Auth.currentAuthenticatedUser().then(user => {
this.setState({user: user});
this.createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner:this.state.user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
const prevNotes = this.state.notes.filter(note => note.id !== newNote.id)
const updatedNotes = [...prevNotes, newNote]
this.setState({ notes: updatedNotes })
}
})...
To unsubscribe the listener I use the lifecycle method:
componentWillUnmount(){
this.createNoteListener.unsubscribe()
Changing to a function based class the listener declaration is like this:
useEffect(() => {
getNotes()
Auth.currentAuthenticatedUser().then(user => {
const createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner: user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id)
const updatedNotes = [...oldNotes, newNote]
return updatedNotes
})
setNote("")
}
............
return () => {
createNoteListener.unsubscribe() //the error is here
}
I am getting an erro saying: 'createNoteListener' is not defined.
Since I need the authenticated user to create the listener, how/where should I get/set the user before declaring the listener?
Thank you all!
createNoteListener is defined in different scope.
Can you try this?
useEffect(() => {
getNotes()
let createNoteListener = null;
Auth.currentAuthenticatedUser().then(user => {
createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner: user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id)
const updatedNotes = [...oldNotes, newNote]
return updatedNotes
})
setNote("")
}
}
)}
)
return () => {
createNoteListener.unsubscribe() //the error is here
}
}
)
I think you need to provide an extra argument to the useEffect Hook
You can do this
useEffect(() => {
getNotes()
Auth.currentAuthenticatedUser().then(user => {
const createNoteListener = API.graphql(graphqlOperation(onCreateNote, { owner: user.username })).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id)
const updatedNotes = [...oldNotes, newNote]
return updatedNotes
})
setNote("")
}
............
return () => {
createNoteListener.unsubscribe()
}, []) // provide empty array as second argument
This is because you want to subscribe only once.
Hope it helps

Resources