Enable a listener when an application is started or in the foreground - reactjs

With react-native, AppState component allows to listen if an application is in the foreground (active) or in the background (background).
Its implementation is very simple :
useEffect(() => {
const appStateListener = AppState.addEventListener('change', appState => {
if (appState === 'active') {
console.log('App active')
} else {
console.log('App not active')
}
// !!!!! no console.log() at application startup, only when changing background/foreground !!!!!
})
return () => appStateListener.remove()
}, [])
react-native-mmkv allows to store values locally. It has a listener to detect changes :
useEffect(() => {
const storageListener = storage.addOnValueChangedListener((changedKey) => {
const newValue = storage.getString(changedKey)
console.log(`"${changedKey}" new value: ${newValue}`)
})
return () => storageListener.remove()
}, [])
But I don't know how to use both simultaneously. I would like to activate the react-native-mmkv listener when the application starts (useEffect()) AND when the application is in the foreground.
When the application is closed or in the background, I would like to remove this listener.
I tried this but I know it's not good, and the application crashes when the application goes from the background to the foreground ("Maximum call stack size exceeded").
useEffect(() => {
const enableStorageListener = () => {
return storage.addOnValueChangedListener((changedKey) => {
//...
})
}
let storageListener = enableStorageListener()
const appStateListener = AppState.addEventListener('change', appState => {
if (appState === 'active') {
storageListener = enableStorageListener()
} else {
storageListener.remove()
}
})
return () => {
appStateListener.remove()
storageListener.remove()
}
}, [])

Can you try to keep the AppState in a state
const [appState, setAppState] = useState("");
useEffect(() => {
const appStateListener = AppState.addEventListener("change", (appState) => {
if (appState === "active") {
setAppState("active");
console.log("App active");
} else {
setAppState("deactive");
console.log("App not active");
}
// !!!!! no console.log() at application startup, only when changing background/foreground !!!!!
});
return () => appStateListener.remove();
}, []);
useEffect(() => {
const storageListener = storage.addOnValueChangedListener((changedKey) => {
if (appState === "active") {
const newValue = storage.getString(changedKey);
console.log(`"${changedKey}" new value: ${newValue}`);
}
});
return () => storageListener.remove();
}, []);

To use both components, you can combine their listeners within a single useEffect hook, and conditionally enable/disable the storage listener based on the app state:
useEffect(() => {
let storageListener = null;
const enableStorageListener = () => {
storageListener = storage.addOnValueChangedListener((changedKey) => {
// ...
});
};
const appStateListener = AppState.addEventListener('change', (appState) => {
if (appState === 'active') {
enableStorageListener();
} else if (storageListener) {
storageListener.remove();
storageListener = null;
}
});
if (AppState.currentState === 'active') {
enableStorageListener();
}
return () => {
appStateListener.remove();
if (storageListener) {
storageListener.remove();
}
};
}, []);
This way, the storage listener is enabled when the app is active and disabled when it's in the background. The return statement in the hook is used to cleanup the listeners when the component is unmounted.

Related

react infinite scroll with IntersectionObserver not working

I don't know why the infinite scroll made of react doesn't work.
Here's what I think.
Connected Element Detection
isFetching = true
setTest()
const [isFetching, setIsFetching] = useState(false);
useEffect(() => {
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsFetching(true);
}
});
setIsFetching(false);
});
io.observe(document.getElementById("finish")!);
return () => io.disconnect();
}, []);
useEffect(() => {
if (!isFetching) return;
setTest([...test, 1]);
}, [isFetching, test]);```

React 17.0: passing down actions/events through component hierarchy

My component composition is as below and I want to call refresh(); of each component when the Refresh button is pressed in the Top Comp component.
I experimented with custom events. But I had problems (refresh method gets called multiple times) with it. I implemented the events as below:
EventBus.js:
const EventBus = {
on(event, callback) {
document.addEventListener(event, (e) => callback(e.detail));
},
dispatch(event, data) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event, callback) {
document.removeEventListener(event, callback);
},
};
TopComp.js:
const TopComp = () => {
const refresh = () => {
EventBus.dispatch("refresh", "");
}
return <button onClick={() => refresh()} >Refresh</button>
}
Level1Comp1.js:
const Level1Comp1 = () => {
const refreshData = () => {
// refresh logic
}
useEffect(() => {
EventBus.on("refresh", () => {
refreshData();
});
return () => {
EventBus.remove("refresh");
}
}, [])
}
I would like to know is there any other way of implementing this.

react/ state not being updated because of cleanup

I have custom hook that adds user info for posts created. And if I don't add cleanup it works as intended. I create new post, press post and it gets added to screen, but with if(mounted.current)setState() it does not update, only on refresh. What could be the problem and how could I fix it?
const AllPostsAssign = () => {
const { userPosts, allUsers } = useData();
const [posts, setPosts] = useState();
const mounted = useRef(true);
// map existing posts and add user object and post id into post object.
useEffect(() => {
const postsWithUsers = allUsers.map((y) => {
const usersAssignedToPosts = userPosts.map((x) => {
if (y.userId === x.data().userId) {
const q = Object.assign(x.data(), { id: x.id });
const z = Object.assign(q, { user: y });
return z;
}
});
return usersAssignedToPosts;
});
const noUndefined = postsWithUsers.flat().filter((post) => post);
// without mounted.current it works.
if (noUndefined && mounted.current) setPosts(noUndefined);
console.log(mounted.current);
console.log(posts);
return () => (mounted.current = false);
}, [userPosts, allUsers]);
// sort by time created and then reverse, so newest posts would be on top.
posts &&
posts.sort((a, b) => {
return a.createdAt.seconds - b.createdAt.seconds;
});
posts && posts.reverse();
return posts;
};
export default AllPostsAssign;
Have your mounted check declared directly inside your useEffect, as such:
useEffect(() => {
let mounted = true;
const postsWithUsers = allUsers.map((y) => {
const usersAssignedToPosts = userPosts.map((x) => {
if (y.userId === x.data().userId) {
const q = Object.assign(x.data(), { id: x.id });
const z = Object.assign(q, { user: y });
return z;
}
});
return usersAssignedToPosts;
});
const noUndefined = postsWithUsers.flat().filter((post) => post);
// without mounted.current it works.
if (noUndefined && mounted) setPosts(noUndefined);
console.log(mounted);
console.log(posts);
return () => (mounted = false);
}, [userPosts, allUsers]);

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()
});
};
}, []);
} ```

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