Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,7 @@ function Component() {
## 6.1.2(Oct 30, 2025)

- feat: add useScratch hook.

## 6.1.6(Nov 21, 2025)

- fix(createStorage): use `useLatest` to avoid unnecessary re-renders and simplify dependency arrays
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reactuses/core",
"version": "6.1.5",
"version": "6.1.6",
"license": "Unlicense",
"homepage": "https://www.reactuse.com/",
"repository": {
Expand Down
24 changes: 14 additions & 10 deletions packages/core/src/createStorage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { guessSerializerType } from '../utils/serializer'
import { useEvent } from '../useEvent'
import { defaultOnError, defaultOptions } from '../utils/defaults'
import { useDeepCompareEffect } from '../useDeepCompareEffect'
import { useLatest } from '../useLatest'

export interface Serializer<T> {
read: (raw: string) => T
Expand Down Expand Up @@ -131,23 +132,26 @@ export default function useStorage<
mountStorageValue,
listenToStorageChanges = true,
} = options
const storageValue = mountStorageValue ?? effectStorageValue
const storageValueRef = useLatest(mountStorageValue ?? effectStorageValue)
const onErrorRef = useLatest(onError)

try {
storage = getStorage()
}
catch (err) {
onError(err)
onErrorRef.current(err)
}

const type = guessSerializerType<T | undefined>(defaultValue)
const serializer = options.serializer ?? StorageSerializers[type]
const serializerRef = useLatest(options.serializer ?? StorageSerializers[type])

const [state, setState] = useState<T | null>(
getInitialState(key, defaultValue, storage, serializer, onError),
getInitialState(key, defaultValue, storage, serializerRef.current, onError),
)

useDeepCompareEffect(() => {
const serializer = serializerRef.current
const storageValue = storageValueRef.current
const data
= (storageValue
? isFunction(storageValue)
Expand All @@ -167,12 +171,12 @@ export default function useStorage<
}
}
catch (e) {
onError(e)
onErrorRef.current(e)
}
}

setState(getStoredValue())
}, [key, serializer, storage, onError, storageValue])
}, [key, storage])

const updateState: Dispatch<SetStateAction<T | null>> = useEvent(
valOrFunc => {
Expand All @@ -184,10 +188,10 @@ export default function useStorage<
}
else {
try {
storage?.setItem(key, serializer.write(currentState))
storage?.setItem(key, serializerRef.current.write(currentState))
}
catch (e) {
onError(e)
onErrorRef.current(e)
}
}
},
Expand All @@ -197,14 +201,14 @@ export default function useStorage<
try {
const raw = storage?.getItem(key)
if (raw !== undefined && raw !== null) {
updateState(serializer.read(raw))
updateState(serializerRef.current.read(raw))
}
else {
updateState(null)
}
}
catch (e) {
onError(e)
onErrorRef.current(e)
}
})

