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
41 changes: 41 additions & 0 deletions src/useSafeState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Dispatch, SetStateAction, useCallback } from 'react'
import useMounted from './useMounted'
import { AsyncSetState } from './useStateAsync'

type StateSetter<TState> = Dispatch<SetStateAction<TState>>

/**
* `useSafeState` takes the return value of a `useState` hook and wraps the
* setter to prevent updates onces the component has unmounted. Can used
* with `useMergeState` and `useStateAsync` as well
*
* @param state The return value of a useStateHook
*
* ```ts
* const [show, setShow] = useSafeState(useState(true));
* ```
*/
function useSafeState<TState>(
state: [TState, AsyncSetState<TState>],
): [TState, (stateUpdate: React.SetStateAction<TState>) => Promise<void>]
function useSafeState<TState>(
state: [TState, StateSetter<TState>],
): [TState, StateSetter<TState>]
function useSafeState<TState>(
state: [TState, StateSetter<TState> | AsyncSetState<TState>],
): [TState, StateSetter<TState> | AsyncSetState<TState>] {
const isMounted = useMounted()

return [
state[0],
useCallback(
(nextState: SetStateAction<TState>) => {
if (!isMounted()) return
return state[1](nextState)
},
[isMounted, state[1]],
),
]
}

export default useSafeState
2 changes: 1 addition & 1 deletion src/useSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class ObservableSet<V> extends Set<V> {
/**
* Create and return a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) that triggers rerenders when it's updated.
*
* ```tsx
* ```ts
* const ids = useSet<number>([1,2,3,4]);
*
* return (
Expand Down
59 changes: 59 additions & 0 deletions test/useSafeState.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { mount } from 'enzyme'
import React, { useEffect, useState } from 'react'
import { act } from 'react-dom/test-utils'
import useSafeState from '../src/useSafeState'
import useStateAsync from '../src/useStateAsync'

describe('useSafeState', () => {
it('should work transparently', () => {
let state

function Wrapper() {
state = useSafeState(useState(false))
return null
}

const wrapper = mount(<Wrapper />)

expect(state[0]).toEqual(false)

act(() => {
state[1](true)
})
expect(state[0]).toEqual(true)

wrapper.unmount()

act(() => {
state[1](false)
})
expect(state[0]).toEqual(true)
})

it('should work with async setState', async () => {
let state

function Wrapper() {
state = useSafeState(useStateAsync(false))
return null
}

const wrapper = mount(<Wrapper />)

expect(state[0]).toEqual(false)

await act(async () => {
await state[1](true)
})

expect(state[0]).toEqual(true)

wrapper.unmount()

await act(async () => {
await state[1](true)
})

expect(state[0]).toEqual(true)
})
})