Giter VIP home page Giter VIP logo

redux-shelf's Introduction

Redux Shelf

Build Status

Avoid writing boilerplate code by using Redux Shelf APIs to manage both Entity and Communication state of an application using Redux's reducers.

Influences

Typically, a React application contains several types of state. James K. Nelson, on this great article, has identified 5 of them. Redux Shelf focus on two of the 5 types: Data state (optionally named Entity state) and Communication state. Below you can find a summary of each type:

Data State

"Data state covers information which your application temporarily stores about the big wide world. That is, it covers your business data."

"Every piece of received Data has a type, and a selector which exactly specifies which data was received."

Communication State

"This type of state covers the seemingly simple yet somewhat thorny information which represents things like loading spinners and error messages."

"Communication state is the status of any not-yet-complete requests to other services."

"This means that all of the following are communication state:

  • The type/selector for any Data you expect to receive
  • The type, selector and expected change of any operations you have requested on Data
  • The error messages for anything which didn’t go quite as planned."

Getting Started

Prerequisites

redux-shelf is build on top of redux, therefore, it's important to understand its concepts.

Installing

yarn add redux redux-shelf

or

npm install redux redux-shelf

Configuration

// reducers/index.js
import { combineReducers } from 'redux';

import { entities, communication } from 'redux-shelf';

export default (appReducers = combineReducers({
  entities,
  communication,
}));

Usage

The following example uses React components.

// userActions.js
import { entities, communication, normalize } from 'redux-shelf';

// Here I assuming that you're using some middleware to handle
// asynchronous actions, for example, Redux Thunk
export function fetchUsers() {
  return async (dispatch) => {
    dispatch(communication.starting('users'));

    try {
      const url = 'endpoint_to_get_users_data';
      const request = await fetch(url);
      const payload = request.json();

	  // redux-shelf requires that the server data is normalized
      dispatch(entities.set('users', normalize(payload)));
      dispatch(communication.done('users'));
    } catch (e) {
      dispatch(communication.fail('users', e));
      console.log(e);
    }
  };
}

...

// UserList.jsx
export const UserList = ({ loading, error, userIds }) => {
  if (error) {
    return <div>Failed to load users</div>
  }

  if (loading) {
    return <div>Loading...</div>
  }

  return (
    <div>
      {userIds.map(userId => <UserItem key={userId} userId={id} />)}
    </div>
  );
);

export default connect(
  ({ entities, communication }) => {
    const {
      loading,
      error,
    } = communication.of('users');

    const userIds = entities.idsOf('users');

    return {
      loading,
      error,
      userIds,
    };
  },
)(UserList);

...

// UserItem.jsx
export const UserItem = ({ name }) => (
  <span>
    {name}
  </span>
);

export default connect(
  ({ entities }, { userId }) => {
    const user = entities.contentOf('users', userId);

    return {
      name: user.name,
    };
  },
)(UserItem);

API

Bellow you can find the API documentation of Redux Shelf.

Entity

  • set(type, payload): Overrides the current state of an Entity.
  • update(type, payload): Merge the current state of an Entity with new state.
  • remove(type, selector): Remove a record of an Entity.
  • idsOf(type): Returns the array of ids of an Entity type provided as parameter.
  • contentOf(type, selector): Returns content object of an specific Entity record, identified by its type and selector both provided as parameters.
  • of(type, selector?): It's an alias for idsOf and contentOf methods. When only type parameter is given to of method it behaves like idsOf call, while when selector parameter is also provided of method will behave like contentOf call.

Note: By using Entity Actions API we're assuming that you'll normalize Entity data on ids/content form. So, you must either use normalize function provided by the library or use another one that works similarly (check Utils section).

Communication

  • starting(type, selector?): Sets communication with STARTING status for the given entity type and selector.
  • done(type, selector?): Sets communication with DONE status for the given entity type and selector.
  • fail(type, selector|error, error): Sets communication with FAIL status for the given entity type, selector and/or error.
  • cancel(type, selector?): Sets communication with CANCEL status for the given entity type and selector.
  • of(type, selector?): Returns an object with loading and error.

Utils

  • normalize(payload, key?): Normalizes a given payload to ids/content shape. If key parameter is not provided, the function will normalize the payload by id property, assuming that it has it. The valid values for payload parameter are: An object or an array of objects. If the value provided as payload parameter is invalid, the function will return a default normalized object { ids: [], content: {} }. See the examples below:
const payload = [{ id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }];
console.log(normalize(payload, 'id'));
// console output
/*
  {
    ids: [1, 2],
    content: {
      1: { id: 1, name: 'Product 1' },
      2: { id: 2, name: 'Product 2' },
    },
  }
*/

...

const payload = { id: 1, name: 'Product 1' };
console.log(normalize(payload));
// console output
/*
  {
    ids: [1],
    content: {
      1: { id: 1, name: 'Product 1' },
    },
  }
*/

...

const payload = [
  { identifier: 1, name: 'Product 1' },
  { identifier: 2, name: 'Product 2' },
  { id: 3, name: 'Product 2' },
  true,
  null,
  42,
];
console.log(normalize(payload, 'identifier'));
// console output
/*
  {
    ids: [1, 2],
    content: {
      1: { identifier: 1, name: 'Product 1' },
      2: { identifier: 2, name: 'Product 2' },
    },
  }
*/

License

MIT

redux-shelf's People

Contributors

lemes avatar guilhermespopolin avatar renerbaffa avatar

Stargazers

Cezar Andrew Villegas Santarin avatar Douglas Tyler avatar Guilherme Maciel avatar Nicolas C. Barbosa avatar Vinicius Heitor avatar Fernando JS Silva avatar Rafael Musetti de Souza avatar Fernando JS Silva avatar Ariel Garabetti M de Oliveira avatar  avatar  avatar  avatar José Sérgio Mendes Pereira Junior avatar Guilherme Lemmi avatar

