atlassian / react-sweet-state

Shared state management solution for React
https://atlassian.github.io/react-sweet-state/
MIT License
867 stars 55 forks source link

createActions helper #224

Open theKashey opened 5 months ago

theKashey commented 5 months ago

With actions being defined aside of createStore it might become a little tricky to type them.

In many cases one had to write something like

const actions = {
    resetTriggerPosition: (): Action<State> => () => ({ setState }) => {
            setState({  });
        },
}

The modern way to write the same with less TS involved can use satisties operator

const actions = {
    resetTriggerPosition: () => () => ({ setState }) => {
            setState({  });
        },
}  satisfies Record<
    string,
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (...args: any[]) => Action<State>
>;

Works, but is it making anything better?


what about creating a helper function createActions in the form of

const createActions =<TState extends object>(): <TActions extends Record<string, ActionThunk<TState, TActions>>(actions: TActions): TActions => actions;

That simple, the same interface as createStore accepts.

This fill change action definition to

```tsx
const actions = createActions<State>()({
    resetTriggerPosition: () => () => ({ setState }) => {
            setState({  });
        },
} )

Something like https://www.typescriptlang.org/play?#code/ATCWDsBcFMCcDMCGBjawDK1LsomAeAFRz2gD5gBvAKBBAApxoB3EmALmAAVFZJREAGyJtyASk4A3APagAJgG5awAL7VlkAJ4AHNAHEsokbhgUAvMHpjgZigCVoiOdPCDNx0mSUadaHNNhoAEFkfhcg7VAPUxsqZRAAZ0MTaE5MbBTo8iU6YABzZNJOAwzSLK944DlQBO08ZAALTiJgaAAPGHA5BOAQsPAsgBpgRHBNYdHNMjJ6SpAUfsIGgFdwAGtOQkrrW2AHSGXYcEJfIgqQFW8QLV1e0NAXfEriFMHngGEXXAg4LlhpbQ9CwyeRvXKEfaHcAANSEyzQwNkcmAAB9uP8ALY1aD4EFyMjKcyWSqISJpSABYL3cKRcpgujIL6IH6wP4AhKbT5QZlMVn-QHKHYUCFYKGwwTwq7AG5oPoPY4rdZPcGiekgQhylw9dqdbp7aCM2ByfAJSCwCB5YaahWrNZDYAa6ngBLTQmxegAOi9vDyHJGYyFd369smEzGFWojOdkGAyECpGtQKyrQ60C6PWkACMAFYGyAzCTAIiJlO6noOQ3G03m8CWoPypa2+2O-ou6b0Bbyv0truBzta7xR03S6DDixxxwwRP4ShtdjVi0qAv0GjXUeQdhWEC7FdJUowFSBveiFdzgCMw007AATIfqIeFEA

Wondering if with modern TS we can remove the first function call, it "bind" State variable.

albertogasparin commented 5 months ago

While I wouldn't mind such API, there has been questions around the constrain of bundling of too many actions on store creation, and the fact that such pattern could not only increase bundle size but also cause unnecessary circular imports.

So, if we were to think of some better APIs for actions, I would probably move towards decomposing them and making them a per-hook:

const store = createStore({ initialState }); // no actions
const useMyStore = createHook(store, { actions: ... }); // specific per hook

or via dispatch like API at the consumer location:

import { action, useMyStoreAction } from './my-store'
// ... inside the component
const triggerChange = useMyStoreAction(action);

or

import { useDispatch } from 'react-sweet-state'
import { store, action } from './my-store'
// ... inside the component
const triggerChange = useAction(store, action);

or

import { useMyStore, action } from './my-store'
// ... inside the component
const [state, dispatch] = useMyStoreViaDispatch();
const triggerChange = () => dispatch(action(...))

The benefit of forcing the import of the action on the consumer side is that it makes more explicit the dependency. The per-hook pattern can be a good way to enable this decomposition when needed, while the dispatch exposes a bit more of the implementation detail... Unsure about the best path forward.

theKashey commented 5 months ago

Splitting actions from state was always my first preference, but I dont see a good usecase with action being "reused" for multiple stores. This defines the "type flow" as store -> action, which is roughly the opposite of the current configuration. Ie option 1 and 2, but not 3 and 4 are they cause "store to meet action" in the user space.

// option 1.5, right in the middle of 1 and 2
const useMyStore = createHook(store, action);