I have 2 screens in Stack Navigator.
All Categories,
Add a new category
In the All Categories screen, all categories are displayed.
useEffect(() => {
loadCategories();
}, []);
This is the useEffect hook that load all the categories.
I have made a touchable opacity to navigate to Add a new category screen, So users can easily add a new category if needed.
What I'm expecting to do: So after adding a new category and going back to the All Categories screen loadCategories() should run again, So the user can see the newly added category there. But the problem is when I add a new category and go back the loadCategories() function doesn't execute again. What will be the cause for this?
If you want to load categories when screen is focus
function Categories({ navigation }) {
React.useEffect(() => {
const loadCategories = navigation.addListener('focus', () => {
loadCategories();
});
return loadCategories;
}, [navigation]);
...rest
}
Often we need to fetch data when we come to some screens back,
For this, there is a hook from the #react-navigation/native library (useFocusEffect), which we can use to accomplish such requirements.
For Ex.
import { useFocusEffect } from '#react-navigation/native';
function Profile({ userId }) {
const [user, setUser] = React.useState(null);
useFocusEffect(
React.useCallback(() => {
const unsubscribe = API.subscribe(userId, user => setUser(user));
return () => unsubscribe();
}, [userId])
);
return <ProfileContent user={user} />;
}
In the above code, Whenever the ProfileContent screen is getting focused, the code inside the useFocusEffect will re-run.
Refer to official docs for in-depth understanding.
I found this answer which is relatable to this question.
https://stackoverflow.com/a/62703838/19317939
import {useIsFocused} from '#react-navigation/native';
...
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) loadCategories();
}, [isFocused]);
Related
I have AppBar component which I want to show currently logged in user. On the same page I have Profile component, which triggers login form. The problem is that they are not synched, obviously currentAuthenticatedUser inside AppBar is called before login is made and not called after, unless page is refreshed:
function AppBar() {
const [user, setUser] = useState();
useEffect(() => {
Auth.currentAuthenticatedUser()
.then((u) => {
setUser(u.username);
});
}, []);
return <div>AppBar: {user}</div>
}
function Profile(props: any) {
return <Authenticator>
{({ user, signOut }: any) => {
return <>
<div>Profile: { user.username }</div>
<button onClick={() => signOut()}>Sign Out</button>
</>
}}
</Authenticator>
}
function App() {
return <b><AppBar /><Profile /></b>
}
After login:
After page refresh:
How to display username in AppBar right after login without the need to refresh the page? Thanks!
You can achieve that functionality by either using local state or context
Local state:
setup your const [user, setUser] = useState(); hook on top of AppBar and Profile components. Then pass down user as prop on the AppBar component and pass down setUser on the Profile component, you would need to create an internal component inside Profile that would take this prop and then be rendered inside the Authenticator component.
Context:
It is a similar process as above but you need to use the React.createContext(<<context_data_here>>) syntax. I would suggest using this approach if your app need access to user on many components. If that is not the case using local state is recommended.
If you are not familiar with context I'd suggest watching this video
Solution borrowed from here
function AppBar() {
const [signedUser, setSignedUser] = useState<{username: string}>();
useEffect(() => {
authListener();
}, []);
async function authListener() {
Hub.listen("auth", (data) => {
switch (data.payload.event) {
case "signIn":
return setSignedUser(data.payload.data);
case "signOut":
return setSignedUser(undefined);
}
});
try {
const user = await Auth.currentAuthenticatedUser();
setSignedUser(user);
} catch (err) {}
}
return <div>AppBar: {signedUser?.username}</div>
}
There are 2 pages: the first loads an array of posts with a jsonplaceholder, the second displays the body of a specific post by clicking on the post.
When I return from the post page to the page with all the posts, the data is updated and re-requested.
How can I save the page state?
const [data, setData] = useState([])
const [posts, setPosts] = useState([])
const getPostsData = async () => {
try {
const postsData = await getData('https://jsonplaceholder.typicode.com/posts')
setData(postsData)
} catch (error) {
console.log(error.message)
}
}
useEffect(() => { getPostsData() }, [])
useEffect(() => { setPosts(data) }, [data])
posts.map(post => <Link to={`${post.id}`} key={post.id}><li className="list-group-item" >{post.title}</li></Link>)
I use 2 states. One is for loading data, and the second is for displaying. I need it for sorting and searching.
I think for your case it's better to use redux , context or react-query, but if you don't want to, you must avoid using Link for the child component because when you change route your previous state will be removed
you can simply show post detail as a modal or part of the main page
I've been looking for this question and found it but they're using class components and react router dom v5
What i want is
When user click browser back button I'll redirect them to home page
If you are simply wanting to run a function when a back navigation (POP action) occurs then a possible solution is to create a custom hook for it using the exported NavigationContext.
Example:
import { UNSAFE_NavigationContext } from "react-router-dom";
const useBackListener = (callback) => {
const navigator = useContext(UNSAFE_NavigationContext).navigator;
useEffect(() => {
const listener = ({ location, action }) => {
console.log("listener", { location, action });
if (action === "POP") {
callback({ location, action });
}
};
const unlisten = navigator.listen(listener);
return unlisten;
}, [callback, navigator]);
};
Usage:
import { useNavigate } from 'react-router-dom';
import { useBackListener } from '../path/to/useBackListener';
...
const navigate = useNavigate();
useBackListener(({ location }) =>
console.log("Navigated Back", { location });
navigate("/", { replace: true });
);
If using the UNSAFE_NavigationContext context is something you'd prefer to avoid then the alternative is to create a custom route that can use a custom history object (i.e. from createBrowserHistory) and use the normal history.listen. See my answer here for details.
Update w/ Typescript
import { useEffect, useContext } from "react";
import { NavigationType, UNSAFE_NavigationContext } from "react-router-dom";
import { History, Update } from "history";
const useBackListener = (callback: (...args: any) => void) => {
const navigator = useContext(UNSAFE_NavigationContext).navigator as History;
useEffect(() => {
const listener = ({ location, action }: Update) => {
console.log("listener", { location, action });
if (action === NavigationType.Pop) {
callback({ location, action });
}
};
const unlisten = navigator.listen(listener);
return unlisten;
}, [callback, navigator]);
};
Well after a long journey to find out how to do that finally i came up with this solution
window.onpopstate = () => {
navigate("/");
}
I came up with a pretty robust solution for this situation, just using browser methods, since react-router-v6's API is pretty sketchy in this department right now.
I push on some fake history identical to the current route (aka a buffer against the back button). Then, I listen for a popstate event (back button event) and fire whatever JS I need, which in my case unmounts the component. If the component unmounts WITHOUT the use of the back button, like by an onscreen button or other logic, we just clean up our fake history using useEffect's callback. Phew. So it looks like:
function closeQuickView() {
closeMe() // do whatever you need to close this component
}
useEffect(() => {
// Add a fake history event so that the back button does nothing if pressed once
window.history.pushState('fake-route', document.title, window.location.href);
addEventListener('popstate', closeQuickView);
// Here is the cleanup when this component unmounts
return () => {
removeEventListener('popstate', closeQuickView);
// If we left without using the back button, aka by using a button on the page, we need to clear out that fake history event
if (window.history.state === 'fake-route') {
window.history.back();
}
};
}, []);
You can go back by using useNavigate hook, that has become with rrd v6
import {useNabigate} from "react-router-dom";
const App = () => {
const navigate = useNavigate();
const goBack = () => navigate(-1);
return (
<div>
...
<button onClick={goBack}>Go back</button>
...
</div>
)
}
export App;
I used <Link to={-1}>go back</Link> and its working in v6, not sure if it's a bug or a feature but seems there is no error in console and can't find any documentation stating this kind of approach
You can try this approach. This worked for me.
import { useNavigate, UNSAFE_NavigationContext } from "react-router-dom";
const navigation = useContext(UNSAFE_NavigationContext).navigator;
const navigate = useNaviagte();
React.useEffect(() => {
let unlisten = navigation.listen((locationListener) => {
if (locationListener.action === "POP") {
//do your stuff on back button click
navigate("/");
}
});
return(() => {
unlisten();
})
}, []);
I'm on rrd#6.8 and testing John's answer worked for me right away for a simple "GO back 1 page", no useNavigate needed:
<Link to={-1}>
<Button size="sm">← Back </Button>
</Link>
So as a simple back button this seems to work without unexpected errors.
A react app using hooks. In useEffect there is an api-call to populate content on page.
Each element fetched is clickable. And a click will open details for the clicked element in a new browser window/tab.
When working in the new window, the user can make changes on that object. The user will then close that tab or at least just go back to the main window/tab.
Question is how I can detect that the user is coming back to the main window. This is because I want to re-fetch data from API. Thus, I want to rerun useEffect.
While googling I found this:
https://www.npmjs.com/package/react-page-visibility
Is that really what I'm looking for? Reading the docs I'm not really sure if that can be the solution to my issue. Is there another way to solve this?
You can use the visibilitychange event to achieve that:
const onVisibilityChange = () => {
if (document.visibilityState === 'visible') {
console.log("Tab reopened, refetch the data!");
}
};
useLayoutEffect(() => {
document.addEventListener("visibilitychange", onVisibilityChange);
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
}, []);
Codesandbox
With React 18 you can use 'visibilitychange' with react brand new hook useSyncExternalStore
export function useVisibilityChange(serverFallback: boolean) {
const getServerSnapshot = () => serverFallback;
const [getSnapshot, subscribe] = useMemo(() => {
return [
() => document.visibilityState === 'visible',
(notify: () => void) => {
window.addEventListener('visibilitychange', notify);
return () => {
window.removeEventListener('visibilitychange', notify);
};
},
];
}, []);
Gist with hook
P.S:Don't forget cross-browser usage
I have a project that needs to show a register modal when the user is null,
it works some times??
I am asking a state element if user exists, then showing the modal, the problem is that when the user is logged in, when the page is shown, sometimes it shows the register modal,
like it renders and then checks if user === null
Is that my problem?
NOTE: on other tabs, this works fine, like it had more time to load the state?
const mapStateToProps = ({ firebase, navigation }) => ({
firebase,
})
function Feed ({ feed, firebase }) {
React.useEffect(
() => navigation.addListener('focus', () =>
{
console.log("aki:: ",firebase.user)
if (firebase.user === null) {
//SHOW MODAL TO INVITE REGISTER
setVisible(true),
navigation.dispatch(navigateToBrowse())
} else {
setVisible(false)
}
}
),
[]
);
It's likely because the firebase.user prop isn't set before this component is rendered. It's impossible to tell without seeing the parent component.
You can block the tree from rendering until firebase.user exists.
Otherwise, you have to differentiate between 1. auth is loading, 2. auth is done loading and user doesn't exist, and 3. auth is done loading and user exists.
I initially thought it was because of how you were handling the navigation side effect, so here's that code anyways:
function Feed({ feed, firebase }) {
const [visible, setVisible] = useState<boolean>(false);
React.useEffect(() => {
const checkForUser = () => {
setVisible(firebase.user === null);
}
// Check for user on all focus events
navigation.addListener('focus', checkForUser);
// Also check for user immediately
checkForUser();
}, []);
React.useEffect(() => {
// Only navigate away when visible gets turned on
if (visible) navigation.dispatch(navigateToBrowse());
}, [visible])
}