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

createAction doesn't support a meta attribute #148

Closed
jmccarrell-lmi opened this issue Jun 21, 2019 · 29 comments
Closed

createAction doesn't support a meta attribute #148

jmccarrell-lmi opened this issue Jun 21, 2019 · 29 comments

Comments

@jmccarrell-lmi
Copy link

And as far as I can tell there is no way to possibly insert it.

not a big deal, any action that has meta attributes, you can just manually create.

If I were to make a proposal, it would be that it would be a second argument.

@markerikson
Copy link
Collaborator

Yep, and it doesn't support customizing payload either, other than passing it in directly. This is something I'd like to handle in some way before calling this 1.0. See #82 .

@markerikson
Copy link
Collaborator

The two main questions are how createAction() should accept callbacks for payload and meta customization, and how createSlice() should accept them.

The obvious inspirations here would be:

https://redux-actions.js.org/api/createaction#createactiontype-payloadcreator-metacreator

https://github.com/ericelliott/autodux#action-creators

Based on that, I'm thinking roughly:

type PayloadCreator = (...args) => Payload;
type MetaCreator = (...args) => Meta;

createAction(
    type : String,
    payloadCreator? : PayloadCreator ,
    metaCreator? : MetaCreator ,
)

createSlice(
    reducers: {
        simpleReducer: ReducerFunction,
        complexReducer: {
             payload? : PayloadCreator ,
             meta? : MetaCreator ,
             reducer : ReducerFunction
        } 
    }
)

Actual TS types to be figured out, of course, but you get the idea.

@phryneas
Copy link
Member

I'm just exploring options right now. Would something like

type PrepareAction<OriginalPayload, Payload, Meta> = (originalPayload: OriginalPayload) => {payload: Payload; meta? : Meta}

createAction(
    type : String,
    prepare?: PrepareAction,
)

createSlice(
    reducers: {
        simpleReducer: ReducerFunction,
        complexReducer: {
             prepareAction: PrepareAction,
             reducer : ReducerFunction
        } 
    }
)

also be acceptable? That could be way easier to type.

@TidyIQ
Copy link

TidyIQ commented Jul 11, 2019

How do we currently workaround the inability to customise the payload with arguments? This seems like an extremely crucial missing feature. Almost every reducer I have needs the payload to be adjusted based on the function parameters.

For example, a reducer that opens an error message snackbar and displays a message. The message can't be hardcoded into the action creator as obviously the error message changes based on what the actual error is.

Or a very common case - saving form input values to state.

@markerikson
Copy link
Collaborator

@TidyIQ : yes, I'm well aware of this issue :) There's an open PR (#149 ) to try to implement that capability, but we're trying to figure out what the best API would look like.

@markerikson
Copy link
Collaborator

Fixed by #149 and out as 0.6.3 . Please let me know if that works for you!

@jonatanpineda
Copy link

I've seen how "prepare" works but i'm trying to add parameters to "meta" without having to use "payload", i'm doing this to make it work:

const teamsSlice = createSlice({
  slice: 'teams',
  initialState,
  reducers: {
    loadTeams: {
      reducer: (state: TeamsState) => {
         state.loading = true
      },
      prepare: (payload?: any) => ({
        payload,
        meta: {
          request: {
            endpoint: '/teams'
          }
        }
      })
    }
  }
});

I want to create that action:

{
  type: 'teams/loadTeams',
  meta: {
    request: {
      endpoint: '/teams'
    }
  }
}

It's possible to do this in createSlice() without having to use payload?.

@markerikson
Copy link
Collaborator

@jonatanpineda : not quite sure what you're asking here.

You don't have to declare any arguments for the prepare callback, and you don't have to return a payload field if you don't want to. However, for whatever object you return, only the payload and meta fields will be included in the final action object.

@jonatanpineda
Copy link

@markerikson I'm just looking to create an action with the meta property using createSlice() style, i guess is like that:

Captura de Pantalla 2019-08-19 a la(s) 10 03 01

but an error is generated:

Captura de Pantalla 2019-08-19 a la(s) 10 14 27

so when i add the payload argument all works:

Captura de Pantalla 2019-08-19 a la(s) 10 18 47

but the final object that i want to create doesn't have payload, i only want to produce this:

{
  type: 'teams/loadTeams',
  meta: {
    request: {
      endpoint: '/teams'
    }
  }
}

how to achieve this correctly?

@markerikson
Copy link
Collaborator

It may be because you're not declaring a type for the action object in the reducer. Then again, it might be that we need to improve our TS types somehow.

@jonatanpineda
Copy link

I'm sorry I didn't show all the error:

Type '{ reducer: (state: TeamsState) => void; prepare: () => { meta: { request: { endpoint: string; }; }; }; }' is not assignable to type 'CaseReducer<TeamsState, any> | EnhancedCaseReducer<TeamsState, any>'.
Type '{ reducer: (state: TeamsState) => void; prepare: () => { meta: { request: { endpoint: string; }; }; }; }' is not assignable to type 'EnhancedCaseReducer<TeamsState, any>'.
Types of property 'prepare' are incompatible.
Type '() => { meta: { request: { endpoint: string; }; }; }' is not assignable to type 'PrepareAction'.
Type '() => { meta: { request: { endpoint: string; }; }; }' is not assignable to type '(...args: any[]) => { payload: any; }'.
Property 'payload' is missing in type '{ meta: { request: { endpoint: string; }; }; }' but required in type '{ payload: any; }'.ts(2322)
createAction.d.ts(16, 5): 'payload' is declared here.

I typed the action with 'AnyAction' in the reducer and the error persists:

Captura de Pantalla 2019-08-19 a la(s) 11 06 17

Probably the project needs to enable that feature, I think it's necessary because of the simplicity of createSlice() we can reduce a lot of redux-typescript stuff and mixed with the ability to add meta properties to use middleware's like redux-offline, it would be such a huge improvement.

@markerikson
Copy link
Collaborator

Okay, I just added #176 to track this.

@markerikson
Copy link
Collaborator

markerikson commented Aug 19, 2019

Stick with that workaround for now. We'll try to get the TS types updated in the near future to allow returning just {meta} by itself.

Note that you should be able to at least skip having payload as an argument, and just return {payload: undefined, meta: whateverValueHere}.

@markerikson
Copy link
Collaborator

@jonatanpineda : after thinking about it further, I've closed #176 . I feel that the payload: undefined workaround is sufficient for now, especially given how complicated the createAction types are already and that almost all use cases would involve passing an actual payload value anyway.

@0w3w
Copy link

0w3w commented Jan 15, 2020

However, for whatever object you return, only the payload and meta fields will be included in the final action object.

I'm wondering why adding that restriction and not simply just send the object that the prepare function returned with all of it's properties (meta, error, etc.).

This is kinda contradictory to this part of the documentation:

Additionally, the object can have a meta and/or an error field that will also be added to created actions. meta may contain extra information about the action, error may contain details about the action failure. These three fields (payload, meta and error) adhere to the specification of Flux Standard Actions.
Here

@markerikson
Copy link
Collaborator

@0w3w : We added support for the error field after this issue, but those are still the only fields that are accepted when returned from the prepare callback.

The point is that the FSA spec only allows {type, payload?, error?, meta?}, so we ignore anything else.

@0w3w
Copy link

0w3w commented Jan 15, 2020

I agree that only FSA properties should be allowed, then, Is there any reason why those properties are not available on the case reducers? I think they should. Accessing error and meta information on the reducer is something I've seen on several projects and is very useful when using FSA.

E.G.

export const doSomethingSlice = createSlice({
    name: 'dosomething',
    initialState: { foo: "bar"},
    reducers: {
        DoSomething: {
            reducer: (state, action) => {
                console.log(action.meta); // <- meta/error is not accesible
            },
            prepare: () => ({ payload: undefined, meta: "something" })
        }
    }
});

Am I missing something? Also, Kudos for the super quick response!

@markerikson
Copy link
Collaborator

Those properties will be accessible, so I'm not sure why you're saying they're not. That's one of the reasons why we made sure to continue passing the entire action object as an argument to reducers, when some other implementations of a createReducer utility only pass the payload field itself. Do you have any kind of a sandbox or project that looks like those fields aren't available?

@phryneas
Copy link
Member

phryneas commented Jan 15, 2020

@0w3w You always need to type action, even with a prepare method present. As it stands, we can check that you are not using a different action than what you return from prepare, but not automatically infer that type.

import {createSlice, PayloadAction} from '@reduxjs/toolkit'

export const doSomethingSlice = createSlice({
  name: 'dosomething',
  initialState: { foo: "bar"},
  reducers: {
      DoSomething: {
          reducer: (state, action: PayloadAction<undefined /*payload*/, string /*type*/, string/*meta*/>) => {
              console.log(action.meta);
          },
          prepare: () => ({ payload: undefined, meta: "something" })
      }
  }
});

@0w3w
Copy link

0w3w commented Jan 15, 2020

@phryneas Bravo! That is what I was missing!
Thanks you both for your quick turnaround times!

@markerikson
Copy link
Collaborator

@phryneas : hmm, not sure I was aware of that. I don't think we've got it covered in the "Usage with TS" page.

@phryneas
Copy link
Member

@phryneas : hmm, not sure I was aware of that. I don't think we've got it covered in the "Usage with TS" page.

Good point. There's no createSlice with prepare example in there. We should add that.

@phryneas
Copy link
Member

phryneas commented Jan 15, 2020

Ah, I see why looking at the type-tests. There I'm only testing the payload and that is just typed exactly as the case without prepare so I didn't bother mentioning it.

My assumption was that the reducers only access the payload as I personally only ever used the other FSA properties from middlewares, never from a reducer. But I guess that's fair game, so definitely needs to go into the docs (and another type test I guess)

0w3w pushed a commit to 0w3w/redux-toolkit that referenced this issue Jan 16, 2020
@0w3w
Copy link

0w3w commented Jan 16, 2020

Hey, so I stepped into another TS error with this:
Screenshot from 2020-01-16 12-20-55

With error:

Type '{ reducer: (state: { foo: string; }, action: PayloadAction<undefined, string, string, never>) => void; prepare: () => { payload: undefined; meta: string; }; }' is not assignable to type 'CaseReducer<{ foo: string; }, { payload: any; type: string; }> | CaseReducerWithPrepare<{ foo: string; }, { payload: any; type: string; }>'.
  Type '{ reducer: (state: { foo: string; }, action: PayloadAction<undefined, string, string, never>) => void; prepare: () => { payload: undefined; meta: string; }; }' is not assignable to type 'CaseReducerWithPrepare<{ foo: string; }, { payload: any; type: string; }>'.
    Types of property 'reducer' are incompatible.
      Type '(state: { foo: string; }, action: PayloadAction<undefined, string, string, never>) => void' is not assignable to type 'CaseReducer<{ foo: string; }, { payload: any; type: string; }>'.
        Types of parameters 'action' and 'action' are incompatible.
          Type '{ payload: any; type: string; }' is not assignable to type 'PayloadAction<undefined, string, string, never>'.
            Property 'meta' is missing in type '{ payload: any; type: string; }' but required in type '{ meta: string; }'.ts(2322)
typings.d.ts(555, 5): 'meta' is declared here.

I think the problem is that the SliceCaseReducer type ... | CaseReducerWithPrepare<State, PayloadAction<any> only accepts ANY.

I forked and tried to fix this by adding additional supported types:

export type SliceCaseReducers<State> = {
  [K: string]:
    | CaseReducer<State, PayloadAction<any>>
    | CaseReducerWithPrepare<State, PayloadAction<any>>
    | CaseReducerWithPrepare<State, PayloadAction<any, string>>
    | CaseReducerWithPrepare<State, PayloadAction<any, string, any>>
    | CaseReducerWithPrepare<State, PayloadAction<any, string, any, any>>
}

But, while my changes fix the issue, it breaks types.test.ts on inferred types:

    Semantic: /home/gmolina/github/redux-toolkit/type-tests/files/createSlice.typetest.ts (73, 19) Parameter 's' implicitly has an 'any' type.

    Semantic: /home/gmolina/github/redux-toolkit/type-tests/files/createSlice.typetest.ts (73, 24) Binding element 'payload' implicitly has an 'any' type.

    Semantic: /home/gmolina/github/redux-toolkit/type-tests/files/createSlice.typetest.ts (147, 19) Parameter 'state' implicitly has an 'any' type.

    Semantic: /home/gmolina/github/redux-toolkit/type-tests/files/createSlice.typetest.ts (155, 19) Parameter 'state' implicitly has an 'any' type.

    Semantic: /home/gmolina/github/redux-toolkit/type-tests/files/createSlice.typetest.ts (190, 17) Parameter 'state' implicitly has an 'any' type.

    Semantic: /home/gmolina/github/redux-toolkit/type-tests/files/createSlice.typetest.ts (261, 17) Parameter 'state' implicitly has an 'any' type.

Here is my proposed change with updated documentation and a test for it.

I didn't create PR cause the tests are not passing. Any guidance on this?

@phryneas
Copy link
Member

This doesn't error for me in this sandbox: https://codesandbox.io/s/brave-sky-ulhsx
Or am I doing something differently from you?

Could you create me a reproducible? I'd like to see the actual error before discussing a possible solution ;)

@0w3w
Copy link

0w3w commented Jan 17, 2020

I'm using strict type checking, changed tsconfig.json to:

{
  "compilerOptions": {
    "module": "commonjs",
    "jsx": "preserve",
    "esModuleInterop": true,
    "sourceMap": true,
    "allowJs": true,
    "lib": [
      "es6",
      "dom"
    ],
    "rootDir": "src",
    "moduleResolution": "node",
    "strict": true,
  }
}

Then the error shows. The project is configured with strict type-checking https://github.com/reduxjs/redux-toolkit/blob/master/tsconfig.json so I think this should work?

As a temporary workaround disabling strictFunctionTypes on the tsconfig fix this error.

@phryneas
Copy link
Member

Sorry, I somehow didn't see your message earlier.

The thing is, the example I linked to you has strict: true - this is the tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "jsx": "preserve",
    "esModuleInterop": true,
    "sourceMap": true,
    "allowJs": true,
    "strict": true,
    "lib": [
      "es6",
      "dom"
    ],
    "rootDir": "src",
    "moduleResolution": "node"
  }
}

So there's gotta be some other difference?

@0w3w
Copy link

0w3w commented Jan 21, 2020

Here's a code sandbox with a reproducible error:
https://codesandbox.io/s/reduxjs-toolkit-issue-148-nsh03

error

I just need some guidance and I may be able to fix it on my branch and create a PR ;)

Also this seems like a new error for this thread, should I create a new Issue?

@phryneas
Copy link
Member

@0w3w sorry, I didn't get to this earlier as I was sick. I took a stab at it and opened #350 . Could you please install the CI build and give it a try if it fixes your problems?

yarn add https://pkg.csb.dev/reduxjs/redux-toolkit/commit/9023d835/@reduxjs/toolkit

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

6 participants