Giter Site home page Giter Site logo

react-state-context's Introduction

React State Context

Travis build status npm version Test Coverage gzip size

Lightweight state management using React Context.

✔ Built on React primitives
✔ Provides a familiar API
✔ Designed with a minimal learning curve in mind
✔ Reasonable file size (~2kb gzipped)

Motivation

When you are getting started with React, storing all of your application state within individual Components' state tends to work well. Component state is a good solution for storing data.

A limitation of component state is that it can be tedious to share it between components that are not near one another within your application's component tree. This problem may become more pronounced as your application grows, and as some of your data needs to be made available to a larger number of separated components.

React provides an API to solve this problem: Context. Context is a mechanism to more easily share data between components, even when they are not close.

As delightful as the Context API is, it is a low-level tool, so using it directly can be a little verbose sometimes. It also doesn't provide opinions on how it should be used, so it can take some time to figure out an organized system for working with it. Lastly, it has some caveats that can trip you up. For these reasons I created React State Context.

React State Context is a thin wrapper around Context that provides a small amount of structure. This structure helps reduce the boilerplate that you must write, and it also helps you to stay organized. Plus, when you use State Context, you can be confident that you are avoiding the caveats that accompany using Context directly.

Installation

Install using npm:

npm install react-state-context

or yarn:

yarn add react-state-context

Concepts

React State Context has three concepts.

StateContext

A StateContext is a wrapper around a normal React Context object. Like a regular Context object, it has two properties: Provider and Consumer.

You use StateContexts in the same way as regular Context. If you have used the new Context API, then it should feel familiar to use StateContexts. If you haven't, don't worry. If I was able to learn it, then you can, too!

💁 The React documentation on Context is a great resource. It can be helpful to familiarize yourself with the content on that page before using State Context.

What is different about a StateContext is that the value that the Consumer provides you with has the following structure:

{
  state,
  ...actions
}

State and actions are the other two concepts of React State Context. Let's take a look!

State

Every StateContext object has an internal state object. Behind the scenes, it is a regular Component's state object. When you render a StateContext.Consumer, the value passed to the render prop will include a state attribute.

<MyStateContext.Consumer>
  {value => {
    console.log('The current state is:', value.state);
  }}
</MyStateContext.Consumer>

Like a React Component's state, the StateContext state must be an object or null.

Actions

Actions are functions that you define, and they are how you modify the state. If you have used Redux, then you can think of them as serving a similar role to action creators.

To update state, return a new value from your action. The returned value will be shallowly merged with the existing state.

Let's take a look at an example action:

export function openModal() {
  return {
    isOpen: true,
  };
}

When you call an action from within your application, you can pass arguments to it. Let's use this to create an action to toggle a modal state based on what is passed into the action:

export function toggleModal(isOpen) {
  return { isOpen };
}

Sometimes, you may need the previous state within an action. In these situations, you can return a function from your action. This function will be called with one argument, setState. Use setState to update the state as you would using a React Component's setState:

export function createTodo(newTodo) {
  return function(setState) {
    setState(prevState => {
      return {
        todos: prevState.todos.concat([newTodo])
      );
    });
  };
}

Note that setState differs from the Component setState in that there is no second argument.

💁 Heads up! The actions API was inspired by redux-thunk. If you have used that API, you may notice the similarity. In redux-thunk, the thunks are passed the arguments (dispatch, getState). In this library, you are passed (setState).

Along with state, the actions that you define will be included in the value that you receive from the Consumer:

<MyStateContext.Consumer>
  {value => {
    console.log('I can add a todo using:', value.createTodo);
    console.log('I can delete todos using:', value.deleteTodo);
  }}
</MyStateContext.Consumer>

Once you feel comfortable with these concepts, you are ready to start using React State Context.

API

This library has one, default export: createStateContext.

createStateContext( actions, [initialState] )

Creates and returns a StateContext.

  • actions [Object]: The actions that modify the state.
  • [initialState] [Object|null]: Optional initial state for the StateContext.
import createStateContext from 'react-state-context';
import * as todoActions from './actions';

const TodoContext = createStateContext(todoActions, {
  todos: [],
});

export default TodoContext;

Use a StateContext as you would any other Context.

import TodoContext from './contexts/todo';

export function App() {
  // To begin, you must render the Provider somewhere high up in the Component tree.
  return (
    <TodoContext.Provider>
      <SomeComponent />
    </TodoContext.Provider>
  );
}
import TodoContext from './contexts/todo';

export function DeeplyNestedChild() {
  // From anywhere within the Provider, you can access the value of the StateContext.
  return (
    <TodoContext.Consumer>
      {value => {
        console.log('The current state is', value.state);
        console.log('All of the todos are here', value.state.todos);

        console.log('I can add a todo using:', value.createTodo);
        console.log('I can delete todos using:', value.deleteTodo);
      }}
    </TodoContext.Consumer>
  );
}

FAQ

What version of React is required?

You need to be using at least React v0.14.

