1. Passing store

1.1. Provider from react-redux ✅

React Redux is a library that contains some tools to help ease the complexity involved with implicitly passing the store via context. Redux does not require that you use this library. However, using React Redux reduces your code’s complexity and may help you build apps a bit faster.

react-redux supplies us with a component that we can use to set up our store in the context, the provider. We can wrap any React element with the provider and that element’s children will have access to the store via context.

Instead of setting up the store as a context variable in the App component, we can keep the App component stateless:

import { Menu, NewColor, Colors } from './containers';

const App = () => (
  <div className="app">
    <Menu />
    <NewColor />
    <Colors />
  </div>
);

export default App;

The provider adds the store to the context and updates the App component when actions have been dispatched. The provider expects a single child component:

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import storeFactory from './store';

const store = storeFactory();

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('react-container'),
);

The provider requires that we pass the store as a property. It adds the store to the context so that it can be retrieved by any child of the App component. Simply using the provider can save us some time and simplify our code.

Once we’ve incorporated the provider, we can retrieve the store via context in child container components. However, React Redux provides us with another way to quickly create container components that work with the provider: the connect function.

1.2. Explicitly Passing the Store

The first, and most logical, way to incorporate the store into your UI is to pass it down the component tree explicitly as a property. This approach is simple and works very well for smaller apps that only have a few nested components.

Let’s take a look at how we can incorporate the store into the color organizer. In the ./index.js file, we will render an App component and pass it the store:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import storeFactory from './store';

const store = storeFactory();

const render = () =>
  ReactDOM.render(<App store={store} />, document.getElementById('react-container'));

store.subscribe(render);
render();

This is the ./index.js file. In this file, we create the store with the storeFactory and render the App component into the document. When the App is rendered the store is passed to it as a property. Every time the store changes, the render function will be invoked, which efficiently updates the UI with new state data.

Now that we have passed the store to the App, we have to continue to pass it down to the child components that need it:

import AddColorForm from './AddColorForm';
import SortMenu from './SortMenu';
import ColorList from './ColorList';

const App = ({ store }) => (
  <div className="app">
    <SortMenu store={store} />
    <AddColorForm store={store} />
    <ColorList store={store} />
  </div>
);

export default App;

The App component is our root component. It captures the store from props and explicitly passes it down to its child components. The store is passed to the SortMenu, AddColorForm, and ColorList components as a property.

Now that we have passed the store from the App, we can use it inside the child components. Remember we can read state from the store with store.getState, and we can dispatch actions to the store with store.dispatch.

From the AddColorForm component, we can use the store to dispatch ADD_COLOR actions. When the user submits the form, we collect the color and the title from refs and use that data to create and dispatch a new ADD_COLOR action:

import { PropTypes, Component } from 'react';
import { addColor } from '../actions';

const AddColorForm = ({ store }) => {
  let _title, _color;

  const submit = (e) => {
    e.preventDefault();
    store.dispatch(addColor(_title.value, _color.value));
    _title.value = '';
    _color.value = '#000000';
    _title.focus();
  };

  return (
    <form className="add-color" onSubmit={submit}>
      <input ref={(input) => (_title = input)} type="text" placeholder="color title..." required />
      <input ref={(input) => (_color = input)} type="color" required />
      <button>ADD</button>
    </form>
  );
};

AddColorForm.propTypes = {
  store: PropTypes.object,
};

export default AddColorForm;

From this component, we import the necessary action creator, addColor. When the user submits the form, we’ll dispatch a new ADD_COLOR action directly to the store using this action creator.

The ColorList component can use the store’s getState method to obtain the original colors and sort them appropriately. It can also dispatch RATE_COLOR and REMOVE_COLOR actions directly as they occur:

import { PropTypes } from 'react';
import Color from './Color';
import { rateColor, removeColor } from '../actions';
import { sortFunction } from '../lib/array-helpers';

const ColorList = ({ store }) => {
  const { colors, sort } = store.getState();
  const sortedColors = [...colors].sort(sortFunction(sort));
  return (
    <div className="color-list">
      {colors.length === 0 ? (
        <p>No Colors Listed. (Add a Color)</p>
      ) : (
        sortedColors.map((color) => (
          <Color
            key={color.id}
            {...color}
            onRate={(rating) => store.dispatch(rateColor(color.id, rating))}
            onRemove={() => store.dispatch(removeColor(color.id))}
          />
        ))
      )}
    </div>
  );
};

ColorList.propTypes = {
  store: PropTypes.object,
};

export default ColorList;

The store has been passed all the way down the component tree to the ColorList component. This component interacts with the store directly. When colors are rated or removed, those actions are dispatched to the store.

The store is also used to obtain the original colors. Those colors are duplicated and sorted according to the store’s sort property and saved as sortedColors. sortedColors is then used to create the UI.

This approach is great if your component tree is rather small, like this color organizer. The drawback of using this approach is that we have to explicitly pass the store to child components, which means slightly more code and slightly more headaches than with other approaches. Additionally, the SortMenu, AddColorForm, and ColorList components require this specific store. It would be hard to reuse them in another application.

1.3. Passing the Store via Context

In the last section, we created a store and passed it all the way down the component tree from the App component to the ColorList component. This approach required that we pass the store through every component that comes between the App and the ColorList.

