redux-toolkit: createSlice hinders intellisense and causes import complexity

Today I started making a trial app with @reduxjs/toolkit and I can see that we’re not going to be able to use it because all of the actions coming out of createSlice are defaulting to type (payload: any): { type: string, payload: any }. Also the issue of circular references mentioned in the docs might cause problems for us and I’d hate to have to define slices differently for edge cases since the system we use now works in all cases.

I don’t think you’re going to be able to change this without completely reworking createSlice. However, I just wanted to show you what I’m coming from for comparison and to give you some insight from a senior dev at a large consulting firm that uses React/Redux heavily.

We define all of our actions, reducers and selectors for each slice of state in a separate file. Our project structure looks something like the tree shown below. With this very simple and straightforward structure, we’re able to do everything we need to. Importing another slice’s actions is a matter of doing import { OtherSliceActions } from "../otherSlice/actions"; We also get perfect intellisense and as you can see - zero external imports in each file.

I know this is more boilerplate but it’s really simple, non-magical boilerplate that we have snippets for. Good intellisense and uniformity is more important to us than less boilerplate.

Project Structure

  • assets/
  • components/
  • layouts/
  • lib/
  • pages/
  • state/
    • auth/
    • prefs/
    • ui/
      • actions.js
      • selectors.js
      • state.js

In state/ui/actions.js:

const type = {
  UI_LOADING_SET: "UI_LOADING_SET",
  UI_NOTIFICATION_SHOW: "UI_NOTIFICATION_SHOW",
  UI_NOTIFICATION_HIDE: "UI_NOTIFICATION_HIDE",
};

export const UIActions = {
  type,

  hideNotification() {
    return { type: type.UI_NOTIFICATION_HIDE };
  },

  setUILoading(value) {
    return { type: type.UI_LOADING_SET, value };
  },

  showError(message = "There was an error processing your request.") {
    return UIActions.showNotification(message, "error");
  },

  showNotification(message, variant, duration) {
    if (duration === undefined && variant !== "error") {
      duration = 15000;
    }
    return { type: type.UI_NOTIFICATION_SHOW, message, variant, duration };
  },

  showUpdated(message = "Your changes have been submitted.") {
    return UIActions.showNotification(message);
  },
};

In ui/selectors.js:

export function uiLoading(state) {
  const {
    ui: { loading },
  } = state;
  return {
    uiLoading: loading,
  };
}

export function uiNotification(state) {
  const {
    ui: { notification },
  } = state;
  return {
    uiNotification: notification,
  };
}

In ui/state.js:

import { UIActions } from "./actions";
/**
 * UI state (**NOT** persisted).
 * @example
 * {
 *    loading: false,
 *    notification: { duration: number, message: string, variant: "" | "error" }
 * }
 */
export const UIState = {
  name: "ui",
  persist: false,
  defaults: {
    loading: false,
  },
  handlers: {
    [UIActions.type.UI_LOADING_SET](state, { value }) {
      return {
        ...state,
        loading: value,
      };
    },
    [UIActions.type.UI_NOTIFICATION_HIDE](state) {
      return {
        ...state,
        notification: undefined,
      };
    },
    [UIActions.type.UI_NOTIFICATION_SHOW](state, { type, ...notification }) {
      return {
        ...state,
        notification,
      };
    },
  },
};

Some files not shown:

  • state/
    • index.js - exports states.js, store.js and anything we need from redux, react-redux plus an enhanced version of connect.
    • states.js - exports all slice states as an array. exports all slice actions and selectors.
    • store.js - imports states.js array, builds reducers, sets up persist, exports store.

I can hear the criticisms already. “If I want better intellisense why not use TypeScript.” and “Circular imports are indicative of a bad design.” and so forth… suffice it to say though that we get cornered into a bad design once in a while and we don’t do TypeScript for our frontends for reasons. Our opinions on Redux usage are just too different. So, I’m going to have to break up with you RTK - our relationship was very brief but I wish you the best of luck and thank you for your efforts!

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (1 by maintainers)

Most upvoted comments

Yes, we just use JSDoc to give intellisense where needed. We don’t like TypeScript on the frontend because from what we’ve seen, it adds about 30% more work from just setting it up, typing everything and then working around it when typings don’t describe reality.

I didn’t see the prepare callback thing before - there was no intellisense for that - but I guess I should have read the docs more closely. This is not a rebuttal in any way - but I just don’t like it. It’s more complex than what we’re doing.

I left this issue open for discussion; please close it at your convenience.

So… you’re using JSDoc, and not TS? Seems like that involves even more effort than it would to just use TS. (Not saying you have to use TS, just that it’s a somewhat surprising approach to use.)

FWIW, both createAction and createSlice do support multi-arg action creators via the “prepare callback” syntax:

https://redux-toolkit.js.org/api/createAction#using-prepare-callbacks-to-customize-action-contents

https://redux-toolkit.js.org/api/createSlice#customizing-generated-action-creators

https://redux-toolkit.js.org/tutorials/intermediate-tutorial#implementing-todo-ids