Reputation: 121
I have a component that uses the context, but does not re-render when the context is updated.
const AppContext = createContext({
isLoggedIn: false,
hasUserLoggedInBefore: false,
});
const useAppContext = () => useContext(AppContext);
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [hasUserLoggedInBefore, setHasUserLoggedInBefore] = useState(
false
);
useEffect(() => {
// to set up initial state
asyncApiCall
.then((result) => {
if (result["user"]) {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
if (result["hasUserLoggedInBefore"]) {
setHasUserLoggedInBefore(true);
} else {
setHasUserLoggedInBefore(false);
}
});
// to listen for changes
// this does not seem to re-render <UserControl />
listenToOnAuthChangeEvent((user) => {
setIsLoggedIn(true);
setHasUserLoggedInBefore(true);
});
});
return (
<AppContext.Provider
value={{
isLoggedIn,
hasUserLoggedInBefore,
}}
>
<div className="App">
<Routes>
<Route element={<UserControl />}>
<Route path="/" element={<Functionality />} />
</Route>
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
</Routes>
</div>
</AppContext.Provider>
);
}
And component that uses the context
function UserControl() {
const { isLoggedIn, hasUserLoggedInBefore } = useAppContext();
if (!isLoggedIn) {
return (
<Navigate to={hasUserLoggedInBefore ? "/login" : "/signup"} />
)
}
return (
<Outlet />
)
}
The async API call will fetch the data. If the user is not logged in, display either the sign up or login page based on whether they have ever logged in in the past. If they are logged in, display <Functionality />
.
From what I can tell, after the API call succeeds and the set
methods are called, the UserControl
component does not re-render to display <Functionality />
, and the <Signup />
component is displayed again.
Upvotes: 2
Views: 1166
Reputation: 203373
Updating the codesandbox from your previous question to use your useEffect
and asyncApiCall
call what I see happen is the user starts on the home path "/"
, and since they are not authenticated they get redirected to "/signup"
page. Later the context state updates and you have to navigate back to "/"
if you want to see the Functionality
component. This is probably the typical use case where the app is making redirection decisions prior to the first authentication call, the solution is to use a third indeterminant "state" to hold off on redirecting or rendering the outlet.
Change the initial isLoggedIn
and hasUserLoggedInBefore
state to undefined.
const [isLoggedIn, setIsLoggedIn] = useState();
const [hasUserLoggedInBefore, setHasUserLoggedInBefore] = useState();
Update the UserControl
auth wrapper to check this specific undefined case and render a loading indicator or null.
function UserControl() {
const { isLoggedIn, hasUserLoggedInBefore } = useAppContext();
// ... snip unrelated code
if (isLoggedIn === undefined) return "... Checking Auth ...";
if (!isLoggedIn) {
return (
<Navigate to={hasUserLoggedInBefore ? "/login" : "/signup"} replace />
);
}
return <Outlet />;
}
This allows the app to wait for the auth status to populate into state before rendering the redirect to either of the login or signup routes, or the Outlet
for the protected component.
Upvotes: 2