This article is based on the compound pattern. It was presented for the first time during a talk by Ryan Florence at ReactJS Phoenix 2016.
I discovered it with Egghead.io, during a course done by Kent.C.Dodds. You can find more resources on his blog.
The pattern puts emphasis on modularity and components composition.
In my company I faced some issues building reusable shared components. I'm going to show you how to tackle these problems, enhance composition, and still give the user the possibility to customize the behavior.
You are being asked to create a simple shared component, a DataList with a select all elements action.
So you did a simple chunk of code, nothing fancy. You have built a DataList component, with your action and the table with the data.
But soon more is asked, from different projects with more or less time to do it. And the simple shared DataList is becoming more and more complex.
Each project has different needs, so everything must be conditional, meaning that this shared component has to hold all the projects' logic.
This is what it looks like in your project
So here you have two actions (foo and bar), the selectAll and the table...
The api and the desired output are hard to read, it's the beginning of the nested props problem.
It's becoming a nightmare, and tweaking or adding features is now a really hard job.
Let's not speak about the documentation...
The Monolith has beaten you...
In his course KCD talked about what he called the compound pattern. The name is self speaking, it involves to break down our components into smaller ones to allow more flexibility.
Instead of being only props driven, we want to be component driven.
We have made every component accessible from outside and now you can compose them as you want. Our DataList could be seen as a dictionary of components to make YOUR Datalist.
In the example above, you can see that the props api is way easier to read. There is no more nested props and each component gets is own set of params.
Let's move on and continue to improve it.
We have some redundancy props. Let's fix that.
First we have to create a new component called Manager, it will hold our context provider for the List and wrap all our compound components.
We can use the context to easily share some props between our children components.
For example to simplify the api, we can decide to pass the id to our manager.
Each child will access it from the consumer, and will create a unique id based on a common value.
So after the changes we have something like this
Our DataList is easy to import and manipulate. We have already made huge progress since our first list draft, but we are still missing some true component logic.
We want to implement a button with the ability to switch between two display modes, a column one and an expanded one.
We have to share the display value between two components, the one the user can trigger to change the value, and of course the list to display the correct render mode.
So our Manager component, which is the parent of everyone, seems to be the right choice.
We are using the useReducer here.
Our manager will hold the state of the current display, and it will also provide the dispatcher.
In our DisplayMode component we can use the dispatch function passed through the context.
The same applies to our Table, we can retrieve the value from the state store in the context.
So now we have our first business logic implemented.
But the user lacks control on the component.
So we want to empower our components with user logic, but also keep a simple api.
Now the Manager can take an initialDisplayMode and put it in the reducer's state as initialState.
This is one customisation, you can change the initial value but keep all the internal logic.
But we also want to go full control.
Now the component can take a callback and be controlled by the user.
We just have to allow the List to receive the value.
A simple api that users can play around with and compose depending on their need, given also the possibility to add their own logic where they need. You can mix fully controlled components with uncontrolled ones, if you want.
Compound pattern, context and hooks allow to create very flexible and customisable components.
Let me hear what pattern you have found to solve your day to day problems
and don't hesitate to leave comments and share your opinions.
Happy react ;)
You can still improve some parts of the api and make it more opinionated.
Let's enhance our context api
Now if someone tries to use a compound component outside its context, an error will be thrown.
We can also alert if the controlled mode is not used well.
And use it like this
If the user forgets to pass a value and be fully controlled, it will throw an error. It's a way to try to make "impossible state impossible".
Little disclaimer from KCD:
@nicomaligne @ThePracticalDev @ryanflorence @dan_abramov Please note that with "compound components" the key is that they share state implicitly. The fact that they're static properties on another component is irrelevant and just a stylistic choice on my part.
16:00 PM - 10 Sep 2019