Skip to content

Commit 90e4a65

Browse files
authored
allow functional updates to state (#98)
1 parent 9f4cf8b commit 90e4a65

File tree

2 files changed

+34
-8
lines changed

2 files changed

+34
-8
lines changed

src/use-localstorage.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
LOCAL_STORAGE_CHANGE_EVENT_NAME,
66
} from './local-storage-events';
77
import { isBrowser } from './is-browser'
8-
import { storage } from './storage'
8+
import { storage } from './storage'
99
import { useEffect, useState, useCallback } from 'react';
1010

1111
/**
@@ -22,8 +22,9 @@ function tryParse(value: string) {
2222
}
2323
}
2424

25-
export type LocalStorageNullableReturnValue<TValue> = [TValue | null, (newValue: TValue | null) => void, () => void];
26-
export type LocalStorageReturnValue<TValue> = [TValue, (newValue: TValue | null) => void, () => void];
25+
export type LocalStorageSetStateValue<TValue> = TValue | ((prevState: TValue | null) => TValue)
26+
export type LocalStorageNullableReturnValue<TValue> = [TValue | null, (newValue: LocalStorageSetStateValue<TValue> | null) => void, () => void];
27+
export type LocalStorageReturnValue<TValue> = [TValue, (newValue: LocalStorageSetStateValue<TValue> | null) => void, () => void];
2728

2829
/**
2930
* React hook to enable updates to state via localStorage.
@@ -103,7 +104,7 @@ export function useLocalStorage<TValue = string>(
103104
};
104105
}, [key, defaultValue, onLocalStorageChange]);
105106

106-
const writeState = useCallback((value: TValue) => writeStorage(key, value), [key]);
107+
const writeState = useCallback((value: LocalStorageSetStateValue<TValue>) => value instanceof Function ? writeStorage(key, value(localState)) : writeStorage(key, value), [key]);
107108
const deleteState = useCallback(() => deleteFromStorage(key), [key]);
108109
const state: TValue | null = localState ?? defaultValue;
109110

test/use-localstorage.test.ts

+29-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { useLocalStorage, deleteFromStorage } from '../src';
22
import { renderHook, act } from '@testing-library/react-hooks';
33

44
jest.mock('../src/storage', () => ({
5-
...jest.requireActual('../src/storage'),
6-
storage: jest.fn(),
5+
...jest.requireActual('../src/storage'),
6+
storage: jest.fn(),
77
}));
88

9-
import { storage, LocalStorageProxy, MemoryStorageProxy } from '../src/storage'
9+
import { storage, LocalStorageProxy, MemoryStorageProxy } from '../src/storage'
1010

1111
describe('Module: use-localstorage', () => {
1212

@@ -130,7 +130,7 @@ describe('Module: use-localstorage', () => {
130130
const defaultValue = 'i';
131131
const newValue = 'o';
132132
const { result } = renderHook(() => useLocalStorage(key, defaultValue));
133-
133+
134134
expect(result.current[0]).toBe(defaultValue);
135135
expect(localStorage.getItem(key)).toBe(defaultValue);
136136

@@ -144,6 +144,31 @@ describe('Module: use-localstorage', () => {
144144
});
145145
});
146146
});
147+
148+
describe('when a functional update is called', () => {
149+
it('passes the current state to the update function', async () => {
150+
const key = 'functionUpdate';
151+
const defaultValue = 'ImTheDefault';
152+
const newValue = 'ImTheNewValue';
153+
const { result } = renderHook(() => useLocalStorage(key, defaultValue));
154+
155+
act(() => result.current[1](prevValue => {
156+
expect(prevValue).toBe(defaultValue);
157+
return newValue;
158+
}));
159+
expect(result.current[0]).toBe(newValue);
160+
});
161+
162+
it('handles updates to arrays', async () => {
163+
const key = 'arrayUpdate';
164+
const defaultValue = ['A'];
165+
const newValue = 'B';
166+
const { result } = renderHook(() => useLocalStorage(key, defaultValue));
167+
168+
act(() => result.current[1](prevValue => prevValue ? [...prevValue, newValue] : [newValue]));
169+
expect(result.current[0]).toEqual([...defaultValue, newValue]);
170+
})
171+
});
147172
})
148173

149174
describe("when localStorage api is disabled", () => {

0 commit comments

Comments
 (0)