React Redux Connectors pattern

pigozzifr

Francesco Pigozzi

Posted on March 6, 2019

React Redux Connectors pattern

TL;DR

Don't worry, I'm not here to talk about the umpteenth react-redux-whatever package.

This post is rather a moment of reflection on what could be a simple but powerful approach to avoid generating dependencies between what would be called containers and what we normally call components.

Many of you will have found their way to solve this problem; for those who did not, keep reading.

The problem

How many times have you found yourself in front of a new project, maybe created using create-react-app, armed with good intentions not to repeat the same mistakes that have generated technical debt in previous projects? I already imagine many of us at that precise moment:

"This time the codebase will be well structured", "The folders will have only one level of subfolders, maybe two" or "I will not bind the components to the containers again".

The last famous words.

The reasoning

When we talk about containers, we are defining nothing but components that are enriched with information from a HOC: connect.

The problem is not about writing mapStateToProps, mapDispatchToProps and mergeProps (yes, there is a third parameter and it is fantastic) again and again and again. The problem is, rather, to know what to do with these components connected to the Redux store.

"Do I include the presentational logic within them? But if I have to move it or modularize it, I should rewrite everything ..."

The naive solution

"Actually I don't see all this distinction between components and containers ... Now I will make sure that every component can access the status of Redux when and how it wants, after all we are talking about Context".

Connectors (?)

For some time I like to ask myself the question "how would I like to write it/use it ?" before implementing any utility in Javascript or React.

In the same way, to solve this problem, I would like to write something like this:

<Layout>
  <Layout.Header>
    <h1>Header</h1>
  </Layout.Header>
  <Layout.Main>
    <PostsConnector>
      {({ posts }) => posts.map(post => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </div>
      ))}
    </PostsConnector>
  </Layout.Main>
</Layout>

This way of looking at it reminds me a lot of the Javascript closures.

After all, React is a powerful library for the fact of leaving freedom and dynamism to the developer to be able to create as preferred the tree of components, either in JSX or not.

Why, at this point, I can not take advantage of some generic Javascript programming concepts in order to solve this kind of specific problems?

Connector's creation

// imports

class PostsConnector extends Component {
  componentDidMount() {
    const { fetchOnMount, fetchPosts } = this.props

    if (fetchOnMount) {
      fetchPosts()
    }
  }

  get childrenProps() {
    const { posts, fetchPosts, createPost } = this.props

    return {
      posts,
      fetchPosts,
      createPost,
    }
  }

  render() {
    const { children } = this.props

    return children(this.childrenProps)
  }
}

// PropTypes and connect's logic

Or using React Hooks

// imports

const PostsConnector = ({
  children,
  fetchOnMount,
  fetchPosts,
  // Redux
  posts,
  fetchPosts,
  createPost,
}) => {
  useEffect(() => {
    if (fetchOnMount) fetchPosts()
  }, [])

  return children({
    posts, fetchPosts, createPost,
  })
}

// PropTypes and connect's logic

Done ✅

Bonus: composeConnectors

All this is very nice but .. "If I had more connectors inside each others?".

In this specific case, your problem may be to have too many connectors.

In the case, however, you still want to proceed and try to avoid the connectors-hell and write something like this:

const ComposedConnector = composeConnectors(
  <PostsConnector />,
  <PostConnector postId="super-secret-id" />,
)

<ComposedConnector>
  {props => console.log(props) || null}
</ComposedConnector>

You can use this utility function:

import React from 'react'

// return ({ children }) => (
//   React.cloneElement(connectors[1], null, secondProps => (
//     React.cloneElement(connectors[0], null, firstProps => (
//       children({ ...firstProps, ...secondProps })
//     ))
//   ))
// )

const composeConnectors = (...connectors) => (
  connectors.reverse().reduce((composed, connector) => (
    ({ children, ...childrenProps }) => (
      React.cloneElement(
        connector,
        null,
        connectorProps => composed({ ...childrenProps, ...connectorProps, children }),
      )
    )
  ), ({ children, ...props }) => children(props))
)

export default composeConnectors

Thanks for reading

If you liked this article just let me know; if not, let me know it anyway.

💖 💪 🙅 🚩
pigozzifr
Francesco Pigozzi

Posted on March 6, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related