web-ridge/react-ridge-state

[idea] API can be even simplier

mixvar opened this issue ยท 13 comments

Nice lib! I am surprised how little code is needed to work around the problem with React Context API.

One thing that I found missing is the ability to pass a function receiving current value into the setter (just like when using React.useState).

But then I realised this can be done like so:

setRidgeState(state, fn(getRidgeState(state)))

and it will work as long as useRidgeState(state) is called in the component

This is a bit awkward and redundant though.

Would an API like this make sense?

useRidgeSubscription(state) // re render on change, no return
state.get() // getter for the value
state.set(...) = // setter for the value

Now there is only one way of getting and setting the state without extra imports needed.
JS getter/setter could be used instead of normal functions.
A loose idea, probably too big of a change anyway.

I agree it could be simpler ideally if you don't have to import 2 things
I was thinking instead of

import { useRidgeState } from "react-ridge-state";
import { cartProductsState } from "../CartState";
const [cartProducts, setCartProducts] = useRidgeState(cartProductsState);

Something like this

import { cartProductsState } from "../CartState";
const [cartProducts, setCartProducts] = cartProductsState.useState();

Global state would be

import { cartProductsState } from "../CartState";
cartProductsState.set(newState)
cartProductsState.get()

I agree we need to add support for a callback with previous state!

And we need some custom selector which only re renders if own selection change
e.g.

import { cartProductsState } from "../CartState";
const filteredProducts = cartProductsState.selector(state => state.filter(p => p.category === 'nice'))

nice :)

I had a go at reimplementing this, take a look: https://gist.github.com/mixvar/406db7c621da8988d9d2174535b578c8

I added TS Nominal hack to hide implementation details and entangle function which is similar to the selector idea
Though it ended up having more API surface, so still probably not ideal.

Cheers

Thanks looks nice. I think we can still make some changes as it's not that heavily used yet. I need to give it some thought. Ideally we keep this very small API but I'm already getting some issues with unneeded re-renders when using array state, so I think it'll be good to make some changes which optimizes for custom selectors and easier api.

I'm probably for sure going with 1 import less is always better. It could be even adding some JS since user need to import less it will save them bytes because using it is less code.

V4 has a simpler api, let me know what you think :)

I like the more flexible setter function ๐Ÿ‘
state.use is cool too.

what is the purpose of state.useValue() though? isn't const [v] = state.use() good enough?

also there is a little 'bug' in readme

function CounterComponent() {
  const [count, setCount] = globalCounterState.use();
  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={() => setCount(c + 1)}>Add 1</button>
    </div>
  );
}

should be onClick={() => setCount(c => c + 1)} probably :)

It is the same in background indeed, maybe it was not needed but I liked it since it looks 'cleaner'. I fixed docs.

Just chiming in on the selector idea. Would it be possible to make it part of use and useValue instead of introducing new selector api?

const counterOne = counterState.useValue(s => s.counterOne);
const [counterTwo, setCounter] = counterState.use(s => s.counterTwo);

That's a good idea but I feel like it's strange in the 'use()' since it looks like the setCounter set's that value while it set's the root value instead of the sub value.

// outdated see comment below
counterState.useSelect(s=>s.deepThing.one)

Decided to go with useSelector instead of useSelect because people are more used to that naming if they come from Redux

// if you only want to subscribe to part of your state (this example the first product)
const cartProducts = cartProductsState.useSelector((state) => state[0]);

// custom comparison function (only use this if you have heavy child components and the default === comparison is not efficient enough)
const cartProducts = cartProductsState.useSelector(
  (state) => state[0],
  (a, b) => JSON.stringify(a) === JSON.stringify(b)
);

@mixvar btw both local and global supports callbacks now with the previous state in it!

I think the current api can be stable for a long period if any other ideas occur let's discuss the in a new issue ๐Ÿ˜ƒ .
Thanks for all the suggestions and improvements!