React: Store your data into localStorage
===

###### tags: `React`
---
## useState to store a data state
Using useState to store a state, let's make a easy start, just to create a greeting component which contains a simple `input` that allows us to type a name, and render the name immediately.
```typescript=
// App.tsx
import "./styles.css";
import Greeting from "./components/Greenting";
export default function App() {
return (
<div className="App">
<Greeting />
</div>
);
}
```
```typescript=
// Greeting.tsx
import React, { ChangeEvent, useState } from "react";
const Greenting = ():JSX.Element => {
const [name, setName] = useState<string>("");
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value;
setName(newName);
}
return (
<form>
<input
type="text"
onChange={handleNameChange}
value={name}
/>
<p>Hello, {name}</p>
</form>
)
}
export default Greeting;
```
> How to know what type of an event?
> If you don't know what type of event in the event handler, you write event handler inline and hover over the event, TS will show the type for you.
> 
---
## Set an initial name as a prop
It's not ok if we render the page, and there's no name appear in the first place, so let's give an initial name.
How can we do that? First of all, we need to define the type of the `initialName`.
```typescript=
// Greeting.tsx
import React, { ChangeEvent, useState } from "react";
// Method 1: Declare a type
type InitialNameType = {
initialName: string;
}
// Method 2: Declare directly
// const Greeting = (initialName: string) => {...}
const Greenting = ({initialName}: InitialNameType):JSX.Element => {
const [name, setName] = useState<string>(initialName);
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value;
setName(newName);
}
return (
<form>
<input type="text" onChange={handleNameChange} value={name}/>
<p>Hello, {name}</p>
</form>
)
}
export default Greenting;
```
```typescript=
// App.tsx
import "./styles.css";
import Greeting from "./components/Greenting";
export default function App() {
return (
<div className="App">
<Greeting initialName="Andy"/>
</div>
);
}
```
What happend if we don't specific the type for the `initialName`? TS will complaint and will show you that `initialName` has an `any` type which we don't want this, we want to make it as clear as possible.

