Skip to content

Latest commit

 

History

History
132 lines (91 loc) · 4.92 KB

File metadata and controls

132 lines (91 loc) · 4.92 KB

In this lesson, we will learn how to create our own custom hook. We will use a custom hook to store values to localStorage.

Let's create a folder named hooks inside your src folder under the smarter-tasks project that you created in the last level. Create a file named useLocalStorage.ts inside the hooks subfolder.

Action: Create a file named hooks/useLocalStorage.ts inside wd301/smarter-tasks/src/hooks

Let's add a helper function to extract value stored in localStorage.

const getStoredValue = (key: string, defaultValue: any) => {
  const savedItem = localStorage.getItem(key);
  if (savedItem) {
    return JSON.parse(savedItem);
  }
  return defaultValue;
};

Now, we can write our hook. Let's call it useLocalStorage. We will use a state to store the values, and an effect which would trigger whenever the state changes. We will then return the state and setter function. That is what hooks usually do. Now, this can be used in components to get the value or set the value.

export const useLocalStorage = (key: string, defaultValue: any) => {
  const [value, setValue] = useState(() => {
    return getStoredValue(key, defaultValue);
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};

Now, we use any as the type of value which is being saved. It breaks the type safety which TypeScript provides. We are going to make it type safe. We have already used a pattern before. If you look at TaskApp.tsx

Action: Switch to TaskApp.tsx file.

React.useState<TaskAppState>;

We provided TaskAppState as the type for the state. This is made possible with a concept called generics. It simply means, when we write a piece of code, we won't limit it to a particular type, instead we provide a placeholder. Then whenever this function is used in other part of the code, a type will be specified, and it will be used. So if you look at the declaration of the useState,

Action: Command + click on React.useState to show the TS declaration file.

function useState<S>(
  initialState: S | (() => S)
): [S, Dispatch<SetStateAction<S>>];

We can see this weird looking syntax with angle brackets. S is a placeholder. When we invoke, useState, we have to provide it. Like, we provided TaskAppState.

Action: open https://www.typescriptlang.org/docs/handbook/2/generics.html in browser, highlight the examples.

This is how we declare a generic function.

Action: also google how to add generic parameter to arrow function TypeScript. https://stackoverflow.com/a/45576880/2861108

So for an arrow function, we have to place the placeholder after the equal sign. E.g.:

const foo = <T,>(x: T) => x;

Let's make our hook also a generic one. We will first add a placeholder to the getStoredValue function. The default value will also have the type T. T is just an arbitrary character short for Type. You can use a word instead of a single letter. The function also returns a value of type T.

const getStoredValue = <T>(key: string, defaultValue: T): T => {
  const savedItem = localStorage.getItem(key);
  if (savedItem) {
    return JSON.parse(savedItem);
  }
  return defaultValue;
};

Now, we will do the same with useLocalStorage. The return value of the function will be a pair, which has a first element of type T, and the second item is a setter function. We can look up the type for it by going to the TypeScript declaration file for react.

export const useLocalStorage = <T,>(
  key: string,
  defaultValue: T
): [T, React.Dispatch<React.SetStateAction<T>>] => {
  const [value, setValue] = useState(() => {
    return getStoredValue(key, defaultValue);
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};

Now, we will replace the state with our custom hook. Let's import it first.

import { useLocalStorage } from "./hooks/useLocalStorage";

Instead of useState, let's use useLocalStorage. We have provided the key as tasks.

const [taskAppState, setTaskAppState] = useLocalStorage<TaskAppState>("tasks", {
  tasks: [],
});

Let's test it. We will open the developer console. Now, there are no entries in the localStorage

Action: Open http://localhost:5173 in browser

Adding an item is working fine. It also got stored in localStorage. Now if we close the tab and reopen it

Action: close the tab, reopen and visit http://localhost:5173

The items are being populated from localStorage.

Let's remove any unused code from our component.

Action: Remove the useEffect example code.

See you in the next lesson.

Reference

Reusing logic with custom hooks, accessed on: April 04, 2023, URL:https://beta.reactjs.org/learn/reusing-logic-with-custom-hooks

TypeScript Generics, accessed on: April 04, 2023, URL:https://www.typescriptlang.org/docs/handbook/2/generics.html