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