easy-peasy icon indicating copy to clipboard operation
easy-peasy copied to clipboard

Cannot use dispatch or action-typed hooks on complex store model, but useStoreState still works?

Open futureproofd opened this issue 4 years ago • 5 comments
trafficstars

Hello, I've recently made a huge jump from v2.6.6 to the latest (v5.0.3) of easy-peasy and now my previously working dispatch hook no longer works. The error I'm receiving is:

const dispatch = useStoreDispatch();
// note this isn't a typescript compilation error. The property of the dispatch object isn't defined
TypeError: Cannot read property 'config' of undefined
> 61 | const dispatchConfig = dispatch.resource.config;

To give some background information, the following is how I've built the store model and hooks to consume via my React component. To create the model for the store, I'm doing so using a series of class instantiations. From what I've read in the docs, it is perfectly acceptable as easy-peasy will walk through the model to get actions/thunks :

(store.ts)

export interface StoreModel {
    app: AppStore;
    resource: ResourceStore;
}

const {
    useStoreActions,
    useStoreState,
    useStoreDispatch
} = createTypedHooks<StoreModel>();

export { useStoreActions, useStoreDispatch, useStoreState };

// I have quite a few more model classes that are getting instantiated here, but cut for brevity.
export const store = createStore({
    app: new AppStore(),
    resource: new ResourceStore()
)}

To get an idea of what the model looks like, the ResourceStore class is defined as follows:

(resource.store.ts)

export class ResourceStore {
    public list = new ListResource();
    public config = new ConfigResource();
}

And the nested ConfigResource instantiation on the ResourceStore object looks like this:

(config.resource.ts)

export class ConfigResource extends Resource<any> {
    public getAppInfo = thunk<ConfigResource, any, StoreModel>(
        async (actions) => {
           return fetch(`${process.env.PUBLIC_URL}/someFile.json`)
              .then((res) => res.json())
              .then((appInfo) => {
                  actions.setAppInfo(appInfo);
                  return appInfo;
              });
        }
     );

   // there are some other thunks and actions here...
}

The store has been exposed via the StoreProvider higher up in the component tree (App.tsx), but this is the first component where the store state is to be retrieved and actions/thunks are to be dispatched:

(MyApp.component.tsx)

export const MyApp = () => {
  const history = useHistory();
  const [routes, setRoutes] = useState<JSX.Element>();

  // note the useStoreState hook is working as expected
  const [loading, someState ] = useStoreState((state) => [
    state.app.loading,
    state.resource.config.someState
  ]);

  const dispatch = useStoreDispatch();
  // This is where the error occurs: 
  const dispatchConfig = dispatch.resource.config;

The useStoreState hook is working as expected, but the resource property does not exist on dispatch. I believe the typedHooks are working correctly because while hovering over each, they resolve as follows:

const dispatch: Dispatch<StoreModel, AnyAction>
    (property) resource: RecursiveActions<ResourceStore>
        (property) config: RecursiveActions<ConfigResource>

I've read through the docs and have tried accessing my store via actions as follows, but get the same error (TypeError: Cannot read property 'config' of undefined)

  const configAction = useStoreActions((actions) => {
    // Also, the debug result is an empty object {}, but the types also appear to resolve while hovering over each property
    console.log(debug(actions));
  });

One last thing: II've also made sure that my state is resolving properly via the useStoreState hook. If I call any particular piece of the model, they are retrieved:

// this works
const [
    modal,
    notification,
    loading,
    group,
    user,
    configResource,
  ] = useStoreState((state) => [
    state.modal,
    state.notification.target,
    state.app.loading,
    state.resource.user.group,
    state.resource.user.target,
    state.resource.config
  ]);

I can see the config resource getAppInfo thunk in state. I just can't seem to call dispatch on it. Any help would be much appreciated. Thanks.

futureproofd avatar May 03 '21 19:05 futureproofd

Update: I've tried reverting to v2.6.6 with the latest version of Typescript (4.2.4) and now I'm getting the following error message on a number of my files that reference pieces of my store:

TS2589: Type instantiation is excessively deep and possibly infinite.

I know this TS error, in particular, has been resolved because the errors disappear when I run npx tsc on v5.0.3. So now I'm stuck between using v2.6.6 and an old version of Typescript or ideally trying to resolve this issue in v5.0.3.

futureproofd avatar May 12 '21 13:05 futureproofd

Apologies for the delay, and my sympathies for the stress you went under with this. If you are still struggling with this would you mind creating a minimal example for me within CodeSandbox?

ctrlplusb avatar Oct 15 '21 07:10 ctrlplusb

Hi, thanks for getting back to me. I understand you must be very busy. I've officially moved on from the position I was in to solve this issue, but my old colleague has most likely taken the reins on this upgrade task. I will reach out to her to see if she's available to provide more information 👍

futureproofd avatar Oct 15 '21 12:10 futureproofd

Hello, sorry for the delay. Here is the codesandbox link, where dispatch hook is not working.

payal-bhs avatar Oct 26 '21 02:10 payal-bhs

Hello, any update for this issue ?

payal-bhs avatar Feb 11 '22 11:02 payal-bhs

Hello, any update for this issue ?

Hi, sorry for the delay. I'm a little confused looking at the provided code sandbox.

useStoreDispatch() returns a dispatch function, but it is used as an object in the example:

  const dispatch = useStoreDispatch();
  //                        👇 dispatch is a function - so this will explode
  const dispatchConfig = dispatch.resource.config;

If you want to use dispatch in this case, you need to provide the type you want to dispatch, e.g.

  const dispatch = useStoreDispatch();
  
  useEffect(() => {
    dispatch({ type: '@thunk.resource.config.getAppInfo(start)' })
  }, []);

I don't really know your usecase here, but if you can useStoreActions here instead - I think that would be an easier approach:

  const { getAppInfo } = useStoreActions(store => store.resource.config);
  
  useEffect(() => {
    getAppInfo()
  }, []);

jmyrland avatar Jun 20 '23 14:06 jmyrland