svrcekmichal / redux-axios-middleware

Redux middleware for fetching data with axios HTTP client
MIT License
920 stars 96 forks source link

Action as promise #68

Closed PeterKottas closed 7 years ago

PeterKottas commented 7 years ago

This issue/question arose from my previous #67. I struggle to get the action returned as a promise. As there is no example that I know of, I wanted to ask how it should be configured properly. right now I have (using typescript)

export const actionCreators = {
    setCurrentRole: (id: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
        dispatch(new SetCurrentRole({ request: { url: `role/${id}/setCurrent`, method: "PUT" } }));
    }
};

And then on my button I have

onClick={(e) => this.props.setCurrentRole(5)}

This however returns undefined (as it seems to be void function) not a promise as hinted by documentation

Props are injected by redux-connect. Code just for completness sake

export default connect<{
    roles: RolesStore.RolesState
},
    typeof RolesStore.actionCreators, SidebarAccountPropsCustom>(
    (state: ApplicationState) => ({
        roles: state.roles
    }), // Selects which state properties are merged into the component's props
    RolesStore.actionCreators                 // Selects which action creators are merged into the component's props
    )(SidebarAccount);

These are my middlewares:

var middlewares: Middleware[] = [axiosMiddleware(client, axiosMiddlewareOptions), thunk, typedToPlain];
    if (windowIfDefined) {
        const loggerMiddleware = (createLogger as any)();
        middlewares.push(loggerMiddleware);
        if (history) {
            middlewares.push(routerMiddleware(history));
        }
    }

axiosMiddlewareOptions just include setup for authentication.

Hope to get to the bottom of this, cheers!

PeterKottas commented 7 years ago

Btw, dispatch also returns undefined, not promise.

PeterKottas commented 7 years ago

If dispatch returned a promise as mentioned in docs, that would actually resolve my #67 as well! Digging into the lib and hoping to find the solution while waiting for response :)

nmaves commented 7 years ago

I thought the best way to help would be to show a quick example. Feel free to open up the console and see that a promise is returned and that you can see the console.log() from that promise.

https://codesandbox.io/s/kmlww4nwqo

PeterKottas commented 7 years ago

Thanks man, definitely works there. Good to see that for sure, although as simplified as it is, it's rather complicated to understand why it won't work for me. I feel it might have something to do with the fact that I am using action creators where the dispatch and getState is injected for me. Not using store directly.

nmaves commented 7 years ago

try putting a return in front of your dispatch

PeterKottas commented 7 years ago

That indeed worked. I was sure I've tried that before but I must have missed it ... Facepalm

As a bonus, this is what I ended up with. It might help somebody else

export const actionCreators = {
    getCurrentRole: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
        dispatch(new GetCurrentRole({ request: { url: Config.apiUrlGetCurrentRole, method: "GET" } }));
    },
    getRoles: (): AppThunkAction<KnownAction, Promise<GetRolesResponse>> => (dispatch, getState) => {
        dispatch(new GetRoles({ request: { url: Config.apiUrlGetRoles, method: "GET" } })).then((response) => console.log(response));
    },
    setCurrentRole: (id: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
        dispatch(new SetCurrentRole({ request: { url: Config.apiUrlSetCurrentRole.replace("{id}", id.toString()), method: "PUT" } }));
    }
};

Where I changed AppThunkAction from

export interface AppThunkAction<TAction> {
    (dispatch: (action: TAction) => void, getState: () => ApplicationState): void;
}

to

export interface AppThunkAction<TAction, TDispatchResponse = void> {
    (dispatch: (action: TAction) => TDispatchResponse, getState: () => ApplicationState): void;
}

All good with the universe, happy to close this and thanks @nmaves !

nmaves commented 7 years ago

I am having trouble reading the TypeScript as I am not that cool yet. But it looks like you are using a thunk. If that is the case then you really have two middlewares that you need to worry about. This can get a bit messy but it should work no problem.

This is a thunk that does not have a return, but the chaining will still work for the action handled by this middleware.

const fetchUsers = (dispatch, getState) => {
  dispatch({
    request: {
      url: "/users"
    }
  }).then(response => {
    console.log('chained response', response)
  })
}

There is really no point to that thunk as it could just be a simple dispach but let's say you have more you want to do in that thunk. Then you need to handle the returned value from the thunk yourself.

const fetchUsers = (dispatch, getState) => {
  return new Promise(resolve => {
    dispatch({
      request: {
        url: "/users"
      }
    }).then(response => {
      console.log('chained response', response)

      resolve(response)
    })
  })
}
nmaves commented 7 years ago

Awesome news @PeterKottas ! Glad to help.

PeterKottas commented 7 years ago

Yeah typescript can look like abuse of angular brackets sometimes :) But it does a lot of good for you once you get used to it. This luckily was quite simple at the end