0

Whenever we need to add a new action to our react/redux application, we copy a lot of boilerplate code, I'd like a helper to try and speed up this process, whilst this is a simple example, it's basically all we'll need to automate.

We create an Action interface, Payload interface, the action payload will need to be exported somehow, and the "action" function as well.


interface Payload {
  something: {
    [key: string]: boolean;
  }
}
export interface Action {
  type: 'UniqueRequestKey';
  payload: Payload
}

export const someAction = (payload: Payload): Action => ({
  type: 'UniqueRequestKey',
  payload
});

The above is what we're currently doing, but i feel like a helper method could make this a lot simpler as it's a lot of repetitive code, we're doing this over and over and over again.

I'd like something simple like the following, doesn't have to be exact at all!

const [someAction, Action??] = createAction<'UniqueRequestKey', {
  something: {
    [key: string]: boolean;
  }
}>();

I know the above isn't correct, but if someone could steer me in the right direction that would be great, I've attempted this with the following but it clearly is wrong

type Combine<T, P> = {
  type: T,
  payload: P
};
function createAction<T,P>(): [(payload: P):Combine<T,P> => {}, type: T] {
  return [(payload: P): Combine<T,P> => ({
    type,
    payload
  }), type];
}

2 Answers 2

1

Something like

const [someAction, Action] = createAction<'UniqueRequestKey', {
  something: {
    [key: string]: boolean;
  }
}>();

cannot work, because types aren't something that can be created at runtime (they are only compile-time concepts). But, you can get the type definition from them.

Imagine a simple helper function like this:

function createAction<Type extends string, Payload>(type: Type, payload: Payload) {
  return { type, payload }
}

which can be used to create your own app's actions:

export const someAction = (payload: {
  something: {
    [key: string]: boolean;
  }
}) => createAction('UniqueRequestKey', payload)

which you can extract the type with

type SomeActionType = ReturnType<typeof someAction>

which evaluate to

{
    type: "UniqueRequestKey";
    payload: {
        something: {
            [key: string]: boolean;
        };
    };
}

Now, if you do have a lot of actions, you may want to consider to just put them all in one big object,

const actions = {
  someAction: (payload: {
    something: {
      [key: string]: boolean;
    }
  }) => createAction('UniqueRequestKey', payload),
  someOtherAction: (payload: {
    fooBar: number
  }) => createAction('BlaBla', payload),
}

which you can use to extract all the types like so:

type ActionsCollection = typeof actions

type Actions = ReturnType<
  ActionsCollection[keyof ActionsCollection]
>

where Actions is a big union with all the available actions you have in the application. From there, if you need one particular action, you could just extract it from it

type SomeActionType = Extract<Action, { type: 'UniqueRequestKey' }>
Sign up to request clarification or add additional context in comments.

2 Comments

This is great, taught me a lot! Thankyou very much
is it possible to shorten this again to const someAction = createAction<Payload>('BlaBla'), i've tried returning a function from createActions, but i can't get the intellisense for the "type" to show up as anything other than string @Fed
0

Generally, you should not write something like that yourself, but use the official Redux Toolkit, which not only contains a createAction function, but also a createSlice function which will further reduce your code - and especially your TypeScript usage by a lot.

Generally, modern Redux does not use switch..case reducers, ACTION_TYPES are an implementation details, action creators are autogenerated, you can use mutable reducer logic (in createSlice) and write almost no TypeScript. All in all it's probably 1/4 of the code.

Please follow the official Redux Tutorial to learn current Redux with Redux Toolkit.

3 Comments

It's all well and good to say this, and i agree, but it's for an application that was built long before I jumped onboard, and refactoring to support toolkit will be a nightmare, so i'm trying to introduce some toollkit like features
Then just use toolkit's createAction instead of writing your own - RTK tree-shakes well and if you use no other features they won't be bundled. Also, RTK slices are just reducer functions. You can use them side-by-side in the same store with legacy reducers and slowly modernize the application whenever you touch a reducer. And your legacy code will profit from configureStore adding debugging middleware that watches out for accidental store mutations.
I'll take a look at it, but the application is too large, which will mean we'll have a spagetti mess of some modern some legacy code, i'd rather have the whole store using toolkit or the whole store using custom helpers to achieve a similar thing

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.