npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

effin-redux

v0.2.11

Published

Extend redux-toolkit with side effect handling and layered reducers

Downloads

13

Readme

Effin Redux

effin-redux logo

Extend redux-toolkit with effects and more.

Check out the live demo (ideally using Redux DevTools) and the code behind it.

Test status npm version

Features

Allow both horizontal and vertical combination of reducers

  • combineReducers combines reducers horizontally, with each of them getting their own slice of the state. This is great for organizing your state, but it provides no solution for when you need to access the state of a slice from another slice. The Redux FAQ itself has no single recommendation, but mentions writing a custom combineReducers implementation as a possible solution.
  • reduceReducers chains reducers vertically, letting them update the same piece of state, one after the other. This is great for acting on the same piece of state, but provides no help when it comes to modularizing the shape of your state.

effin-redux combines the two. This allows you to create slices that depend on the state returned from other slices.

Usage

When passing your reducers to configureStore(), use the custom combineSlices() implementation of effin-redux:

// app.ts
const slices = [counterSlice, infoSlice, fizzBuzzSlice] as const; // const is mandatory, and the order matters
const appReducer = combineSlices(slices);
export const store = configureStore({ reducer: appReducer });
export type AppState = ReturnType<typeof store.getState>

Make sure to also create the helpers that allow reading the state of other slices:

export const { readAppState, readOriginalAppState } = getHelpers<AppState>();

In your slices, you can use these helpers to get access to the root state, not just the slice's state:

// slices/fizzBuzz.ts
import { readAppState } from "$app";

export const fizzBuzzSlice = createSlice({
  name: "fizzBuzz",
  initialState,
  reducers: {},
  extraReducers: (builder) => builder.addMatcher(
    (action) => action.type.startsWith("counter"),
    (state) => {
      // state only refers to the state of this one slice
      // but with readAppState, you can get access to the state of other slices too:
      const appState = readAppState(state);
      const currentNumber = appState.counter.count;
      state.value = calculateFizzBuzz(currentNumber);
    },
  ),
});

Note:

  • readAppState() returns the instance of the state which could already have been modified by any other slices during the handling of this action. If you use it, be aware that the order in which your slices are executed matters.
  • readOriginalAppState() returns the app state from before any slice reducers have been executed. If you only use this one, the order of your reducer slices does not matter.

References

Allow calculating side effects in the reducer

Redux Toolkit, the opinionated redux library includes the Thunk middleware by default and recommends using it for side effects. However, triggering side effects by dispatching thunks from within the application goes against what redux is trying to achieve!

The application's logic should live in its reducer. This is already true for state transitions. But calculating which side effect to trigger and with what arguments is part of the logic too, and it's deeply connected to those state transitions. Why should it be placed in UI components?

effin-redux adds helpers that let your reducer describe what side effects it wants to trigger, and then triggers them on your behalf. The effects themselves are described by the same thunks that redux-toolkit provides.

Usage

Use the provided configureStore() implementation to get side effects:

import { configureStore } from "effin-redux";

export const store = configureStore(appReducer);

Alternatively, if you need to use the original configureStore() function from redux-toolkit, you can patch your reducer manually via effin-redux's withEffects() helper:

import { withEffects } from "effin-redux";

export const store = configureStore<AppState>({ reducer: withEffects(appReducer) });

Define your effects for your slices:

import { createEffectInputs, createEffects, forSlice, addEffect } from "effin-redux";

const inputs = createEffectInputs<AppState>()({
  fetchExternalNumber: () => {
    return fetch("https://www.randomnumberapi.com/api/v1.0/random?count=1")
      .then((response) => response.json())
      .then((parsedResponse: [number]) => {
        return parsedResponse[0];
      });
  },
  setSpecificNumber: async ({ requestedNumber }: { requestedNumber: number }) => {
    return { requestedNumber };
  },
});

const effects = createEffects<CounterState>()(inputs, forSlice("counter"));

Then schedule them in your reducer:

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    externalNumberRequested: (state) => {
      addEffect(state, effects.fetchExternalNumber());
      addEffect(state, effects.consoleLog());
    },
    specificNumberRequested: (state, action: PayloadAction<{ requestedNumber: number }>) => {
      addEffect(state, effects.setSpecificNumber({ requestedNumber: action.payload.requestedNumber }));
    },
  }),

And handle them like any other async thunk:

  extraReducers: (builder) =>
    builder
      .addCase(effects.fetchExternalNumber.pending, (state) => {
        state.isWaitingForExternalNumber = true;
      })
      .addCase(effects.fetchExternalNumber.fulfilled, (state, action) => {
        state.isWaitingForExternalNumber = false;
        state.count = action.payload;
      })
      .addCase(effects.setSpecificNumber.fulfilled, (state, action) => {
        state.count = action.payload.requestedNumber;
      }),
  ),
});

Using the Redux Developer Tools, you will be able to inspect what effects has been scheduled by your reducer, and also when it is being executed.

References

Recover lost state type in createSlice reducers

When defining slices via createSlice(), the type of the state argument within the reducers object should be correctly inferred. Unfortunately, this feature broke with a TypeScript change.

effin-redux adds helpers to work around this - you still have to add the type explicitly, but now at least you only have to do it once, not in every case.

Usage

Just wrap your slices' reducers and extraReducers with the appropriate helpers:

import { createExtraReducers, createReducers } from "effin-redux";

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: createReducers<CounterState>()({
    // ...case reducers
  }),
  extraReducers: createExtraReducers<CounterState>((builder) => {
    // ...extra reducers
  }),
});

References

Installation

npm install effin-redux

The package is distributed as CJS and comes with TypeScript type definitions included.