Search code examples
reactjsreact-routerauthorizationreact-typescript

React with TypeScript - Authorization and component visibility depending on user claims


I am working on a react application.

I am trying to create login and register functionality.

I have a Authorized.tsx component which looks like this

export const Authorized = (props: authorizedProps) => {
  const [isAuthorized, setIsAuthorized] = useState(true);
  const { claims } = useContext(AuthContext);

  useEffect(() => {
    if (props.role) {
      const index = claims.findIndex(
        claim => claim.name === 'role' && claim.value === props.role)
      setIsAuthorized(index > -1);
    } else {
        setIsAuthorized(claims.length > 0);
    }
  }, [claims, props.role]);

  return (
    <>
        {isAuthorized ? props.authorized : props.notAuthorized}
    </>
    );
};

interface authorizedProps {
  authorized: ReactElement;
  notAuthorized?: ReactElement;
  role?: string;
}

This component hides and shows diffrent kind of components depending on if the user is authorized or not.

I am using this component to only show the Login.tsx component for users that are not logged in. I dont want anyone who is not logged in to be able to visit the website.

In my Index.tsx I am using the Authorized.tsx component like this

const Index = () => {
  const [claims, setClaims] = useState<claim[]>([
    // { name: "email", value: "test@hotmail.com" },
  ]);

  return (
    <div>
      <BrowserRouter>
        <AuthContext.Provider value={{ claims, update: setClaims }}>
          <Authorized authorized={<App />} notAuthorized={<Login />} />
        </AuthContext.Provider>
      </BrowserRouter>
    </div>
  );
};

All the authorized users will be able to visit the site, everyone else will be asked to log in.

However, the problem I have is when I tried adding the Register.tsx component into the Login.tsx component as a navigational link.

I wish to be able to navigate between Register and Login

This is how the Login.tsx component looks like

export const Login = () => {

  return (
    <>
      <h3>Log in</h3>
      <DisplayErrors errors={errors} />
      <AuthForm
        model={{ email: "", password: "" }}
        onSubmit={async (values) => await login(values)}
        BtnText="Log in" />
          <Switch>
            <Route path="/register">
              <Register />
            </Route>
            <Link to='/register'>Register</Link>
          </Switch>
    </>
  );
};

But what actually happends when I press the 'Register' link is that the Register component gets added below the Login component

Before pressing the 'Register' link

enter image description here

After pressing the 'Register' link

enter image description here

I understand it has something to do with the Authorized.tsx component in Index.tsx. That I am telling it to only show the Login component when not authorized.

But I dont know how I could fix it so I will be able to navigate between the Login and the Register

All help I could get would be much appreciated!

Thanks


Solution

  • With the current implementation you are rendering a Login component that then also renders a route for a Register component to be rendered on. Login remains mounted and rendered the entire time. From what you describe you want to render Login and Register each on their own route.

    Abstract both these components into a parent component that manages the route matching and rendering.

    Example

    const Unauthenticated = () => (
      <Switch>
        <Route path="/register" component={Register} />
        <Route component={Login} />
      </Switch>
    );
    

    ...

    export const Login = () => {
      ...
    
      return (
        <>
          <h3>Log in</h3>
          <DisplayErrors errors={errors} />
          <AuthForm
            model={{ email: "", password: "" }}
            onSubmit={login}
            BtnText="Log in"
          />
          <Link to='/register'>Register</Link>
        </>
      );
    };
    

    ...

    const Index = () => {
      const [claims, setClaims] = useState<claim[]>([
        // { name: "email", value: "test@hotmail.com" },
      ]);
    
      return (
        <div>
          <BrowserRouter>
            <AuthContext.Provider value={{ claims, update: setClaims }}>
              <Authorized
                authorized={<App />}
                notAuthorized={<Unauthenticated />}
              />
            </AuthContext.Provider>
          </BrowserRouter>
        </div>
      );
    };