# How to Create a Custom React Hook for LocalStorage Using React Context

---

Managing local storage in React applications can become cumbersome, especially when you need to synchronize data across multiple components. By creating a custom hook combined with React Context, you can simplify this process and make your code more reusable and maintainable. In this article, I’ll guide you through creating a custom React hook for local storage and demonstrate how to integrate it using React Context.

### Setting Up the Project

First, let’s set up a new React project using Vite with `TypeScript` for better type safety.

#### Open your terminal and execute the following command to create a new Vite project:

```bash
pnpm create vite@latest react-store-hook
```

> During the setup, select `react` and then `typescript` to ensure your project uses TypeScript.

#### Navigate to the project directory and install the dependencies:

```bash
cd react-store-hook
pnpm install
```

---

### Setting Up the React Context

Next, we’ll set up a React Context to provide our custom hook across the application. This way, we can access the local storage state in any component.

#### First, define the type for the context:

Create a folder `types` inside the `src` folder and a file `index.d.ts` inside the folder.

```typescript
// src/types/index.d.ts

declare type StoreContextType = {
  [key: string]: unknown;
};
```

Now create a folder named `hooks` inside `src` folder.

#### Create the context file:

Create a `StoreContext.ts` file. The `StoreContext` will provide the global state and a method to update it across your application.

```typescript
// src/hooks/StoreContext.ts

import { createContext, Dispatch, SetStateAction } from "react";

export const StoreContext = createContext<{
  store: StoreContextType;
  setStore: Dispatch<SetStateAction<StoreContextType>>;
}>({
  store: {},
  setStore: () => {},
});
```

#### Next, create the provider component:

Create a `StoreProvider.tsx` file. The `StoreProvider` component will wrap your application and provide the `StoreContext` to its children.

```typescript
// src/hooks/StoreProvider.tsx

import { useState, ReactNode, FC } from "react";
import { StoreContext } from "./StoreContext";

const StoreProvider: FC<{ children: ReactNode }> = ({ children }) => {
 const [store, setStore] = useState<StoreContextType>({});

 return <StoreContext.Provider value={{ store, setStore }}>{children}</StoreContext.Provider>;
};

export { StoreProvider };
```

#### Creating the Custom Hook: useStore

Now let’s create the custom hook `useStore`. This hook will interact with both the `StoreContext` and local storage. Create a `useStore.ts` file.

**Add the following code step by step to**`useStore.ts`**:**

**Step 1: Import Necessary Modules**

```typescript
import { useContext, useEffect, useCallback, useState } from "react";
import { StoreContext } from "./StoreContext";
```

Here, we import React hooks and the `StoreContext`. We'll use these to manage state and context.

**Step 2: Define the Hook Signature**

```typescript
const useStore = <T>(
    key: string,
    initialValue?: T | null,
    storeInLocalStorage: boolean = true
): [T, (value: T) => void, boolean] => {
```

The `useStore` hook takes three parameters:

* `key`: The key to store the value under.
    
* `initialValue`: The initial value to set.
    
* `storeInLocalStorage`: A flag to determine if the value should be stored in local storage.
    

It returns a array containing:

* The stored value.
    
* A function to update the value.
    
* A loading state.
    

**Step 3: Initialize the State**

```typescript
const { store, setStore } = useContext(StoreContext);

const initializeState = useCallback(() => {
    if (storeInLocalStorage && typeof window !== "undefined") {
        const storedValue = localStorage.getItem(key);
        return storedValue !== null ? JSON.parse(storedValue) : initialValue;
    }
    return initialValue || null;
}, [key, initialValue, storeInLocalStorage]);

const [isLoading, setIsLoading] = useState<boolean>(true);
const [localValue, setLocalValue] = useState<T | undefined>(initializeState);
```

* We use `useContext` to get the `store` and `setStore` from `StoreContext`.
    
* `initializeState` is a function that gets the initial state from local storage if the flag is set and the window object is available. Otherwise, it returns the initial value.
    
* `isLoading` indicates if the state is being initialized.
    
* `localValue` holds the current value.
    

**Step 4: Sync State with Local Storage and Context**

```typescript
useEffect(() => {
    setIsLoading(true);

    if (storeInLocalStorage && localValue !== undefined) {
        localStorage.setItem(key, JSON.stringify(localValue));
    }
    setStore(prevStore => ({
        ...prevStore,
        [key]: localValue,
    }));

    setIsLoading(false);
}, [key, localValue, storeInLocalStorage, setStore]);
```

