jotai icon indicating copy to clipboard operation
jotai copied to clipboard

`SetStateAction<T>` should accept `T | typeof RESET`

Open V-Mann-Nick opened this issue 3 years ago • 3 comments

When using atomWithHash or atomWithReset it would be nice if the state setter function would also accept the RESET symbol as return type.

Problem

Suppose I want to encode an isOpen flag into the url hash but also remove it entirely from the URL when it's false.

import { useAtom } from 'jotai'
import { atomWithHash, RESET } from 'jotai/utils'
import React, { useCallback, useMemo } from 'react'

export const useUrlHashToggle = (key: string) => {
  const isOpenAtom = useMemo(() => atomWithHash(key, false), [key])
  const [isOpen, setIsOpen] = useAtom(isOpenAtom)

  const toggle = useCallback(
    () => setIsOpen(isOpen ? RESET : true),
    [isOpen, setIsOpen]
  )

  return { isOpen, toggle }
}

const Component: React.FC = () => {
  const { isOpen, toggle } = useUrlHashToggle('key')

  const expensiveComponents = useMemo(
    () => <button onClick={toggle}>{/* expensive stuff */}</button>,
    [toggle]
  )

  return (
    <div>
      {expensiveComponents}
      {isOpen && <div>{/* other stuff */}</div>}
    </div>
  )
}

As the toggle function depends on isOpen it will change reference when isOpen is toggled. This way expensiveComponents will have to rerender on state change although they don't necessarily depend on isOpen.

Desired Usage

It would be great if it were possible to return RESET in a state setter function:

export const useUrlHashToggle = (key: string) => {
  const isOpenAtom = useMemo(() => atomWithHash(key, false), [key])
  const [isOpen, setIsOpen] = useAtom(isOpenAtom)

  const toggle = useCallback(
    () => setIsOpen((prevIsOpen) => prevIsOpen ? RESET : true),
    [setIsOpen]
  )

  return { isOpen, toggle }
}

Now the toggle function would not change reference.

V-Mann-Nick avatar Aug 07 '22 08:08 V-Mann-Nick

Thanks for suggestion. Yeah, it's probably possible. For background, our original intention was that the symbol is used more internally.

The simple toggle example isn't very convincing because it can be setIsOpen((prev) => !prev). Do you have another example that you face in practice?

Anyway, do you want to open a PR?

dai-shi avatar Aug 07 '22 09:08 dai-shi

Thanks a lot for the quick answer.

The simple toggle example isn't very convincing because it can be setIsOpen((prev) => !prev).

Well in this case I think it is, as using RESET with atomWithHash removes the entire item from the URL hash.

Suppose in the above example I use useUrlHashToggle('isOpen'):

  • The URL hash after setIsOpen(true) would be #isOpen=true.
  • After setIsOpen(false) it would be #isOpen=false.
  • After setIsOpen(RESET) it would be #.

The last case is desired as it would be nice to keep the URL hash tidy.

Anyway, do you want to open a PR?

I really would like to, but I currently do not have the time to do so and I'm also going to be on vacation for more than a month. I'll be back in October and I could perhaps do it then.

V-Mann-Nick avatar Aug 07 '22 10:08 V-Mann-Nick

Alright. If anyone else wants to try, please feel free.

dai-shi avatar Aug 07 '22 11:08 dai-shi

Alright. If anyone else wants to try, please feel free.

Attempted to tackle issue here: https://github.com/pmndrs/jotai/pull/1346

Not sure if typing is optimal, but I hope this is the outcome @V-Mann-Nick desired

austinwoon avatar Aug 13 '22 15:08 austinwoon

@austinwoon Sorry for the late reply. Just checked and I can confirm my use case is perfectly covered with this change. Thanks a lot 👏

V-Mann-Nick avatar Oct 20 '22 18:10 V-Mann-Nick