kilianc kilianc - 1 month ago 14
React JSX Question

Decoupling React Components and Redux Connect

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
?

Answer

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.

Comments