skip to Main Content

In my TS React App, I am using Zustand to manage the states of all dialogs/modals.

Here is how I typed the ModalStoreState:

type ModalStoreState = {
   /**
    * 1st arg is "name" of the target dialog, while 2nd arg is "data" corresponding with it.
    * 
    * How should I type "data" in a way that when I create a getter fn, the type will be inferred correctly?
    */
   opened: [string, any][]; 
};

While here is how I typed the ModalStoreActions:

type ModalStoreActions = {
   open: (data: [string, any]) => void;
   close: (name: string) => void;
   /**
    * Here is the getter fn, how do I type this one that it will infer the type of the object provided on open?
    */
   getData: (name: string) => ...
};

export const useModalStore = create<ModalStore>((set, get) => ({
   ...initialState,
   open: (data) => set((state) => ({ opened: state.opened.concat(data) })),
   close: (name) => set((state) => ({ opened: state.opened.filter((x) => x[0] !== name) })),
   getData: (name) => {
      /**
       * This is how I assumed the getter fn, would be implemented. :D
       */
      const opened = get().opened.find((x) => x[0] === name);

      if (!opened) {
         return null;
      }

      return opened[1];
   },
}));

Is it possible to infer the type of the "data" provided during "open" fn, considering that I should only be able to input either "object" or "null" type?

2

Answers


  1. You can leverage TypeScript’s generics and conditional types.

    Define a type that ensures the data can only be of type object or null:

    type ModalData = object | null;
    

    Use generics to type ModalStoreState and ModalStoreActions.

    type ModalStoreState<T extends ModalData> = {
       opened: [string, T][];
    };
    
    type ModalStoreActions<T extends ModalData> = {
       open: (data: [string, T]) => void;
       close: (name: string) => void;
       getData: (name: string) => T | null;
    };
    

    Finally, apply this generic type when creating the Zustand store:

    export const useModalStore = create<ModalStoreState<ModalData> & ModalStoreActions<ModalData>>((set, get) => ({
       opened: [],
       open: (data) => set((state) => ({ opened: state.opened.concat(data) })),
       close: (name) => set((state) => ({ opened: state.opened.filter((x) => x[0] !== name) })),
       getData: (name) => {
          const opened = get().opened.find((x) => x[0] === name);
          return opened ? opened[1] : null;
       },
    }));
    

    ModalData restricts the type of data to be either an object or null.

    Generics (T) allow the getData to infer the correct type of data based on what was provided to open.

    The return type of getData will be inferred correctly as either the type of data or null.

    Login or Signup to reply.
  2. To ensure strong typing and type inference in Zustand for managing modals/dialogs, you can use TypeScript’s advanced types and conditional types. The goal is to make sure that the data parameter passed to the open function and retrieved by the getData function are correctly typed and consistent.

    Here’s how you can achieve this:

    1. Define Types for Modal Data
    First, define a type that describes the data you expect for each modal. You can use a TypeScript interface or a type alias to specify what kind of data each modal should hold.

    type ModalData = {
      [key: string]: any; // Use specific types instead of `any` for each modal if possible
    };
    

    2. Define the Modal Store State
    You need to ensure that opened in ModalStoreState can map modal names to their corresponding data types.

    type ModalStoreState = {
      opened: Map<string, ModalData | null>; // Map for easier data handling
    };
    

    3. Define the Modal Store Actions
    The open function needs to accept data that aligns with the modal name. You can use TypeScript generics to ensure type safety.

    type ModalStoreActions = {
      open: <T extends ModalData | null>(name: string, data: T) => void;
      close: (name: string) => void;
      getData: <T extends ModalData | null>(name: string) => T | null;
    };
    

    4. Implement the Modal Store

    Use Zustand’s create function to implement the store and manage type safety:

    import create from 'zustand';
    
    type ModalStore = ModalStoreState & ModalStoreActions;
    
    export const useModalStore = create<ModalStore>((set, get) => ({
      opened: new Map(),
    
      open: <T extends ModalData | null>(name: string, data: T) =>
        set(state => {
          const newOpened = new Map(state.opened);
          newOpened.set(name, data);
          return { opened: newOpened };
        }),
    
      close: (name: string) =>
        set(state => {
          const newOpened = new Map(state.opened);
          newOpened.delete(name);
          return { opened: newOpened };
        }),
    
      getData: <T extends ModalData | null>(name: string) => {
        const opened = get().opened.get(name);
        return opened as T | null;
      },
    }));
    

    An example of how you would use this store in a React component:

    const ExampleComponent = () => {
      const open = useModalStore(state => state.open);
      const close = useModalStore(state => state.close);
      const getData = useModalStore(state => state.getData);
    
      const handleOpenModal = () => {
        open('exampleModal', { someData: 'value' });
      };
    
      const handleCloseModal = () => {
        close('exampleModal');
      };
    
      const modalData = getData<{ someData: string }>('exampleModal');
    
      return (
        <div>
          <button onClick={handleOpenModal}>Open Modal</button>
          <button onClick={handleCloseModal}>Close Modal</button>
          {modalData && <div>Data: {modalData.someData}</div>}
        </div>
      );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search