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

redux-remove

v0.1.1

Published

Redux Remove helps you gradually replace Redux with isolated state providers that are powered by React Context and `useReducer()`. The main idea is that reducers are no longer composed into a single reducer and provider at the top level of your app. Inste

Downloads

7

Readme

Redux Remove

Redux Remove helps you gradually replace Redux with isolated state providers that are powered by React Context and useReducer(). The main idea is that reducers are no longer composed into a single reducer and provider at the top level of your app. Instead each reducer is wrapped in a state manager that can be composed together so you only using the parts of your application logic that you need.

Aside from breaking apart application state into composable chunks, the aim is to keep the API as similar as possible to redux and react-redux to ease the migration away from Redux. Eventually you won't need this library at all either and it will walk away knowing its job is done.

Installation

# If you use npm:
npm install react-redux

# Or if you use Yarn:
yarn add react-redux

Setup

In most Redux apps there is a single "Root" reducer that composes all your child reducers together.

import { combineReducers } from "redux";
import { likesReducer } from "/reducers/likes";
import { userReducer } from "/reducers/user";

const rootReducer = combineReducers({
    user: userReducer,
    likes: likesReducer,
});

Defining your state managers

At first you'll want to leave all your Redux code as it is. You'll start by wrapping each reducer in a state manager. You can either write a new reducer from scratch or re-use the Redux reducer you already have.

import { createStateManager } from "redux-remove";
import { initialState, userReducer } from "/reducers/user";

export const userManager = createStateManager(userReducer, initialState);

Putting your state into context

State managers have a context provider component. Any part or page of your app can compose only the providers it needs.

import { likesManager } from "/state-managers/likes";
import { userManager } from "/state-managers/user";

const App = () => (
    <userManager.Provider>
        <likesManager.Provider>
            <Homepage />
        </likesManager.Provider>
    </userManager.Provider>
);

Accessing state/dispatch via hooks

Any component nested under the <userManager.Provider> component has access to the user state in context. The state manager exposes state and dispatch via some hooks:

  • useSelector(selectorFn) accepts a selector function with receives the manager state and returns a value
  • useDispatch() returns the manager dispatch function
import { userActions } from "/actions/user-actions";
import { userManager } from "/state-managers/user";

const Avatar = () => {
    const username = userManager.useSelector((state) => state.name);
    const dispatch = userManager.useDispatch();

    const renameUser = () => {
        dispatch(userActions.setUsername("Jimmy Spoons"));
    };

    return (
        <span>
            {username}
            <button onClick={renameUser}>Rename to "Jimmy Spoons"</button>
        </span>
    );
};

The connect() HOC

Redux Remove gives you a connect() HOC that looks very similar to React Redux. The difference is that you need to provide it the state managers it cares about. It will only have access to the state of those managers.

Connect accepts the same three factory functions that React Redux does:

  • mapStateToProps(state, ownProps): StateProps
  • mapDispatchToProps(dispatch, ownProps): DispatchProps
  • mergeProps(stateProps, dispatchProps, ownProps): MergeProps

Let's look at a connector for a page component that combines data from two state managers user and teams.

mapStateToProps(state, ownProps): StateProps

If we want to simply provide some state to the wrapped component we can pass a mapStateToProps function.

import { connect } from "redux-remove";
import { TeamPage } from "/components/TeamPage";
import { userManager } from "/state-managers/user";

const ConnectedTeamPage = connect({ user: userManager, teams: teamsManager })(
    (state) => ({
        userName: state.user.name,
        teams: state.teams,
    }),
)(TeamPage);

Notice in the example above that the user state was accessible as state.user and the teams state was accessible as state.teams. The state is nested under the same keys that you use when passing the managers to connect().

mapDispatchToProps(dispatch, ownProps): DispatchProps

mapDispatchToProps is how we allow the wrapped component to dispatch actions to the state managers. The difference here from React Redux is that there is no longer one single dispatch() function that dispatches the action to every single reducer. Instead the dispatch argument is a map of dispatch functions, one for each manager you provide. This means that you must dispatch actions to each manager individually.

const ConnectedTeamPage = connect({ user: userManager, teams: teamManager })(
    undefined, // no need to pass mapStateToProps
    (dispatch) => ({
        joinTeam: (teamId: string) => {
            // tell the user manager the user has joined a team
            dispatch.user(actions.joinTeam(teamId));

            // tell the team manager the user has joined a team
            dispatch.team(actions.joinTeam(teamId));
        },
    }),
)(TeamPage);

mergeProps(stateProps, dispatchProps, ownProps): MergeProps

mergeProps behaves exactly the same as React Redux and can be used to further combine the return values of mapStateToProps and mapDispatchToProps.

const ConnectedTeamPage = connect({ user: userManager, teams: teamManager })(
    (state) => ({
        userId: state.user.id,
        teams: state.teams,
    }),
    (dispatch) => ({
        joinTeam: (userId: string) => (teamId: string) => {
            dispatch.user(actions.joinTeam(teamId));
            dispatch.team(actions.joinTeam(teamId));
        },
    }),
    (stateProps, dispatchProps, ownProps) => ({
        ...ownProps,
        teams: stateProps.teams,
        joinTeam: dispatchProps.joinTeam(stateProps.userId),
    }),
)(TeamPage);

TypeScript

Redux Remove is written in TypeScript and types are inferred automatically from the definition of your reducers when using both hooks or the connect() HOC. However there are a few cases with connect() that require some manual typing.

Typing ownProps

ownProps are the props that you need to pass manually into the wrapped component. By default they're the result of taking the base component props and removing all the keys that are returned from mapStateToProps, mapDispatchToProps and mergeProps.

type TeamPageProps = {
    userName: string;
    teams: Team[];
    onLoad: () => void;
}

const TeamPage: React.FC<TeamPageProps> = (props) => { ... }

const ConnectedTeamPage = connect({ user: userManager, teams: teamsManager })(
    (state) => ({
        userName: state.user.name,
        teams: state.teams,
    }),
)(TeamPage);

// Prop type of ConnectedTeamPage will be:
// Omit<TeamPage, "userName" | "teams">
// which equals { onLoad: () => void }

However, if any of your factory functions make use of the ownProps argument then you need to type ownProps manually.

type OwnProps = {
    currentTeamId: string;
};

const ConnectedTeamPage = connect({ user: userManager, teams: teamsManager })(
    (state, ownProps: OwnProps) => ({
        userName: state.user.name,
        teams: state.teams,
        currentTeam: state.teams[ownProps.teamId],
    }),
)(TeamPage);

defining connect params outside of connect()

You may want to define your mapStateToProps, mapDispatchToProps, mergeProps functions as named functions and pass them into connect(). This is what that might look like:

import { CombinedDispatch, CombinedState, connect } from "redux-remove";
import { TeamPage } from "/components/TeamPage";
import { userManager } from "/state-managers/user";

const managers = { user: userManager };

type OwnProps = {
    currentTeamId: string;
};

const mapStateToProps = (
    state: CombinedState<typeof managers>,
    ownProps: OwnProps,
) => ({
    userName: state.user.name,
    teams: state.teams,
    currentTeam: state.teams[ownProps.teamId],
});

const mapDispatchToProps = (
    dispatch: CombinedDispatch<typeof managers>
) => ({
    onJoinTeam: (teamId: string) => dispatch.user(actions.joinTeam(teamId))
})

const mergeProps = (
    stateProps: ReturnType<typeof mapStateToProps>,
    dispatchProps: ReturnType<typeof mapDispatchToProps>,
    ownProps: OwnProps,
) => {
    ...
}

const ConnectedTeamPage = connect(managers)(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
)(TeamPage);

Roadmap

  • Support object style mapDispatchToProps
  • Support dispatch middleware like redux-thunk
  • Performance testing