Search code examples
reactjsreact-router-dom

react-router - idiomatic way to place a User component and pass props, and should I use context?


I am a react newbie using react-router like this:

index.tsx

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />, // Uses <Outlet />.
    children: [
      {
        path: '/login/callback',
        element: <Login />,
      },
      {
        path: '/dashboard',
        element: <Dash />,
      },
    ],
  },
]);

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

Question

I need to add a new component to hold user information (whether there's a user logged in, and if so, their name and token).

Where would I place it and how would I use it given that:

  1. A login callback needs to be handled by <Login />, where the user will be set.
  2. The current user will be displayed inside the Root component. If there is no current user, a login button will be rendered instead.

What I have considered

interface User {...}

function UserInfo(): JSX.Element {
    const [user, setUser] = useState<User>({});

    ...
}

and have setUser be passed as a prop to <Login />, while the <UserInfo /> component would be used inside <Root /> (eg to provide the user name).

I am wondering how I could accomplish that in a good way.

On the one hand, I think I should lift state up and move the useState hook outside of UserInfo and into index.tsx.

I took a look at the react-router docs but I couldn't find a matching example.

I am not sure using a context would help here, but maybe could be placed inside Root:

<Container sx={{ mt: 4, mb: 4 }}>
    <UserInfo>
        <div id="detail">
            <Outlet />
        </div>
    </UserInfo>
</Container>

Solution

  • From what I can understand of your question you are wanting to store and expose out to various routed content a user state. If any component other than UserInfo needs this data then lifting state up is about the only viable solution. Promoting it to the Root component makes sense since it can reference it and expose it out to it's sub-ReactTree via the Outlet context it is already rendering/providing. Descendent components would use the useOutletContext hook to access the provided value.

    Example:

    const Root = () => {
      const [user, setUser] = useState<User>({});
    
      ...
    
      return (
        <Container sx={{ mt: 4, mb: 4 }}>
          <UserInfo user={user} />
          <div id="detail">
            <Outlet context={{ user, setUser, /* anything else */ }} />
          </div>
        </Container>
      );
    };
    
    interface UserContext {
      user: User;
      setUser: React.Dispatch<React.SetStateAction<User>>;
      // ...anything else
    }
    
    const Login = () => {
      ...
    
      const { setUser } = useOutletContext<UserContext>();
    
      ...
    };
    

    Edit react-router-idiomatic-way-to-place-a-user-component-and-pass-props-and-shoul