One of the early defining feature of #redwoodJS was Cells: React components designed to help you organise a Graphql's request lifecycle with frontend components for each of its states. RWJS is rather flexible, which leaves a lot of freedom for creative design and spectacular mistakes (I've had my share). Organising your code can become a challenge, so this post is meant to help you.
Over my past 3 years full time on RWJS I have been repeatedly confronted to the feeling that Cells were useful but were difficult to scale with. At the end of the day, I feel like the only real immutable part of a Cell, thus defining its nature, being the QUERY, there was no reason to feel constrained by a Cell's other parts, namely:
{ QUERY, beforeQuery, afterQuery, isEmpty, Loading, Failure, Empty, Success, displayName, }
Most of the time the only thing I had to change was the Success component, which created the itch, but was not big enough to bring forward a clearer solution. I used as
and variant
props to change the Success
component, but felt unsatisfied.
Better variants with createCell
The following proposition is far from being perfect, but it's the one version that allowed me to feel confident in a design based on RedwoodJS, with minor tweaks to take cells to another level. I admit I might have been the only one in need of this and that it may be for lack of better engineering skills, but hey, it's a journey, we're all learning.
createCell powers the transformation of your XCell.tsx
. The framework exposes it, so you can reuse it and get creative.
To address my code organisation problem with a 'variant' solution, I'm fulling relying on createCell
.
Step by step
Create variants
In the same directory as the cell you are working on, create a variants
directory and add the following as a base for index.ts
:
// ./variants/index.ts
import * as List from './List'
import * as Select from './Select'
export type Variants = 'list' | 'select'
export const variants = new Map<Variants, unknown>([
['list', List],
['select', Select],
])
This declares only two variants, let's keep things simple and light. The exported type Variants
helps with the Cell's variant
prop typing and variants
is the map we will use in the default export.
Add the variant code
The imported modules, List
and Select
, should have at least one Cell part different from what we have defined in our original cell. In my case, I neutralized the Loading
, Failure
and Empty
components in the Select
variant and added a different afterQuery
in the List
variant.
I didn't do it, but a better way to handle the specificity and complexity here would be to rely on yarn rw g cell X
to create the boilerplate code for each variant. You'd be set with tests & stories upfront.
Neutralize the import transformer
Back in the cell's directory, rename your XCell.tsx
to drop the Cell
suffix, otherwise you'll have import conflicts. This remains your entry point and should have all your default Cell parts.
Activate variants with default support
Your default export in this file should look like the following:
type Params = {
random: string
}
export default React.memo(function ({ variant, ...props }: {
variant: Variants
} & CellSuccessProps<MyQuery, Exact<Params>>) {
return createCell<
CellSuccessProps<MyQuery, MyQueryVariables>,
MyQueryVariables
>({
QUERY,
afterQuery,
Loading,
Failure,
Empty,
Success,
...(variant ? variants.get(variant) : {}),
})(props)
})
That's about it.
Now the reason I prefer this over the Cell boilerplate is that the generated code helped me develop fast but on longer term I found myself more confused when facing other cases for my cells, as I didn't necessarily anticipate the need for a cell to be way more flexible. For a long time I thought only working on a different Success
component was enough - and in some way it is, but enabling the entire module to be altered in variants is helping me pushing design and attention to details a lot further.
Another solution would be to generate the same cell with a different name and the same query over and over, but this rings to me as a trap to bloat the codebase and face more and more the dreaded problem of naming things.
So I'll stick to this variants
implementation.
Top comments (0)