2
\$\begingroup\$

I've recently started working on some projects with react & redux.

I understand it is best practice to manage component/UI state inside a component with setState etc. However there are various advantages to fully utilising the global state to do with making components and the application as w whole easier to test, debug, and mock; the more the application state resembles a single object tree and the components resemble pure functions.

I wonder if it is possible to have the best of both with a mechanism resembling the following, which allows the usual use of this.setState and this.state inside the component but actually relies on the redux store to maintain this state, thus making it mockable, audit-able, repayable etc along with the rest of the store:

// reducer/componentState.js

export (state, action) => {
  const id = action.id;
  switch (action.type) {
    case 'REGISTER_COMPONENT':
      return state.set(id, Object.assign({}, action.initialState));
    case 'DEREGISTER_COMPONENT':
      return state.remove(id);
    case 'SET_STATE':
      return state.set(id, Object.assign({}, state.get(id), action.newState));
    case 'REPLACE_STATE':
      return state.set(id, Object.assign({}, action.newState));
  }
  return state;
};


// components/MyComponentContainer.js

function mapStateToProps(state, ownProps) {
  return {
    state: state.getIn(['componentState', ownProps.id])
  };
}

function mapDispatchToProps(dispatch, ownProps) {
  return {
    _binding: {
      registerComponent(initialState) {
        dispatch({type: 'REGISTER_COMPONENT', ownProps.id, initialState});
      },
      deregisterComponent() {
        dispatch({type: 'DEREGISTER_COMPONENT', ownProps.id});
      },
      setState(newState) {
        dispatch({type: 'SET_STATE', ownProps.id, newState});
      },
      replaceState(newState) {
        dispatch({type: 'REPLACE_STATE', ownProps.id, newState});
      }
    }
  };
}

...

// components/MyComponent.js

export class Contacts extends React.Component {
  constructor(props) {
    super(props);
    this.setState = this.props._binding.setState;
    this.replaceState = this.props._binding.setState;

    this.state = {
      something: true
    };
  }

  componentWillMount() {
    this.props._binding.registerComponent(this.state);
  }

  componentWillReceiveProps(nextProps) {
    this.state = nextProps.state;
  }

  componentWillUnmount() {
    this.props._binding.deregisterComponent();
  }

  ...
}

Obviously this pattern, contains a lot of boilerplate in the component that could be abstracted with helper functions or by maybe subclassing React.Component.

Besides general feedback on my reasoning and code, I'd like to know if any libraries exist that do something similar, or if I'm fundamentally abusing react here in some way that makes this a bad idea.

\$\endgroup\$
  • \$\begingroup\$ This isn't the answer (or an answer) that you want to hear, but this isn't how you should be using Redux. setState is very useful for transient component state, but the Redux store should only contain application-wide state. Replacing setState in this manner is likely a footgun, and it also makes it a lot harder for you to denormalise state like data in the manner you should use Redux. Hopefully I will get around to writing a fuller answer, but you can find more info about this on the Redux Without Profanity website. \$\endgroup\$ – Dan Pantry Sep 12 '16 at 20:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.