Flex your cells
Tags: redwoodjs, graphql, react
Published: 2022-02-17 10:23:02


## Some context

We've been building SportOffice on [RedwoodJS](https://redwoodjs.com) for almost a year now and we've made a point to use the framework as it comes - with little to no exotic stuff sprayed over.

This helped us go live in December and today we are hitting numbers ( &euro; ), with a CRM built entirely with RedwoodJS ( and, yeah, Stripe, AWS, we're in an ecosystem anyway ). RW's not in v1 yet, but there's no looking back for us.

Now with all its power in its standard setup, couple of things could need some exposition online to help people better understand what's possible with it.

Today, I will precisely talk about Cells.

## Reminding you of Cells
In a standard Redwood app, you'd have a `web` and an `api` side, both self explanatory. The `api` would be powered by Redwood itself - but it could be anything else, really, from an Express served api to a stitched graphql schema and beyond ( sky's the limit to Redwoods ).

Cells are components that manage the whole fetch-some-data-display-in-front cycle, errors and empty payloads included. A typical Cell will at the very least be a module with no `default` export, exporting:
- a `const QUERY = gql[...]`
- a `const Success: React.FC<SuccessProps> = [...]`

Sample below.

It's clean and easy, I've been using them for so long I don't even know if it ever felt difficult. Certainly it felt great to leave `fetch` calls in React components behind.

So, cells in themselves are very convenient, but sometimes you need a bit more flexibility. What if you needed to call entirely different queries but the rest of the component should stay the same? It's doable with a standard cell, but not in a very clean way.


## Some code

What I'm about to show you is not neat and shiny, it's unpolished code extracted to demonstrate the point - please forgive me if your eyes do bleed. This is the price of knowledge ( for some of us ).

We needed a `Select` for all our `Users` where `roles.include('student')`. This has been enough for about ten months:

```tsx
import { userToOption } from 'src/components/Model/User'  
import { Input, Select } from 'src/ui'  
  
export const QUERY = gql`  
 query SELECT_STUDENT($where: WhereUserInput) {  
	 options: students(where: $where) {  
		 id  
		 firstname
		 lastname  
	 }
}`  
  
export const Loading = () => (  
  <Input name={'student'} disabled pointer={'model:student.label'} />  
)  
  
export const Success: typeof Select = ({  
  name = 'students',  
  options,  
  ...selectProps  
}) => (  
  <Select  
	{...selectProps}  
    name={name}  
    pointer={'model:student.label'}  
    options={options?.map((student) => userToOption(student))}  
  />  
)
```

It uses a `students` service with a `where` parameter, it's safe for you to assume that this should fit straight into a **prisma** query.

**Problem now is that we need the same `Select`, targeting the same `role`, but in different contexts that will actually require different db queries.**

One way to do that could be to pass an argument to our `graphql query` and then, on the `api` side, `switch` over it to trigger different method calls.
Though it's a valid way to handle this in some cases, I wasn't too keen this time on doing so. I'd prefer to keep my methods and endpoints explicit and focused, which I found to be more scalable.

To do so I created 3 endpoints, each with their own api services & separate methods, to fetch my students in their different context. And to make sure this would be used properly in front, I relied on `createCell` ( formerly `withCell`), to select the query I'd need to call:

```tsx
import { createCell } from '@redwoodjs/web'  
import { userToOption } from 'src/components/Model/User'  
import { Input, Select } from 'src/ui'  
  
interface CellProps {  
  sessionId?: number  
  courseId?: number  
}  
  
export const QUERY_ALL_STUDENTS = gql`  
 query QUERY_ALL_STUDENTS($where: WhereUserInput) {  
   options: students(where: $where) {  
     id  
     firstname
	 lastname  
   }
}`  
  
export const QUERY_SESSION_STUDENTS = gql`  
 query QUERY_SESSION_STUDENTS($id: Int) {  
   options: getSessionStudents(id: $id) {  
     id  
     firstname
	 lastname  
   }
}`  
  
export const QUERY_COURSE_STUDENTS = gql`  
 query QUERY_COURSE_STUDENTS($id: Int) {  
   options: getCourseStudents(id: $id) {
     id  
     firstname
	 lastname  
   }
}`  
  
const Loading = () => (  
  <Input name={'student'} disabled pointer={'model:student.label'} />  
)  
  
const Success = ({ selectProps, name, options }) => {  
  return (  
    <Select  
      {...selectProps}  
      name={name}  
      pointer={'model:student.label'}  
      options={options?.map((student) => userToOption(student))}  
    />  
 )  
}  
  
export default function ({ sessionId, courseId }: CellProps) {  
  const { query, id } = React.useMemo(() => {  
    switch (true) {  
      case Boolean(sessionId && !courseId):  
        return { id: sessionId, query: QUERY_SESSION_STUDENTS }  
      case Boolean(!sessionId && courseId):  
        return { id: courseId, query: QUERY_COURSE_STUDENTS }  
      default:  
        return { query: QUERY_ALL_STUDENTS }  
    }  
  }, [sessionId, courseId])  
  
  return createCell({  
    QUERY: query,  
    displayName: 'StudentsSelect',  
    Loading,  
    Success,  
  })({ id })  
}
```

I think this is the cleanest way I've found so far to deal with this.

It let's me keep a very clean API - which I'm going to really need further on as this is an important part of our business, and it lets me avoid creating dozens of the same component with only one prop to differentiate them.

So at the end of the day, I feel like it's clean enough on the `web` and `api` sides of my lawn.


Cheers,

---

Notes

---

- Notice how each query has their own name? Whichever way you want to tackle such issue, always keep in mind graphql client will require you to use query names as if they were ids. RedwoodJS will let some warning show if you don't comply.
- Cells are documented [here](https://learn-redwood.netlify.app/docs/tutorial/cells), [here as well](https://redwoodjs.com/docs/cells) and [here](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx)

---