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.
Top comments (0)