skip to Main Content

I am using react with typescript, to make a page for managing a users files and folders.
The page contains three tabs, one for the users own files, one for files shared with him and his recycle bin.

I am thinking of handling the calls to the the storage api in a StorageProvider, which would have functions like FetchMyFiles, FetchRecycleBin, RenameFolder and also store the fetched data for use by underlying components.

Any child component would then be able to call the shared helper functions which would update the context for everyone. The main app component could for example call loadMyFolders when the tab is selected and a CreateFolder component could call createFolder.

Does this approach make sense in react, and if so, how do I update the folders state in the code below?

export const StorageContext = createContext<StorageContextType | undefined>(undefined);

type StorageContextType = {

    myFolder: JsonFolder | undefined;
    recycleBin: JsonFolder | undefined;
    sharedFolder: JsonFolder | undefined;
    loadMyFolder: (signal: AbortSignal) => Promise<void>;
    loadRecycleBin: (signal: AbortSignal) => Promise<void>;
    createFolder: (signal: AbortSignal, parentFolderId?: string, folderName?: string) => Promise<void>;
    //... a bunch of other methods for interacting with the api and update the three states

}

type SwwStorageProviderProps = {
    baseUrl: string;
    children: ReactNode;
}

export default function StorageProvider(props: SwwStorageProviderProps) {

    const [myFolder, setMyFolder] = useState<JsonFolder | undefined>(undefined);
    const [recycleBin, setRecycleBin] = useState<JsonFolder | undefined>(undefined);
    const [sharedFolder, setSharedFolder] = useState<JsonFolder | undefined>(undefined);

    async function loadMyFolder(signal: AbortSignal): Promise<void> {
        const url = props.baseUrl + 'Api/Folder/Get';
        const myFolders = await getFolderFromSww(url, signal);
        setMyFolder(myFolders);
    }
    
    //... other function implementations

    return (
        <StorageContext.Provider value={{
            myFolder: myFolder,
            recycleBin: recycleBin,
            sharedFolder: sharedFolder,
            loadMyFolder: loadMyFolder,
            loadRecycleBin: loadRecycleBin,
            createFolder: createFolder
        }}>
            {props.children}
        </StorageContext.Provider>
    );

}

2

Answers


  1. In short, our project uses a similar method with createContext() to store commonly used states and functions (for example, common API callings) across pages.

    As long as this FoldersContext is shared by many smaller components and with many layers, it will be simpler than passing them down using props one by one.

    And if you want to update the state in this FoldersContext, you just have to provide setter methods (usually the setState methods) in the Provider as well, for example,

    In main.tsx,

    // ...other imports
    import StorageProvider from "./storage";
    
    ReactDOM.createRoot(document.getElementById("root")!).render(
      // ...other layers
      <StorageProvider>
        <App />
      </StorageProvider>
    )
    

    In storage.tsx,

    // ...necessary imports
    
    export const FoldersContext = createContext<
      FoldersContextType | undefined
    >(undefined);
    
    type FoldersContextType = {
      // REMARK: Not too sure about the promise, but in case you want to call some APIs,
      // let's keep them for now (if they are just getters, then no need for Promise<any>).
      // Or you could just directly refer to them, for example, "myFolders: JsonFolder | undefined".
      getMyFolders(signal: AbortSignal): Promise<JsonFolder | undefined>;
      getRecycleBin(signal: AbortSignal): Promise<JsonFolder | undefined>;
      getSharedFolders(signal: AbortSignal): Promise<JsonFolder | undefined>;
      setMyFolders: (newFolders: JsonFolder) => void;
      setRecycleBin: (newRecycleBin: JsonFolder) => void;
      setSharedFolders: (newSharedFolders: JsonFolder) => void;
    }
    
    type StorageProviderProps = {
      apiBaseUrl: string;
      children: ReactNode;
    }
    
    export default function StorageProvider({
      apiBaseUrl, children,
    }: StorageProviderProps) {
    
      // REMARK: You might want to have JsonFolder[] as you put "s" at the end of all variables.
      const [userFolders, setUserFolders] = useState<JsonFolder | undefined>(undefined);
      const [recycleBin, setRecycleBin] = useState<JsonFolder | undefined>(undefined);
      const [sharedFolders, setSharedFolders] = useState<JsonFolder | undefined>(undefined);
    
      async function getRecycleBin(signal: AbortSignal): Promise<JsonFolder | undefined> {
        //Code for getting recycle bin
        return recycleBin;
      }
    
      async function getMyFolders(signal: AbortSignal): Promise<JsonFolder | undefined> {
        //Code for getting user folders
        return userFolders;
      }
    
      // ...other functions
    
      return (
        <FoldersContext.Provider
          value={{
            getMyFolders(signal: AbortSignal) {
              // REMARK: Just as an example with the types you defined.
              return new Promise((resolve, reject) => resolve(getMyFolders(signal)))
            },
            // ...getRecycleBin(), getSharedFolders()
            setMyFolders: (newFolders: JsonFolder) => setUserFolders(newFolders),
            setRecycleBin: (newRecycleBin: JsonFolder) => setRecycleBin(newRecycleBin),
            setSharedFolders: (newSharedFolders: JsonFolder) => setSharedFolders(newSharedFolders),
          }}>
            {/* ...other components */}
            {children}
        </FoldersContext.Provider>
      );
    }
    

    In App.tsx,

    // ...other imports
    import { FoldersContext } from "./storage";
    
    function App() {
      const { getMyFolders } = useContext(FoldersContext);
    
      return (
        <>
          {/* The main components */}
        </>
      )
    }
    

    You can check more of its behaviours in the documentation.

    Login or Signup to reply.
  2. This approach make sense, however there’s something wrong in your implementation: you save the functions used to modified the state in the state itself.

    type FoldersStateType = {
        myFolders: JsonFolder | undefined;
        recycleBin: JsonFolder | undefined;
        sharedFolders: JsonFolder | undefined;    
    }
    
    type FoldersContextType = {
        folders: FoldersStateType  | undefined;
        setMyFolders: (newFolders: JsonFolder) => void;
        setRecycleBin: (newRecycleBin: JsonFolder) => void;
        setSharedFolders: (newSharedFolders: JsonFolder) => void;   
    }
    
    
    export default function StorageProvider(props: StorageProviderProps) {
    
        const [folders, setFolders] = useState<FoldersStateType | undefined>(
        { myFolders: undefined, recycleBin: undefined, sharedFolders: undefined });
    
        async function getRecycleBin(signal: AbortSignal): Promise<JsonFolder | undefined> {
            //Code for getting recycle bin
            return recycleBin;
        }
    
        async function getMyFolders(signal: AbortSignal): Promise<JsonFolder | undefined> {
            //Code for getting user folders
            return userFolders;
        }
    
        const contextValue = { folders, getMyFolders, getRecycleBin };
    
        return (
            <FoldersContext.Provider value={contextValue}>
                {props.children}
            </FoldersContext.Provider>
        );
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search