Skip to content

React Frontend Application

Clément Creusat edited this page Jul 16, 2024 · 3 revisions

Check user's rights

Inside each new application made with edifice-react-boilerplate, you have access to a basic rights store with zustand

import { RightRole } from 'edifice-ts-client';
import { createStore, useStore } from 'zustand';

type UserRights = Record<RightRole, boolean>;

/**
 * https://doichevkostia.dev/blog/authentication-store-with-zustand/
 */

/**
 * Basic store for managing "rights" array
 * Use this store with `checkUserRight` utils
 * You can check rights in a react-router loader
 * And set userRights with the store to get a stable global state
 * 
 * const userRights = await checkUserRight(rights);
  const { setUserRights } = useUserRightsStore.getState();
  setUserRights(userRights);
 */
interface State {
  userRights: UserRights;
}

type Action = {
  actions: {
    setUserRights: (userRights: UserRights) => void;
  };
};

type ExtractState<S> = S extends {
  getState: () => infer T;
}
  ? T
  : never;

type Params<U> = Parameters<typeof useStore<typeof store, U>>;

const initialState = {
  userRights: {
    creator: false,
    contrib: false,
    manager: false,
    read: false,
  },
};

const store = createStore<State & Action>()((set, get) => ({
  ...initialState,
  actions: {
    setUserRights: (userRights: UserRights) => set(() => ({ userRights })),
  },
}));

// Selectors
const userRights = (state: ExtractState<typeof store>) => state.userRights;
const actionsSelector = (state: ExtractState<typeof store>) => state.actions;

// Getters
export const getUserRights = () => userRights(store.getState());
export const getUserRightsActions = () => actionsSelector(store.getState());

// React Store
function useUserRightsStore<U>(selector: Params<U>[1]) {
  return useStore(store, selector);
}

// Hooks
export const useUserRights = () => useUserRightsStore(userRights);
export const useUserRightsActions = () => useUserRightsStore(actionsSelector);

Best way to check rights is inside a react-router's loader:

  • First, you need to fetch a resource to have access to rights array
  • Then, you can pass this data to the checkUserRight utility
  • Finally, send userRights to the store
export const loader = (queryClient: QueryClient) => async ({params, request}): LoaderFunctionArgs) => {
  
  // Fetch resource rights
  const app = await queryClient.ensureQueryData(queryOptions(params.id));

  const userRights = await checkUserRight(app.rights);
    const { setUserRights } = getUserRightsActions();
    setUserRights(userRights);
}

Then you can use the store in any component or re-export via a custom hook to add other methods that manipulate rights.

  1. Direct access
const userRights = useUserRights();
  1. Through a custom hook
export const useAccessStore = () => {
  const { user } = useUser();

  const userRights = useUserRights();

  const hasRightToDoSomething = () => {
    const right = userRights.creator ||
      userRights.manager
    return right;
  };

  const hasRightToDoSomethingElse = (item: value) => {
    const right = (userRights.creator ||
      userRights.manager ||
      (userRights.contrib &&
        item?.owner?.userId.includes(user?.userId as string))) as boolean;

    return right;
  };

  return { userRights, hasRightToDoSomething, hasRightToDoSomethingElse };
};
Clone this wiki locally