Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decoupling React Components and Redux Connect

Tags:

reactjs

redux

As seen here I am trying to decouple my app's components as much as I can and make them not aware of any storage or action creator.

The goal is to have them to manage their own state and call functions to emit a change. I have been told that you do this using props.

Considering

// Menu.jsx

import React from 'react'
import { className } from './menu.scss'
import Search from 'components/search'

class Menu extends React.Component {
  render () {
    return (
      <div className={className}>
        <a href='#/'>Home</a>
        <a href='#/foo'>foo</a>
        <a href='#/bar'>bar</a>
        <Search />
      </div>
    )
  }
}

And

// Search.jsx

import React from 'react'
import { className } from './search.scss'

class Search extends React.Component {
  render () {
    let { searchTerm, onSearch } = this.props

    return (
      <div className={`search ${className}`}>
        <p>{searchTerm}</p>
        <input
          type='search'
          onChange={(e) => onSearch(e.target.value)}
          value={searchTerm}
        />
      </div>
    )
  }
}

Search.propTypes = {
  searchTerm: React.PropTypes.string,
  onSearch: React.PropTypes.function
}

export default Search

And reading here I see a smart use of Provider and connect and my implementation would look something like this:

import { bindActionCreators, connect } from 'redux'
import actions from 'actions'

function mapStateToProps (state) {
  return {
    searchTerm: state.searchTerm
  }
}

function mapDispatchToProps (dispatch) {
  return bindActionCreators({
    dispatchSearchAction: actions.search
  }, dispatch)
}

export default connect(mapStateToProps, mapDispatchToProps)(Search)

Assuming I have a store handling searchTerm as part of the global state.

Problem is, where does this code belongs to? If I put it in Search.jsx I will couple actions with the component and more important to redux.

Am I supposed to have two different versions of my component, one decoupled and one connect()ed and have <Menu /> to use it? If yes what would my files tree look like? One file per component or a like a make-all-connected.js ?

like image 594
kilianc Avatar asked Dec 20 '15 08:12

kilianc


1 Answers

In redux, exist a new kind of component that is called containers, this is the component that use connect(mapStateToProps, mapActionsToProps), to pass the state and actions to the current component.

All depends of the use of the component. For example, if you component Search only going to be use with the same state and action, You container could be the same that your component like this:

// Search.jsx
import { connect } from 'redux'
import actions from 'actions'
import React from 'react'
import { className } from './search.scss'

class Search extends React.Component {
  render () {
    let { searchTerm, onSearch } = this.props

    return (
      <div className={`search ${className}`}>
        <p>{searchTerm}</p>
        <input
          type='search'
          onChange={(e) => onSearch(e.target.value)}
          value={searchTerm}
        />
      </div>
    )
  }
}

Search.propTypes = {
  searchTerm: React.PropTypes.string,
  onSearch: React.PropTypes.function
}


function mapStateToProps ({searchTerm}) {
  return {
    searchTerm
  };
}

const mapDispatchToProps = {
    onSearch: actions.search
}

export default connect(mapStateToProps, mapDispatchToProps)(Search)

But if your plan is reuse this component in another containers and the searchTerm or the action are different on the global state. The best way is passing this properties through other containers, and keep the Search component pure. Like this:

// Container1.jsx
import { connect } from 'redux'
import actions from 'actions'
import React, { Component } from 'react'

class Container1 extends Component {
  render() {
    const { searchTerm, handleOnSearch } = this.props;
    return (
      <div>
        <Search searchTerm={searchTerm} onSearch={handleOnSearch} />
      </div>
    ) 
  }
}

function mapStateToProps ({interState: {searchTerm}}) {
  return {
    searchTerm
  };
}

const mapDispatchToProps = {
  handleOnSearch: actions.search
}

export default connect(mapStateToProps, mapDispatchToProps)(Container1)

// Container2.jsx
import { connect } from 'redux'
import otherActions from 'otheractions'
import React, { Component } from 'react'

class Container2 extends Component {
  render() {
    const { searchTerm, handleOnSearch } = this.props;
    return (
      <div>
        <Search searchTerm={searchTerm} onSearch={handleOnSearch} />
      </div>
    ) 
  }
}

function mapStateToProps ({otherState: {searchTerm}}) {
  return {
    searchTerm
  };
}

const mapDispatchToProps = {
  handleOnSearch: otherActions.search
}

export default connect(mapStateToProps, mapDispatchToProps)(Container2)

For more information, read the official docs about using redux with react.

like image 132
Jesús Quintana Avatar answered Sep 19 '22 21:09

Jesús Quintana