# 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)"
);
```