3
votes

I'm thinking about building a pretty complex chart component in React, and I want it to be resuable across many different project. Needless to say, a component like that has a multitude of states that needs to be managed somehow.

Redux seems like the perfect fit for this, but if I just wrap the top level container in a Provider with a custom store... wouldn't the component's internal redux store interfere with the global application state if the component is included in a larger React/Redux app?

import React, { Component } from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import internalReducer from './reducer';

export default class MyReduxComponent extends Component {

    render() {
        const store = createStore(internalReducer, this.props.initialState);
        return <Provider store={store}>
            <Chart />
        </Provider>
    }

}
3

3 Answers

4
votes

Is there a reason you're not just using React component state? Redux is meant to be used for state that needs to be accessible by by multiple components across your entire app. If you're managing state that's only meant to be used by descendants of a specific component, then you may as well just use component state - no need for Redux in that case. Your component will be much more reusable that way, and won't rely on additional dependencies.

1
votes

Your component should not have its own internal redux store, but rather have its own reducers + actions / action creators the reducer knows about, so it can be easily integrated with an existing redux app:

import { chartReducer, chartActionCreators } from 'your-chart'

combineReducers({
  ...global.stuff,
  chartReducer
})

store.dispatch(chartActionCreators.action())

--

import Chart from 'your-chart'

export default () =>
  <div>
    <Chart />
  </div>

A user of your library may not need to use any of the actions explicitly, just including the reducer should be enough, and as such the chart state can be accessed anywhere in the app.

Additionally, you can write some middleware if you need to augment your action calls in some way.

http://redux.js.org/docs/advanced/Middleware.html

1
votes

I think I found a quite nice way to solve the problem, using childContextTypes and setState in the top component. That allows subcomponents on any depth to just "import" the context actions they need. (that eliminates the need for passing down callbacks as props through multiple nested components).

Example on JSBin

A top component could look like this

export default class ReusableComponent extends Component {

  // Define the public API interface to child components
  static childContextTypes = {
    // expose component state store
    store: PropTypes.object,
    // actions
    setFoo: PropTypes.func,
    setBar: PropTypes.func
  };

  // Set initial state, can be overriden by props
  constructor(props) {
    super(props)
    this.state = {
      foo: 'My Foo',
      ...props
    }
  }

  // Define methods for public API 
  getChildContext() {
    return {
      store: this.state,
      setFoo: this.setFoo.bind(this),
      setBar: this.setBar.bind(this)
    };
  }

  // Reducer for action setFoo
  setFoo(foo) {
    this.setState({ foo })
  }

  // Just render components, no need for passing props
  render() {
    return <div>
      <UpdateFooComponent />
      </div>
  }

}

And a child component

class UpdateFooComponent extends Component {

  // 'import' the store and actions you need from top component
  static contextTypes = {
    store: PropTypes.object,
    setFoo: PropTypes.func
  };

  clickHandler(e) {
    this.context.setFoo('Hello from subcomponent');
  }

  render() {
    const { foo } = this.context.store;
    return <div>
      <button onClick={::this.clickHandler}>Update foo</button>
      <p><strong>foo:</strong> {foo}</p>
    </div>
  }

}