Although the new Context API was introduced in 16.3.0, this library is built using the excellent create-react-context library, which polyfills the API for older React versions.

Why would someone use this over Redux?

The reason that I initially started using Redux was to more easily share data between components. Although Redux can seem like a simple system once you become familiar with it, the number of concepts it has can make it daunting to newcomers. At least, that's how I felt when I was learning it.

For me, React State Context solves the problems that I originally used Redux for in a more straightforward way, which is why I think a solution like this seems promising.

What does one lose by migrating away from Redux?

The Redux library supports middleware, and it enables time travel debugging, which are both things that you do not get from React State Context. If you rely heavily on those features of Redux, then this library may not be suitable for your needs.

Outside of the Redux source code itself, there is an enormous community around that library. There are considerable benefits to using a library that has such a large number of users, and you will lose that community by switching to this library, or most other alternative state management libraries, for that matter.

With that said, React State Context is built on top of React's built-in Context API. Although the new Context API is likely not very familiar to most React developers today, we believe that that will change as time goes on.

How is this different from Unstated?

Unstated is a fantastic library, and it served as inspiration for this library. The primary difference is that Unstated introduces new concepts for state management, like Container and Subscribe. One of the design goals of React State Context was to avoid introducing additional concepts whenever possible in an effort to reduce the learning curve.

We believe that we avoided introducing those new concepts while still getting a remarkably similar developer experience. Perhaps you will feel the same way!

Contributing

Are you interested in helping out with this project? That's awesome – thank you! Head on over to the contributing guide to get started.

react-state-context's People

Contributors

jamesplease avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-state-context's Issues

Return thunk value to caller

This will allow more useful abstractions to be built with react-state-context, and it makes it behave more like redux-thunk, which is a nice bonus.

Add guides

  • Thunk guide (don't nest setState calls!

Because React State Context is a thin wrapper around Context, many things such as best practices apply to both Context and State Context.

When should one use React context?

First, consider how localized your data is. If it just needed in a one or few components that are near to one another in the tree (such as a parent and a child), then component state is worth considering.

If you need information across distant components in your application, then that is when it is a good idea to consider using context.

What sorts of things can go in context?

You can put anything that you would like into context. In my experience, there are two kinds of data in React applications:

  1. UI data, such as whether a modal is open. Interacting with this data is typically synchronous.
  2. Remote data, such as a list of movie resources in a database. Interacting with this data is typically asynchronous.

Based on my current understanding, the state + context pair is, and will remain, the go-to approach for the first kind of data.

For the second kind, React Suspense is forthcoming, and that may change the best practices.

How should contexts be organized?

One thing that I about React Context is that it encourages different contexts for different groupings of information. Many popular libraries work in a different way: you have a single store that is divided up into sections.

With context (and therefore, State Context), you should have one context for each logical grouping of data. For instance, if you have some information related to a particular section of the UI, then that may be one area of context. And then if you have some data related to, say, a logged-in user, you might have another area of context for that data.

It's up to you how you divide it up. In general, though, you don't want to have a single context for all of your information.

Using lots of contexts is ugly. There is so much nesting.

Agreed. I made React Composer to help out with that.

New Redux-inspired action API

This API would be an improvement for actions, I think:

const actions = {
  // When you do not need the previous state, you can return an object
  updateNumber(newNumber) {
    return {
      number: newNumber
    };
  },

  // When you need the previous state, return a thunk
  incrementNumber = () => {
    return setState => {
      setState(prevState => {
        number: prevState.number + 1
      });
    };
  }
}

Pros

  • More like redux-thunk, which many people have experience with
  • Much simpler usage when you do not need the previous state

Add examples

  • Basic example
  • Async action
  • Compare with Redux

Long-running actions

This library supports long-running actions right now. What this means is that you can do something like:

const createTodo = setState => newTodo => {
  setState({ loading: true });
    
  asyncCallToMakeTodo(todo)
    .then(val => {
      setState(prevState => {
        const clonedTodos = [...prevState.todos];

        return {
          todos: clonedTodos.push(newTodo),
        };
      });
    });   
};

The question is: should this functionality be removed?

The original intention of this library was to really serve as a replacement Redux for the use case of needing to share data between distant components. Long-running actions, like thunks, are an important feature of Redux, mostly for fetching data from remote places.

The argument to not support this is stems from the forthcoming Suspense update. Suspense caches are a way to store information outside of React. It may make sense for all remote data to go inside of a Suspense cache, and then only use StateContext for UI (synchronous) state that needs to be shared between distant components.

What would the API look like without this? Dan A. suggested something like getDerivedStateFromProps, where you return the new value for state. Presumably I'd be able to find a way to get a prevState value into the action with that API. I think it would actually be a pretty cool API if the Suspense story made sense.

Anyway, I'm not too sure right now!

v1.0.0

I'll feel comfortable releasing v1.0.0 after #3 is resolved

0.3.0 seems pretty good. I'm going to test it out a little bit more, but it is looking like a promising 1.0 candidate.

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.