Watchers

James Cloos avatar  avatar  avatar  avatar

redux-shelf's Issues

Project scope definition

In order to guarantee correct focus on our goals during all library development process, it ncessary that we establish its scope first, by responding the follow question: "What specif problem we wanna solve?"

Proposal: changes on API to handle normalizr schema

Given the approaches described bellow, let's discuss over and decide how we should handle schema. My idea is to use Paul Armstrong's normalizr lib to give us schema support, where one could provide the defined schema to an action as an argument so that internally we use to normalize the entry payload. Later the normalized payload would be dispatched so that our reducer could handle the changes.

Main changes

  1. Forget about ids and content and go with normalizr entities and result. Relationships between entity schemas will give the users the arrays of ids. Also, we cannot predict and cover all the possible scenarios against a data list such as a server sorted and paginated list of items;

  2. set and update entities actions must also accept a schema object from normalizr lib, e.g: actions.update(string|schema, data). If a string is given, the user must provide the data already formated;

  3. One entity action will be able to update one or more entities on store;

  4. Remove all not-more-needed APIs (normalize, idsOf ...);

To be considered

  1. Do not bundle normalizr together with the library. The user will still have the option to not use the schema;

  2. We do not care whether the relationships are valid;

Examples

Schemas

import { schema } from 'redux-shelf';

// Define a users schema
export const user = new schema.Entity('users');

// Define your comments schema
export const comment = new schema.Entity('comments', {
  commenter: user
});

// Define your article 
export const article = new schema.Entity('articles', {
  author: user,
  comments: [comment]
});

Action

import { entities, communication } from 'redux-shelf'
import { article as articleSchema } from './schemas';

export function fetchArticleById(articleId) {
  return async (dispatch) => {
    dispatch(communication.starting('article', articleId));

    try {
      const request = await fetch(`my-api/article/${articleId}`);
      const payload = await request.json();

      dispatch(entities.update(articleSchema, payload));

      dispatch(communication.done('article', articleId));
    } catch (e) {
      dispatch(communication.fail('article', articleId, e));
    }
  };
}

JSON payload from api

{
  "id": "123",
  "author": {
    "id": "1",
    "name": "Paul"
  },
  "title": "My awesome blog post",
  "comments": [
    {
      "id": "324",
      "commenter": {
        "id": "2",
        "name": "Nicole"
      }
    }
  ]
}

Dispatched payload normalirzed through your provided schema. Entities reducer will iterate over entities keys and update/set each entity. result key will be ignored and therefore not store. However, as users can still provide they own custom reducers, they are able to do whatever the heck they want with it.

{
  result: "123",
  entities: {
    "articles": { 
      "123": { 
        id: "123",
        author: "1",
        title: "My awesome blog post",
        comments: [ "324" ]
      }
    },
    "users": {
      "1": { "id": "1", "name": "Paul" },
      "2": { "id": "2", "name": "Nicole" }
    },
    "comments": {
      "324": { id: "324", "commenter": "2" }
    }
  }
}

entities.update behavior

I was thiking and maybe it should be better provided the id of the record that we want to update in a separated parameter. Considerer the examples below:

// store
users: {
   000: {
      id: 000,
      name: 'John',
      age: 26,
   },
   001: {
      id: 001,
      name: 'Phil',
      age: 20,
   },
}

With the currently API, when we want to update data of an specific record we do this:

// action
...
const payload  { id: 001, age: 30 };
entities.update('users', payload);

...
// store
...
users: {
   ...
   001: {
      id: 001,
      name: 'Phil',
      age: 30, // an specific change was made
   },
}

However, in case that entitiy doesn't have an id property on it's content, this way of updating things will result on adding a id property on it. Check the example below:

// store
...
message_status {
   // message id
   asbd212: {
      likes_count: 21,
      sentAt: 1519224838060,
   },
}

// action
...
const payload = {  id: asbd212, likes_count: 50 };
entities.update('message_status', payload);

// updated store
...
message_status {
   // message id
   asbd212: {
      id: asbd212, 
      likes_count: 50,
      sentAt: 1519224838060,
   },
}

So, how to proceed on this cases? Maybe message status is being treated as entity but it shouldn't, if we strictly follow entity state definition on James article:

Every piece of received Data has a type, and a selector which exactly specifies which data was received.

what do you guys think? Am I at Disney? 😐 💩

@renerbaffa
@lemes


PS: Maybe we have another problem with this approah of updating things. Check the example below:

// store
...
users: {
   000: {
      userId: 000,
      name: 'John',
      age: 26,
   },
}

// action
...
const payload = {  id: userId, name: 'Jonas' };
entities.update('users', payload);

// updated store
...
users: {
   000: {
      id: 000,
      userId: 000,
      name: 'Jonas',
      age: 26,
   },
}
//

Provide a normalize function

redux-shelf API expects data normalized on an specific shape in order to work properly. With this, a normalized function should be provided as a helper function of redux-shelf usage.

This function should work as described below:

normalize([{ id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }], 'id');
// should output
// {
//    ids: [1, 2],
//    content: {
//      1: { name: 'Product 1' },
//      2: { name: 'Product 2' }
//    }
// } 

normalize({ id: 1, name: 'Product 1' }, 'id');
// should output
// {
//    ids: [1],
//    content: {
//      1: { name: 'Product 1' }
//    }
// }     

Update 'of' method documentatin on Entity Actions API

Method of documentation on Entity Actins AP section should be updated. The present explanation should be replaced by descriptions of two other methods: idsOf and contentOf. Additionally, of method should be descripted as an alias to the idsOf and contentOf methods.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.