Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helper to wrap state from other hooks? #57

Closed
ianstormtaylor opened this issue Jul 2, 2020 · 4 comments
Closed

Add helper to wrap state from other hooks? #57

ianstormtaylor opened this issue Jul 2, 2020 · 4 comments

Comments

@ianstormtaylor
Copy link

ianstormtaylor commented Jul 2, 2020

Running into a situation that I think would be nicely handled in use-immer since it seems somewhat common...

If you a value is scoped to your component, then the current useImmer works well. But if you don't have control over the value (eg. it's passed from a parent, or it's gotten from local storage, etc.) then you have the deal with the annoyance of using a callback to set the new state.

But it would be nice to be able to do something like:

function Example() {
  // The "user" lives in Local Storage and should be updated on changes.
  // This returns `[user, setUser]`, but you want to use immer to update it.
  const userState = useLocalStorage('user')

  // So you can wrap the state tuple easily and have this new hook handle 
  // the callback logic for you to save.
  const [user, updateUser] = useImmerState(userState)
  
  // Here the `updateValue` knows to call `setValue` under the covers 
  // to save the update in Local Storage.
  updateUser(user => {
    user.name = 'New Name'
  })
}

Since the [value, setValue] pattern is widespread, this new hook would be very flexible and could be used to augment any existing tuple-returning state hook.

What do you think?

@ianstormtaylor
Copy link
Author

An alternative way to handle this nicely would be to allow useImmer to take a second argument that is a callback called whenever the update happens. Which would allow you to write the same example as:

function Example() {
  const [user, setUser] = useLocalStorage('user')
  const [, updateUser] = useImmer(user, setUser)
  
  updateUser(user => {
    user.name = 'New Name'
  })
}

I think this is actually more flexible, so this would be the better solution.

@akshayjai1
Copy link

Hi Ian,

Can you please update the Example component, I am not able to figure out what this component intends to do, and that will also help get more clarity about your issue.

Thank you

@mweststrate
Copy link
Collaborator

@ianstormtaylor I think I get what you are getting at. I think the first solution is nicer, the second one looks incorrect, as immer wouldn't receive the new user after an update.

However, I think this is a simpler solution that doesn't need new api's:

import {produce} from immer

function Example() {
  const [user, _setUser] = useLocalStorage('user')
  const updateUser = useMemo(() => produce(_setUser), [])
  
  updateUser(user => {
    user.name = 'New Name'
  })
}

@Newbie012
Copy link

Newbie012 commented Nov 9, 2020

@mweststrate How would you implement such a thing using reducer?

Edit - I have managed to do so with useEffect:

export function useCart() {
  const [storageCart, setStorageCart] = useLocalStorage("cart", initalCart);
  const [cart, dispatch] = useImmerReducer<Cart>(reducer, storageCart);

  React.useEffect(() => {
    setStorageCart(cart);
  }, [cart, setStorageCart]);

  return [cart, dispatch] as const;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants