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-crud-factory

v0.3.7

Published

Functional factory to create Redux actions and reducers that interface with a backend

Downloads

27

Readme

Redux Crud Factory

Redux Crud Factory is a declarative toolkit that allows for creating CRUD (create, read, update, and delete) actions that allow a React app to interact with a backend api. State management is handled by Redux using Redux Thunk middleware. The api calls are performed using Axios. The backend can be as simple as an api based on a ViewSet using the Django Rest Framework.

Features

  • Request a list of objects from the backend, create a new object, modify or delete an existing object in the list. Either one of these operations automatically modify the state (redux store) and the components will be updated.
  • Allow for nested state: Imagine books are ordered by author and books received from the backend have an author key. By supplying a parent = 'author' option in the config to books, all book objects received from the backend will be assumed to have this parent key and are ordered by the value of the objects parent key in the state. This creates multiple separated states for each value of parent (i.e. for each author). By supplying the parent as a prop to a component, either as an id or an object, this component will receive the parents child state: <BooksList author="Stephen King" />.
  • Select single object (think radio box) or multiple objects (think check box): The list of objects, either with or without parents, can be selected by adding a select = 'single' or select = 'multiple' option to config.actions.

The simplest full CRUD can be created like this

import reduxCrudFactory from 'redux-crud-factory';
import axios from 'axios';

// Our object name 'farmAnimals' must be camelCase
export const factory = reduxCrudFactory({
  axios,
  config: {
    farmAnimals: {
      route: 'https://example.com/api/farm-animals',
      actions: {
        create: true,           // createFarmAnimal(obj) will perform post request to https://example.com/api/farm-animals
        get: true,              // getFarmAnimal(42) will perform get request to https://example.com/api/farm-animals/42
        getList: true,          // getFarmAnimalsList() will perform get request to https://example.com/api/farm-animals
        update: true,           // updateFarmAnimal(obj) will perform patch request to https://example.com/api/farm-animals/42
        delete: true,           // deleteFarmAnimal(obj) will perform delete request to https://example.com/api/farm-animals/42
      },
    },
   },
});

Or generate more elaborate cruds with many bells and whistles

import reduxCrudFactory from 'redux-crud-factory';
import axios from 'axios';
import otherAxios from './OtherAxios';

// Our object name 'farmAnimals' must be camelCase
export const factory = reduxCrudFactory({
  axios: axios.create({                                   // Default axios instance that is used for each factory in the config object
    baseURL: 'https://example.com'
  }),
  onError: console.error,                                 // Log errors to console or use react-toastify
  actions: {                                              // Default actions for all the factories
    get: true,
    getList: true,
    create: true,
    update: true,
    delete: true,
  },
  config: {
    farmAnimals: {
      route: '/api/farm-animals/',                      // Trailing slash here
      actions: { get: true },                           // Duplicate get action as it is already available, can be removed
    },
    plants: {
      route: '/api/plants',                             // No trailing slash on this route
      actions: { delete: false },                       // Add or disable actions if you like to don't repeat yourself
      axios: otherAxios,                                // Maybe this route needs authentication
      
      includeActions: {                                 // Create custom actions!
        sellPlant: {                                    // The following functions are now generated: getPlant, getPlantsList, createPlant, updatePlant & sellPlant
                              
          method: 'post',                               // Any http method required
          
          route: ({ id }) =>                            // route can be string or function called when you call sellPlant(plant, { args: { your stuff.. }, params ))
            `/api/plants/${id}/sell`,                   // Request params are handled automatically, args can be used in route, prepare or onResponse
            
          prepare: (plant, { args, params } =>          // Do something with additional args or params before data is sent to api
            ({ ...plant }), 
                                                        // Handle response.data from api. 
          onResponse: (plant, { updatePlant, getFarmAnimalsList, params, args }) =>
            {
              updatePlant(plant);                       // All redux actions are available here. Update the plant in the state
              getFarmAnimalsList({                      // Also request farmAnimals based on this plant id. State update will be automatic as
                params: plant: plant.id,                // getFarmAnimalsList() is a standard action
               });
            },
          },
        },
      },
    },
  },
});

Show what we got in the console

> console.log(factory)

{
  actionTypes: {farmAnimals: {…}, plantsAndVegetables: {…}},
  actions: {farmAnimals: {…}, plantsAndVegetables: {…}},
  actionsStripped: {farmAnimals: {…}, plantsAndVegetables: {…}},
  mapToProps: {farmAnimals: ƒ, plantsAndVegetables: ƒ},
  mapToPropsStripped: {farmAnimals: ƒ, plantsAndVegetables: ƒ},
  reducers: {farmAnimals: ƒ, plantsAndVegetables: ƒ},
  config: {farmAnimals: {…}, plantsAndVegetables: {…}},
}

The object factory contains the following components

actionTypes:

All the Redux action types, for instance { getList: 'GET_FARM_ANIMALS_LIST', create: 'CREATE_FARM_ANIMAL', ... }. Note that name farmAnimals is used to create human readable Redux action types. Single/plural is automatically handled including words like category/categories.

actions:

All available functions that can trigger Redux actions with formatted names: { getFarmAnimalsList: ƒ, createFarmAnimal: ƒ, updateFarmAnimal: ƒ, ... }.

actionsStripped:

Same as actions above but with stripped down names: { getList: ƒ, create: ƒ, update: ƒ, ... }.

mapToProps:

The functions that gets data from the store into our React component: { farmAnimalsList: { ... }, farmAnimalsIsLoading: false, farmAnimalsHasErrored: false, ... }. The formatted lis

mapToPropsStripped:

Same as mapStateToProps however with stripped down names: { list: { ... }, getListIsLoading: false, getListHasErrored: false, ... }.

reducers:

The Redux reducer function that will handle state managementfarmAnimals: ƒ }. This object can be easily used with combineReducersfrom Redux (see example below) and leads to a *single source of truth* for the object name:combineReducers({ ...factory.reducers, other: otherReducer })`

config:

The same config object as supplied however expanded with all the available options.

Connect to the redux store

import { Provider } from 'react-redux';
import { applyMiddleware, createStore, combineReducers } from 'redux';
// Redux thunk is required middleware
import thunk from 'redux-thunk';

// Log each redux action without changing the state. Not required but this allows us to see what's going on under the hood.
const consoleLogReducer = (state = null, { type, ...action }) => {
    console.log(type, action, state);
    return state;
}

const rootReducer = (state, action) => consoleLogReducer(
    combineReducers({
        ...factory.reducers
        // Add more reducers here...
    })(state, action),
    action
);

// The `Root` component used in our React App.
const Root = ({ children, initialState = {} }) => {
    const middleware = [thunk];

    const store = createStore(
        rootReducer(,
        initialState,
        applyMiddleware(...middleware)
    );

    return (
        <Provider store={store}>
            {children}
        </Provider>
    );
};

Receive data from an api

Here the data will be saved in the redux store like this { farmAnimals: { ... } }. An axios instance is required and needs to be supplied.

In the simple example above the specification for a complete CRUD are created. The api response is assumed to be:

[
    {
        id: 1,
        type: 'donkey',
        name: 'Benjamin'
    },
    {
        id: 2,
        type: 'goat',
        name: 'Muriel'
    }
]

Objects in the redux store

{
    farmAnimals: {
        list: {
            1: {
                id: 1,
                type: 'donkey',
                name: 'Benjamin',
            },
            2: {
                id: 2,
                type: 'goat',
                name: 'Muriel',
            },
        },
        createError: null,
        createIsLoading: false,
        deleteError: null,
        deleteIsLoading: false,
        getListError: null,
        getListIsLoading: false,
        updateError: null,
        updateIsLoading: false
    },
}

Note that the list object is not an array but a key/value pair based on the id even though the api returns a list. Of course this id field can be modified.

Get data to components

Now we can get the data and Redux functions in our component (FarmAnimalsList.js).

// import farmAnimalsFactory from ...
import { Component } from 'react';
import { connect } from 'react-redux';

// Feel free to use functional components instead.
class FarmAnimalsList extends Component {
    componentDidMount() {
        this.props.getFarmAnimalsList();
    }

    render() {
        const { getFarmAnimalsIsLoading, farmAnimalsList, createFarmAnimal } = this.props;
        
        if (getFarmAnimalsIsLoading || !farmAnimalsList) return 'Loading farm animals...';
        
        return <ul>
            {Object.values(farmAnimalsList).map((farmAnimal, key) =>
                <li key={key}>
                    The {farmAnimal.type} is called {farmAnimal.name} 
                </li>
            )}
        </ul>;
    }
};

export default connect(
    factory.mapToProps.farmAnimals,
    factory.actions.farmAnimals
)(FarmAnimalsList);

and

import React, { Component } from 'react';
import FarmAnimalsList from './FarmAnimals';

class App extends Component {
    render() {
        return (
            <Root>
                <FarmAnimalsList />
            </Root>
        );
    }
}

export default App;