---
## useEffect
Use localStorage to store data and use `useEffect` to retreive from it.
When we use `useEffect`, once the page is loading, it will trigger immidiately to look for `name` in our localStorage.
```typescript=
import React, { ChangeEvent, useEffect, useState } from "react";
type InitialNameType = {
initialName: string;
}
const Greenting = ({initialName}: InitialNameType):JSX.Element => {
const [name, setName] = useState<string>(initialName);
useEffect(() => {
window.localStorage.setItem("name", name);
})
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value;
setName(newName);
}
return (
<form>
<input type="text" onChange={handleNameChange} value={name}/>
<p>Hello, {name}</p>
</form>
)
}
export default Greenting;
```
Now we are able to store `name` in the localStorage, but how can we pull it out and if there's no value in localStorage, we want to show our `initialName`?
```typescript=
import React, { ChangeEvent, useEffect, useState } from "react";
type InitialNameType = {
initialName: string;
}
const Greenting = ({initialName}: InitialNameType):JSX.Element => {
const [name, setName] = useState<string>(window.localStorage.getItem("name") || initialName);
useEffect(() => {
window.localStorage.setItem("name", name);
})
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value;
setName(newName);
}
return (
<form>
<input type="text" onChange={handleNameChange} value={name}/>
<p>Hello, {name}</p>
</form>
)
}
export default Greenting;
```
---
## Lazy state initialization
When we use `useState`, we understand that `setName` runs every time we typing a new name, the function triggers the re-render, if the function body runs, that means all the code inside it runs as well. Which means any variables we create or arguments we pass are created and evaluated every render.
When do we need a lazy state initialization?
- The initial value for your state is computationally expensive.
- When we need to read into localStorage which is an IO operation.
Accoirding to [React Beta](https://beta.reactjs.org/reference/react/useState#updating-state-based-on-the-previous-state), we can pass the **next state directly**, or **a function that calculates it from the previous state.**
**If we pass a function as nextState, it will be treated as an updater function. It must be pure, should take the pending state as its only argument, and should return the next state.**
Let's see the result if we pass the next state directly.
Every types trigger a re-render, meaning that everytime we write a character, we reading out of localstorage.

Now, let's create a function called `getInitialValue` as parameter. We can see we've got `Rendering` and `Getting Initial Value once` when page first loading, meaning the function would be called only when page's first loading,

```typescript=
// Greeting.tsx
// another way of passing a function as param
const [name, setName] = useState<string>(
() => window.localStorage.getItem("name") || initialName
);
```
### Example of passing state directly and a function
```typescript=
// Count.tsx
// Passing state directly
const Count = ():JSX.Element => {
const initialNum = 20;
const [count, setCount] = useState<number>(initialNum);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
return (
<div>
<p>{count}</p>
<button
type="submit"
onClick={handleClick}>Add</button>
</div>
)
}
export default Count;
```
The resule would be:
- First rendering: 20;
- Each click would only be adding 1;
- result: 20, 21, 22, 23 so on and so forth.

```typescript=
// Count.tsx
// Passing state directly
const Count = ():JSX.Element => {
const initialNum = 20;
const [count, setCount] = useState<number>(initialNum);
const handleClick = () => {
setCount(count => count + 1);
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<p>{count}</p>
<button
type="submit"
onClick={handleClick}>Add</button>
</div>
)
}
export default Count;
```

Here, count => count + 1 is our updater function. It takes the pending state and calculates the next state from it.
So the initial value is 20, React puts out updater functions in a [queue](https://beta.reactjs.org/learn/queueing-a-series-of-state-updates). Then, during the next render, it will call them in the same order.
---
## Optimize useEffect
useEffect will trigger re-render even if the parent component re-renders, it will still affects useEffect.
```typescript=
// App.tsx
import "./styles.css";
import {useState} from "react";
import Greeting from "./components/Greenting";
export default function App() {
const [num, setNum] = useState<number>(0);
return <div className="App">
<Greeting initialName="Andy"/>
<button onClick={() => setNum(num => num + 1)}>{num}</button>
</div>;
}
```
```typescript=
// Greeting.tsx
import React, { ChangeEvent, useEffect, useState } from "react";
type InitialNameType = {
initialName: string;
};
const Greenting = ({ initialName }: InitialNameType): JSX.Element => {
console.log("Greeting rendering")
const [name, setName] = useState<string>(
() => window.localStorage.getItem("name") || initialName
);
useEffect(() => {
console.log("useEffect rendering")
window.localStorage.setItem("name", name);
});
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
...
};
return (
...
};
export default Greenting;
```
Everytime we click the button, it will trigger `useEffect` as well.

We need to pass a second argument, which is a dependency, it could be an empty array or an array containing dependencies.
>The most basic dependency array would be an empty array. The empty array indicates that the useEffect doesn’t have any dependencies on any state variables. Therefore, the callback function is only called once the page renders in this case.
In this case, if we use an empty array, what would happend?
- We no longer see useEffect to be triggered.
- The value in the localstorage was not changed.
The reason why the value in the localstorage wasn't changed was because we did not pass `name` as a dependency.

If you have installed eslint, it will show you that useEffect has a missing dependency `name` so that if the `name` has changed, useEffect would be able to re-render based on it, hence, add `name` inside of that empty array would optimize the performance.

> React does a shadow comparison for all of the dependencies, if we pass two objects, even if they have the same properties, it would cause the re-run due to the shadow comparison( `Object.is` comparison algorithm) (object before is not as same as the current object.)
> [useEffect](https://beta.reactjs.org/reference/react/useEffect#useeffect)
> [useEffect-dependency](https://codedamn.com/news/reactjs/useeffect-dependency)
---
## Create a custom hook
We can reorganize our code and make some of them a custom hook, let's make a custom hook for store and retrive data from localstorage.
First of all, let's create a folder called `hooks`, and add a file called `useLocalStorage`, it's okay to name it whatever you want, but start with `use` is a convention when it is a `custom hook`.
```typescript=
// useLocalStorage.tsx
import {useEffect, useState} from "react";
const useLocalStorage = (key:string, defaultValue:string) => {
const [state, setState] = useState(() =>
const localStorageValue = window.localStorage.getItem(key);
if (localStorageValue) {
return JSON.parse(localStorageValue)
}
return defaultValue;
}
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(state));
}, [key, state])
export defualt useLocalStorage;
```
```typescript=
// Greeting.tsx
import React, { ChangeEvent} from "react";
import useLocalStorage from "../hooks/useLocalStorage";
type InitialNameType = {
initialName: string;
};
const Greenting = ({ initialName }: InitialNameType): JSX.Element => {
const [name, setName] = useLocalStorage("name", initialName);
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value;
setName(newName);
};
console.log("Greeting is rendering")
return (
...
);
};
export default Greenting;
```
>[CodeSandbox](https://codesandbox.io/s/gracious-tess-ei60ms?file=/src/hooks/useLocalStorage.tsx)