DEV Community


Posted on

How-to organize your cells for flexibility

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, }
Enter fullscreen mode Exit fullscreen mode

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],
Enter fullscreen mode Exit fullscreen mode

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>,
    ...(variant ? variants.get(variant) : {}),
Enter fullscreen mode Exit fullscreen mode

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)