3

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>
    }

}
hampusohlsson
  • 10,109
  • 5
  • 33
  • 50

3 Answers3

4

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.

Shane Cavaliere
  • 2,175
  • 1
  • 17
  • 18
  • 2
    Yeah, but imagine that the component consists of 14 different sub-components which all need to know about each other's states. That will get messy fast. – hampusohlsson May 04 '16 at 17:29
  • 1
    The main top-level Chart component would be managing the state that needs to be shared across its descendants. Those descendants could access/change that state through props. The descendant components wouldn't be holding any shared state themselves. This approach wouldn't be any more messy than trying to use some sort of embedded Redux, and it's more in line with the intended use. – Shane Cavaliere May 04 '16 at 17:50
  • I think you're right. I created an answer with a prototype if anyone stumbles on this question. – hampusohlsson May 04 '16 at 21:10
  • I had the same iea @hampusohlsson. Working on a canvas based visualization with ton of state where readibility really becomes an issue after a certain amount of state is added. Redux sub apps seemed the correct approach. Would you recommend component state/hooks or redux sub app approach? – Himanshu Chhabra Mar 23 '21 at 11:31
  • @HimanshuChhabra I asked this question 5 years ago and a lot has happened since. Would definitely go with the state/hooks approach today to keep state internal to the component only – hampusohlsson Mar 23 '21 at 23:24
1

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

azium
  • 20,056
  • 7
  • 57
  • 79
  • I thought of that too, but then my component would be useless for people who don't use redux? – hampusohlsson May 04 '16 at 17:10
  • 1
    That's not necessarily true... it really depends on how you implement it. You could fallback to your own redux store if one isn't already available, you could fallback to `this.state`, you could export multiple versions of your chart.. there a number of a possibilities here, but in the scenario where there is already one redux, having a nested one isn't such a good idea because `connect` would be looking at different providers. – azium May 04 '16 at 17:42
  • Also.. `redux` has become the flux standard.. it's even part of facebook's proper github account. I think it's safe to say that making a `redux-chart` library would be highly appreciated (if there wasn't an existing one) – azium May 04 '16 at 17:43
  • Good points! I guess I have to make the component a little bit smart when deciding how to handle the state. – hampusohlsson May 04 '16 at 17:48
1

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>
  }

}
hampusohlsson
  • 10,109
  • 5
  • 33
  • 50
  • That could certainly work. Context is similar to global variables, so it should probably be avoided if possible. For instance, in this case you've defined a 'store' context in your ReusableComponent - if anyone using this component is also using react-redux, there will be a conflict because the Provider component from react-redux also defines a 'store' context. Also, as you probably know, context is still considered experimental, so the API is likely going to change down the road. – Shane Cavaliere May 04 '16 at 21:11
  • Right... Hmm. So you would say the best way is to pass down *everything* as props even if that means sending props to components that doesn't use them, other than just passing them along onto their children? – hampusohlsson May 04 '16 at 21:30
  • 1
    That should probably be your default choice. If passing props through components becomes a major hassle, you then might want to consider the tradeoffs between the convenience of context and its downsides. There is almost always an elegant solution to be found with props though, and it makes it easier to track the flow of data through your components. For some "context" (tee hee) on this topic, check out this answer by Dan Abramov (the creator of Redux) regarding the use of context in react-redux: http://stackoverflow.com/questions/36428355/react-with-redux-what-about-the-context-issue – Shane Cavaliere May 04 '16 at 21:43
  • could you not just place a Container Component at the top of your chart component that accesses state/store directly? And for your actions you should not need to pass those down because they would be picked up by the reducer you are registering at the top. – Raif May 23 '16 at 13:45