我试图在next.js中创建一个具有页面刷新弹性的useState
的替代品。
遇到的一种可能的解决方案是使用window.localStorage
来保存和检索状态。这将使状态在页面刷新后仍然保持。
我发现了以下的useLocalStorage
钩子的ReactJS实现https://usehooks.com/useLocalStorage/
function useLocalStorage(key, initialValue) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === "undefined") {
return initialValue;
}
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
if (typeof window !== "undefined") {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}
然而,它生成以下错误当我使用它在NextJS:
未捕获错误:由于初始UI与服务器上呈现的内容不匹配,因此补水失败
经过一番搜索,我发现window
对象不存在于(Next.js)服务器端,这是错误的可能原因(窗口未在Next.js React应用程序中定义)。一种可能的解决方案是保护的使用window
useEffect
钩,只有在客户端运行。
我当前的useLocalStorage
钩子的实现是
function useLocalStorage<T>(key: string, defaultValue: T): [T, Dispatch<SetStateAction<T>>] {
const [value, setValue] = useState<T>(defaultValue);
useEffect(() => {
try {
const item = window.localStorage.getItem(key);
setValue(item ? JSON.parse(item) : defaultValue);
}
catch (error) {
setValue(defaultValue);
}
}, [key, defaultValue]);
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
然而,这次钩子并不总是像预期的那样工作,因为没有保证useEffect回调的执行顺序。因此,有时状态会丢失。
我想知道在NextJS中什么是正确的实现,并了解我的代码逻辑失败的地方。
下面是一个useLocalStorage钩子,它与next.js一起工作
import React, { useDebugValue, useEffect, useState } from "react";
export const useLocalStorage = <S>(
key: string,
initialState?: S | (() => S)
): [S, React.Dispatch<React.SetStateAction<S>>] => {
const [state, setState] = useState<S>(initialState as S);
useDebugValue(state);
useEffect(() => {
const item = localStorage.getItem(key);
if (item) setState(parse(item));
}, []);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [state]);
return [state, setState];
};
const parse = (value: string) => {
try {
return JSON.parse(value);
} catch {
return value;
}
};
Markos的回答一直在为我重置刷新状态。我添加了一个小调整,解决了这个问题:
import React, { useDebugValue, useEffect, useState } from "react"
const useLocalStorage = <S>(
key: string,
initialState?: S | (() => S)
): [S, React.Dispatch<React.SetStateAction<S>>] => {
const [state, setState] = useState<S>(initialState as S)
useDebugValue(state)
useEffect(() => {
const item = localStorage.getItem(key)
if (item) setState(parse(item))
}, [])
useEffect(() => {
if (state !== initialState) {
localStorage.setItem(key, JSON.stringify(state))
}
}, [state])
return [state, setState]
}
const parse = (value: string) => {
try {
return JSON.parse(value)
} catch {
return value
}
}
export default useLocalStorage
我不知道作者是否仍然相关。但也许有人能派上用场。
这对我来说很有效。我用一个函数替换了一个useEffect
,这样useEffect
就不会被不受控制地调用。
默认值将设置为useEffect
,在所有其他情况下将调用changeValue
函数。
export const useLocalStorage = (key: string, defaultValue: any) => {
const [value, setValue] = useState(defaultValue);
const changeValue = (value) => {
setValue(value);
localStorage.setItem(key, JSON.stringify(value));
}
useEffect(() => {
const stored = localStorage.getItem(key);
if (!stored) {
setValue(defaultValue);
localStorage.setItem(key, JSON.stringify(defaultValue));
} else {
setValue(JSON.parse(stored));
}
}, [defaultValue, key]);
return [value, changeValue]
}
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
export default function useLocalStorage<T>(
key: string,
defaultValue: T
): [T, Dispatch<SetStateAction<T>>] {
const isMounted = useRef(false)
const [value, setValue] = useState<T>(defaultValue)
useEffect(() => {
try {
const item = window.localStorage.getItem(key)
if (item) {
setValue(JSON.parse(item))
}
} catch (e) {
console.log(e)
}
return () => {
isMounted.current = false
}
}, [key])
useEffect(() => {
if (isMounted.current) {
window.localStorage.setItem(key, JSON.stringify(value))
} else {
isMounted.current = true
}
}, [key, value])
return [value, setValue]
}
在这里,useRef
被用来防止defaultValue
在第一次渲染时被存储在localStorage
中。基本上,它跳过第二个useEffect
的回调,在第一次渲染时运行,因此初始化可以在第一个useEffect
钩子没有竞争条件的情况下完成。
这对我有用:Next.js使用SSR的本地存储问题
…它没有在每次刷新时重置值,不像@MarkosTh09的解决方案。