user3073511
user3073511

Reputation: 121

Component not re-rendering when context is updated

I have a component that uses the context, but does not re-render when the context is updated.

App.tsx

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

UserControl.tsx

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

Answers (1)

Drew Reese
Drew Reese

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.

Edit component-not-re-rendering-when-context-is-updated

enter image description here enter image description here

Upvotes: 2

Related Questions