Expand Down
4 changes: 4 additions & 0 deletions packages/website-docusaurus/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,7 @@ function Component() {
## 6.1.2(Oct 30, 2025)

- feat: add useScratch hook.

## 6.1.6(Nov 21, 2025)

- fix(createStorage): use `useLatest` to avoid unnecessary re-renders and simplify dependency arrays
44 changes: 39 additions & 5 deletions packages/website-docusaurus/docs/state/useLocalStorage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,48 @@ React side-effect hook that manages a single `localStorage` key
function Demo() {
// bind string
const [value, setValue] = useLocalStorage("my-key", "key");

// bind object with custom serializer
const [myObj, setMyObj] = useLocalStorage(
"myObj",
{
name: "test",
},
{
serializer: {
read: (val) => {
console.log("read", val);
return JSON.parse(val);
},
write: (val) => {
console.log("write", val);
return JSON.stringify(val);
},
},
}
);

return (
<div>
<div>Value: {value}</div>
<button onClick={() => setValue("bar")}>bar</button>
<button onClick={() => setValue("baz")}>baz</button>
{/* delete data from storage */}
<button onClick={() => setValue(null)}>Remove</button>
<div>
<h3>String Value</h3>
<div>Value: {value}</div>
<button onClick={() => setValue("bar")}>Set to "bar"</button>
<button onClick={() => setValue("baz")}>Set to "baz"</button>
<button onClick={() => setValue(null)}>Remove</button>
</div>

<div style={{ marginTop: "20px" }}>
<h3>Object Value</h3>
<div>Object: {JSON.stringify(myObj)}</div>
<button onClick={() => setMyObj({ name: "updated" })}>
Update Object
</button>
<button onClick={() => setMyObj({ name: "test", count: 1 })}>
Add Property
</button>
<button onClick={() => setMyObj(null)}>Remove Object</button>
</div>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,51 @@ description: 轻松管理 `localStorage`。 本文介绍其用法、最佳实践
## Usage

```tsx live

function Demo() {
// bind string
// 绑定字符串
const [value, setValue] = useLocalStorage("my-key", "key");

// 绑定对象(使用自定义序列化器)
const [myObj, setMyObj] = useLocalStorage(
"myObj",
{
name: "test",
},
{
serializer: {
read: (val) => {
console.log("读取", val);
return JSON.parse(val);
},
write: (val) => {
console.log("写入", val);
return JSON.stringify(val);
},
},
}
);

return (
<div>
<div>值: {value}</div>
<button onClick={() => setValue("bar")}>设置为bar</button>
<button onClick={() => setValue("baz")}>设置为baz</button>
{/* delete data from storage */}
<button onClick={() => setValue(null)}>移除</button>
<div>
<h3>字符串值</h3>
<div>值: {value}</div>
<button onClick={() => setValue("bar")}>设置为 "bar"</button>
<button onClick={() => setValue("baz")}>设置为 "baz"</button>
<button onClick={() => setValue(null)}>移除</button>
</div>

<div style={{ marginTop: "20px" }}>
<h3>对象值</h3>
<div>对象: {JSON.stringify(myObj)}</div>
<button onClick={() => setMyObj({ name: "updated" })}>
更新对象
</button>
<button onClick={() => setMyObj({ name: "test", count: 1 })}>
添加属性
</button>
<button onClick={() => setMyObj(null)}>移除对象</button>
</div>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,51 @@ description: 輕鬆管理 `localStorage`。 本文介紹其用法、最佳實踐
## Usage

```tsx live

function Demo() {
// bind string
// 綁定字串
const [value, setValue] = useLocalStorage("my-key", "key");

// 綁定物件(使用自定義序列化器)
const [myObj, setMyObj] = useLocalStorage(
"myObj",
{
name: "test",
},
{
serializer: {
read: (val) => {
console.log("讀取", val);
return JSON.parse(val);
},
write: (val) => {
console.log("寫入", val);
return JSON.stringify(val);
},
},
}
);

return (
<div>
<div>值: {value}</div>
<button onClick={() => setValue("bar")}>設置為bar</button>
<button onClick={() => setValue("baz")}>設置為baz</button>
{/* 從存儲中刪除數據 */}
<button onClick={() => setValue(null)}>移除</button>
<div>
<h3>字串值</h3>
<div>值: {value}</div>
<button onClick={() => setValue("bar")}>設置為 "bar"</button>
<button onClick={() => setValue("baz")}>設置為 "baz"</button>
<button onClick={() => setValue(null)}>移除</button>
</div>

<div style={{ marginTop: "20px" }}>
<h3>物件值</h3>
<div>物件: {JSON.stringify(myObj)}</div>
<button onClick={() => setMyObj({ name: "updated" })}>
更新物件
</button>
<button onClick={() => setMyObj({ name: "test", count: 1 })}>
添加屬性
</button>
<button onClick={() => setMyObj(null)}>移除物件</button>
</div>
</div>
);
};
Expand Down