* The `useEffect` hook syncs the `localValue` with local storage and the context whenever `localValue`, `key`, or `storeInLocalStorage` changes.
    
* It updates local storage if the flag is set and sets the value in the context.
    

**Step 5: Create the Set Value Function**

```typescript
const setValue = useCallback(
    (value: T) => {
        setLocalValue(value);
        setStore(prevStore => ({
            ...prevStore,
            [key]: value,
        }));

        if (storeInLocalStorage) {
            localStorage.setItem(key, JSON.stringify(value));
        }
    },
    [key, storeInLocalStorage, setStore]
);
```

* The `setValue` function updates the `localValue` and the context.
    
* It also updates local storage if the flag is set.
    

**Step 6: Return the Hook Values**

```typescript
    return [store[key] !== undefined ? (store[key] as T) : (localValue as T), setValue, isLoading];
};

export default useStore;
```

The hook returns the current value, the `setValue` function, and the loading state.

**Full**`useStore.ts`**File:**

```typescript
// src/hooks/useStore.ts

import { useContext, useEffect, useCallback, useState } from "react";
import { StoreContext } from "./StoreContext";

const useStore = <T>(
 key: string,
 initialValue?: T | null,
 storeInLocalStorage: boolean = true
): [T, (value: T) => void, boolean] => {
 const { store, setStore } = useContext(StoreContext);

 const initializeState = useCallback(() => {
  if (storeInLocalStorage && typeof window !== "undefined") {
   const storedValue = localStorage.getItem(key);
   return storedValue !== null ? JSON.parse(storedValue) : initialValue;
  }

  return initialValue || null;
 }, [key, initialValue, storeInLocalStorage]);

 const [isLoading, setIsLoading] = useState<boolean>(true);
 const [localValue, setLocalValue] = useState<T | undefined>(initializeState);

 useEffect(() => {
  setIsLoading(true);

  if (storeInLocalStorage && localValue !== undefined) {
   localStorage.setItem(key, JSON.stringify(localValue));
  }
  setStore(prevStore => ({
   ...prevStore,
   [key]: localValue,
  }));

  setIsLoading(false);
 }, [key, localValue, storeInLocalStorage, setStore]);

 const setValue = useCallback(
  (value: T) => {
   setLocalValue(value);
   setStore(prevStore => ({
    ...prevStore,
    [key]: value,
   }));

   if (storeInLocalStorage) {
    localStorage.setItem(key, JSON.stringify(value));
   }
  },
  [key, storeInLocalStorage, setStore]
 );

 return [store[key] !== undefined ? (store[key] as T) : (localValue as T), setValue, isLoading];
};

export default useStore;
```

---

### Putting It All Together

Finally, let’s see how to use the `StoreProvider` and `useStore` hook in your application.

#### In the `main.tsx` file:

```typescript
// src/main.tsx

import { StoreProvider } from "./hooks/StoreProvider";
// other imports

ReactDOM.createRoot(document.getElementById("root")!).render(
 <React.StrictMode>
  <StoreProvider>
   <App />
  </StoreProvider>
 </React.StrictMode>
);
```

#### In the component where you want to use the hook:

```typescript
// src/MyComponent.tsx

import React from 'react';
import useStore from './hooks/useStore';

const MyComponent: React.FC = () => {
    const [value, setValue, isLoading] = useStore<string>('myKey', 'defaultValue');

    if (isLoading) {
        return <div>Loading...</div>;
    }

    return (
        <div>
            <h1>Stored Value: {value}</h1>
            <button onClick={() => setValue('newValue')}>Set New Value</button>
        </div>
    );
};

export default MyComponent;
```

---

### Conclusion

In this article, we’ve walked through the process of creating a custom React hook for managing local storage, integrating it with React Context to provide a clean and efficient solution for state management across your application. By encapsulating local storage logic within a hook, we can keep our components simpler and more focused on their core responsibilities. Using TypeScript enhances type safety and makes the code more robust.

The `useStore` hook, combined with `StoreContext` and `StoreProvider`, provides a flexible and reusable way to manage local storage, ensuring that data is synchronized across components and reducing the need for repetitive code. This approach not only improves maintainability but also makes your application more scalable and easier to understand.

Implementing custom hooks and contexts in your React applications can significantly improve your development workflow. We encourage you to try integrating these patterns into your projects and experience the benefits of a cleaner, more maintainable codebase.

By following this guide, you’ll have an excellent tool for managing state in your React development toolbox. Feel free to experiment with this to tailor the useStore hook to your specific needs.

Happy coding!
