---
# System prepended metadata

title: React hook
tags: [react]

---

# React hook


## useStorage
```typescript
import { SetStateAction, useState } from "react"
export const useLocalStorage = <T>(key: string, initionValue: T | (() => T)) => {
  return useStorage(key, initionValue, localStorage)
}
export const useSessionStorage = <T>(key: string, initionValue: T | (() => T)) => {
  return useStorage(key, initionValue, sessionStorage)
}
export const useStorage = <T>(key: string, initionValue: T | (() => T), storage: Storage) => {
  const [value, setValue] = useState<T | undefined>(() => {
    const storageValue = storage.getItem(key)
    if (storageValue !== null) return JSON.parse(storageValue)
    const newValue = typeof initionValue == 'function' ? (initionValue as () => T)() : initionValue
    storage.setItem(key, JSON.stringify(newValue))
    return newValue
  })
  const onChange = (value: SetStateAction<T | undefined>) => {
    setValue(value)
    const newValue = typeof value === 'function' ? (value as () => T)() : value
    storage.setItem(key, JSON.stringify(newValue))
  }
  const remove = () => {
    setValue(undefined)
    storage.removeItem(key)
  }
  return [value, onChange, remove] as const
}
```

## useLayoutEffect vs useEffect vs useInsertionEffect

```typescript
  useEffect(() => {
    console.log('run after useEffect ')

  }, [])
  useLayoutEffect(() => {
    console.log('run before useLayoutEffect')
  }, [])
  useInsertionEffect(() => {
    console.log('run useInsertionEffect')
  }, [])
```

### 執行時機點
* useEffect : after render
* useLayoutEffect : before render
* useInsertionEffect : before dom change

### useInsertionEffect 使用時機
通常 `css-in-js` 會用到例如以下的例子，我們希望串見一個 `button` 並且擁有自己的 `css` ， 因為 `css-in-js` 的緣故不希望透故 `css`導案注入 `style`。

```
// In your JS file:
<button className="success" />

// In your CSS file:
.success { color: green; }
```
所以我們創建一個 `Button` 的 `component` 透過 `useInsertionEffect` 把 `button` 相關的樣式透過 `js` 寫入 `head` 中，同時因會生命週期關係，`button` 相關的 `style` 會需要再 `dom` 改變前去注入，以免 `render` 時找不到相關的 `style` 這也是 `useInsertionEffect` 出現的原因。

```typescript
// Inside your CSS-in-JS library
let isInserted = new Set();
function useCSS(rule) {
  useInsertionEffect(() => {
    // As explained earlier, we don't recommend runtime injection of <style> tags.
    // But if you have to do it, then it's important to do in useInsertionEffect.
    if (!isInserted.has(rule)) {
      isInserted.add(rule);
      document.head.appendChild(getStyleForRule(rule));
    }
  });
  return rule;
}

function Button() {
  const className = useCSS('...');
  return <div className={className} />;
}
```
那因為 `useInsertionEffect` 不會再 `server` 執行，如果你希望在 `sever`先收集哪些用過的 `rule` 可以這樣做 。

```typescript
let collectedRulesSet = new Set();

function useCSS(rule) {
  if (typeof window === 'undefined') {
    collectedRulesSet.add(rule);
  }
  useInsertionEffect(() => {
    // ...
  });
  return rule;
}
```
或是也可以透過 `useInsertionEffect` 先 `pre-render style` 
```typescript
 useInsertionEffect(() => {
    const style = document.createElement('style')
    style.innerHTML = `
      .box{
        width:400px;
        height:400px;
        background:red;
      }
    `
    document.head.appendChild(style)
    // not run on server only on client 
    // often do in css-in-js need to change dom tree
  }, [])

```

 
 
 ## useSetParamAndPushs
 ```typescript
 const useSetParamAndPushs = () => {
  const router = useRouter()

  return (param: string, value: string = '') => {
    const currentURL = new URL(window.location.href)

    const currentSearch = new URLSearchParams(window.location.search)
    if (value === '') {
      currentSearch.delete(param)
    } else {
      currentSearch.set(param, value)
    }
    currentURL.search = currentSearch.toString()
    router.push(currentSearch.toString())
    router.refresh()
  }
}
 ```
 

## useMediaQuery
 ```typescript
 import { useSyncExternalStore } from "react";

export const useMediaQuery = (query: string) => {
    const subscribe = (callback: () => void) => {
        const matchMedia = window.matchMedia(query)
        matchMedia.addEventListener('change', callback)
        return () => matchMedia.removeEventListener('change', callback)
    }

    const getSnapshot = () => {
        return window.matchMedia(query).matches
    }

    const getServerSnapshot = () => {
        throw Error("useMediaQuery is a client-only hook");
    }

    return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
};

// useage

const isSmallDevice = useMediaQuery("only screen and (max-width : 768px)");
const isMediumDevice = useMediaQuery(
"only screen and (min-width : 769px) and (max-width : 992px)"
);
const isLargeDevice = useMediaQuery(
"only screen and (min-width : 993px) and (max-width : 1200px)"
);
const isExtraLargeDevice = useMediaQuery(
"only screen and (min-width : 1201px)"
);

```