Let’s say we have some cargo to move from Washington, DC, to San Francisco, CA. We could use a train, but that would require that we lay tracks through at least nine states so that our cargo can travel to California. This is like explicitly passing the store down the component tree from the root to the leaves. You have to “lay tracks” through every component that comes between the origin and the destination. If using a train is like explicitly passing the store through props, then implicitly passing the store via context is like using a jet airliner. When a jet flies from DC to San Francisco, it flies over at least nine states—no tracks required.

Similarly, we can take advantage of a React feature called context that allows us to pass variables to components without having to explicitly pass them down through the tree as properties.1 Any child component can access these context variables.

If we were to pass the store using context in our color organizer app, the first step would be to refactor the App component to hold context. The App component will also need to listen to the store so that it can trigger a UI update every time the state changes:

import { PropTypes, Component } from 'react';
import SortMenu from './SortMenu';
import ColorList from './ColorList';
import AddColorForm from './AddColorForm';
import { sortFunction } from '../lib/array-helpers';

class App extends Component {
  getChildContext() {
    return {
      store: this.props.store,
    };
  }

  componentWillMount() {
    this.unsubscribe = store.subscribe(() => this.forceUpdate());
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const { colors, sort } = store.getState();
    const sortedColors = [...colors].sort(sortFunction(sort));
    return (
      <div className="app">
        <SortMenu />
        <AddColorForm />
        <ColorList colors={sortedColors} />
      </div>
    );
  }
}

App.propTypes = {
  store: PropTypes.object.isRequired,
};

App.childContextTypes = {
  store: PropTypes.object.isRequired,
};

export default App;

First, adding context to a component requires that you use the getChildContext lifecycle function. It will return the object that defines the context. In this case, we add the store to the context, which we can access through props.

Next, you will need to specify childContextTypes on the component instance and define your context object. This is similar to adding propTypes or defaultProps to a component instance. However, for context to work, you must take this step.

At this point, any children of the App component will have access to the store via the context. They can invoke store.getState and store.dispatch directly. The final step is to subscribe to the store and update the component tree every time the store updates state. This can be achieved with the mounting lifecycle functions (see “Mounting Lifecycle”). In componentWillMount, we can subscribe to the store and use this.forceUpdate to trigger the updating lifecycle, which will re-render our UI. In componentWillUnmount, we can invoke the unsubscribe function and stop listening to the store. Because the App component itself triggers the UI update, there is no longer a need to subscribe to the store from the entry ./index.js file; we are listening to store changes from the same component that adds the store to the context, App.

Let’s refactor the AddColorForm component to retrieve the store and dispatch the ADD_COLOR action directly:

const AddColorForm = (props, { store }) => {
  let _title, _color;

  const submit = (e) => {
    e.preventDefault();
    store.dispatch(addColor(_title.value, _color.value));
    _title.value = '';
    _color.value = '#000000';
    _title.focus();
  };

  return (
    <form className="add-color" onSubmit={submit}>
      <input ref={(input) => (_title = input)} type="text" placeholder="color title..." required />
      <input ref={(input) => (_color = input)} type="color" required />
      <button>ADD</button>
    </form>
  );
};

AddColorForm.contextTypes = {
  store: PropTypes.object,
};

The context object is passed to stateless functional components as the second argument, after props. We can use object destructuring to obtain the store from this object directly in the arguments. In order to use the store, we must define contextTypes on the AddColorForm instance. This is where we tell React which context variables this component will use. This is a required step. Without it, the store cannot be retrieved from the context.

Let’s take a look at how to use context in a component class. The Color component can retrieve the store and dispatch RATE_COLOR and REMOVE_COLOR actions directly:

import { PropTypes, Component } from 'react';
import StarRating from './StarRating';
import TimeAgo from './TimeAgo';
import FaTrash from 'react-icons/lib/fa/trash-o';
import { rateColor, removeColor } from '../actions';

class Color extends Component {
  render() {
    const { id, title, color, rating, timestamp } = this.props;
    const { store } = this.context;
    return (
      <section className="color" style={this.style}>
        <h1 ref="title">{title}</h1>
        <button onClick={() => store.dispatch(removeColor(id))}>
          <FaTrash />
        </button>
        <div className="color" style={{ backgroundColor: color }} />
        <TimeAgo timestamp={timestamp} />
        <div>
          <StarRating
            starsSelected={rating}
            onRate={(rating) => store.dispatch(rateColor(id, rating))}
          />
        </div>
      </section>
    );
  }
}

Color.contextTypes = {
  store: PropTypes.object,
};

Color.propTypes = {
  id: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  color: PropTypes.string.isRequired,
  rating: PropTypes.number,
};

Color.defaultProps = {
  rating: 0,
};

export default Color;

ColorList is now a component class, and can access context via this.context. Colors are now read directly from the store via store.getState. The same rules apply that do for stateless functional components. contextTypes must be defined on the instance.

Retrieving the store from the context is a nice way to reduce your boilerplate, but this is not something that is required for every application. Dan Abramov, the creator of Redux, even suggests that these patterns do not need to be religiously followed.

Copyright © Guanghui Wang all right reserved,powered by GitbookFile Modified: 2019-08-25 13:56:34

results matching ""

    No results matching ""