2.0.1 • Published 5 years ago

react-quickstore v2.0.1

Weekly downloads
3
License
MIT
Repository
github
Last release
5 years ago

QuickStore

A React component library for state management that utilizes the Context API and React Hooks.

npm install react-quickstore

Check out a live example on codesandbox.io.


QuickStore makes use of the Context API implemented in version 16.3 of React. If you're not familiar with it, it's recommended that you check it out first to gain an understanding, and to see if you even need this level of state management in your application.

The two components that ship in the QuickStore library are named Provider and Consumer, the same naming convention used in Context. This makes implementing QuickStore easier once you understand the API.

QuickStore version 2.0.0 and greater utilize React version 16.8.6 to make use of the new useContext hook! You can now use this new feature by importing the useQuickStore custom hook from this library.

Provider Component

The Provider component is akin to the data store present in other state management solutions. It's the place your application's data is stored and manipulated when requests are handed to it. The Provider component is the parent of the two in this library.

Consumer Component

The Consumer component is the part that accesses the data from the Provider. It uses the render prop pattern for its implementation, and it passes along to the function an object with two properties: state and dispatch. The state property is an object that contains all the key/value pairs of data in the Provider "store". The dispatch property is a function that can be called to send actions, or requests, back to the Provider to change the data in the store.

Example

Below is the basic implementation pattern for QuickStore's components:

import { Provider, Consumer } from "react-quickstore";

...

<Provider>
  <Consumer>
    {({ state, dispatch }) => (
      ... UI elements ...
    )}
  </Consumer>
</Provider>

Consumer :: state

The Consumer component passes to its child function an object with two properties, one for accessing the state in the Provider, and one for manipulating that state. The first one is the state property, which is a reference to the state object held in the Provider store.

If our Provider store data looks like this...

{
  name: 'Chewbacca',
  height: 'tall'
}

...then we access the values like so:

<Consumer>
  {({ state }) => (
    <p>
      {state.name} is {state.height}.
    </p>
  )}
</Consumer>

Consumer :: dispatch

The second property is dispatch, which is a function that allows the state within Provider to be manipulated. It accepts two arguments: an object that represents the changes to be made in state, and an optional callback function to be invoked after the changes occur.

The action object passed to dispatch in its simplest form contains just the key/value pairs of the data you want to change in the store.

For example, if our store data looks like this...

{
  planet: "Earth";
}

...and we dispatch this action object...

<Consumer>
  {({ state, dispatch }) => (
    <p>I have been to {state.planet}.</p>
    <button onClick={() => dispatch({ planet: 'Mars' })}>
      Change Planet
    </button>
  )}
</Consumer>

...then our store data will look like this:

{
  planet: "Mars";
}

The action objects you dispatch back to the Provider can be as complex as you need them to be. Just note that under the hood, Provider is performing a setState with the changes you pass it, so keep that in mind when deciding how you're updating your values when dispatching these simple objects.

For a more controlled dispatch experience, see the sections below on the different reducer props you can pass to Provider.

Provider :: Props

The Provider component accepts three optional props for enchancing your state management solution.

  • initialState: an object that sets the initial shape and values of the data stored in the Provider.
const initialState = { message: "Hello from QuickStore!" };

<Provider initialState={initialState}>
  <Consumer>{({ state }) => <p>Message: {state.message}</p>}</Consumer>
</Provider>;
  • reducer: a function that accepts two arguments (current state and action dispatched) and returns an object that represents which key/values in the state object that should be changed. This is useful when implementing more controlled state management experiences where "types" and "payloads" are used. See example below:
const initialState = { message: '' };

// Listen for specific "type" and return updates using the supplied payload.
// This approach is common in patterns used by Flux and Redux.
const reducer = (state, action) => {
  switch (action.type) {
    case "UPDATE_MESSAGE":
      return { message: action.payload };
    default:
      return action;
  }
};

<Provider initialState={initialState}>
  <Consumer>
    {({ state }) => (
      <p>Message: {state.message}</p>
      <button
        onClick={() => {
          dispatch({ type: 'UPDATE_MESSAGE', payload: 'Hello!' })
        }}
      >
        Update Message
      <button>
    )}
  </Consumer>
</Provider>
  • asyncReducer: similar to the reducer prop, this is an async function that accepts the same two arguments, but returns a promise with a resolved value set to the changes that should be made in state. Asynchronous operations can be awaited inside this function.
// Working off of the example above, this is
// what an asyncReducer usage could look like:
const asyncReducer = async (state, action) => {
  switch (action.type) {
    case "GET_SERVER_MESSAGE":
      return await someAjaxMethod().then(message => {
        return { message };
      });
    default:
      return action;
  }
};

<Provider asyncReducer={asyncReducer}> ... </Provider>;

The asyncReducer function is processed first internally in QuickStore, with the result then being passed to the reducer function afterwards. This allows you have both synchronous and asynchronous operations and keep them separated for clarity. Below is an example of returning the result of an async operation in the asyncReducer to operations peformed in the reducer:

// Pass results from async operation to synchronous reducer to be processed:
const asyncReducer = async (state, action) => {
  switch (action.type) {
    case "GET_SERVER_MESSAGE":
      return await someAjaxMethod().then(message => {
        // This will now tell the reducer to process this result:
        return { type: "UPDATE_MESSAGE", payload: message };
      });
    default:
      return action;
  }
};

// The synchronous reducer can now handle both cases where updated values
// come from either an async or sync request, since it's simply looking
// for the correct action.type to match the case within:
const reducer = (state, action) => {
  switch (action.type) {
    case "UPDATE_MESSAGE":
      return { message: action.payload };
    default:
      return action;
  }
};

<Provider asyncReducer={asyncReducer} reducer={reducer}>
  ...
</Provider>;

useQuickStore :: Custom Hook

The useQuickStore custom hook allows you to import state and dispatch in function components, instead of having to use the Consumer component's render props pattern. This improves the overall functionality and design of the QuickStore component library! Invoking the useQuickStore custom hook returns an object with the state and dispatch properties, which function the same way that the Consumer render prop version. Just make sure that your function component is in the child component tree of your Provider component.

Example

Below is the basic implementation pattern for QuickStore's useQuickStore custom hook:

import React, { Fragment } from "react";
import { useQuickStore } from "react-quickstore";

// inside store's state object:
// {
//  title: 'My Title'
// }

function Title() {
  const { state, dispatch } = useQuickStore();

  return (
    <Fragment>
      <h1>{state.title}</h1>
      <button onClick={() => dispatch({ title: "My New Title" })}>
        Change Title
      </button>
    </Fragment>
  );
}
2.0.1

5 years ago

2.0.0

5 years ago

1.0.3

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago