Cell Architecture: Avoiding redundancy?

I am building a page for my application and confused about the optimal architecture as it relates to Redwood cells.

Below I have rough mocks of what I would like my page to look like while the data is loading, versus when it has loaded:

Note that the Demographics section of the page includes almost all the same fields as the Header section, except that it also has an additional two fields (Ethnicity and Sex).

My intuition is that the “Redwood Way” of building this would look like below:

Page.tsx

<h1>Page Header</h1>
<PageHeaderCell />

<Tab.Group>
  <Tab.List>
    <Tab>Details</Tab>
    <Tab>Timeline</Tab>
  </Tab.List>
  <Tab.Panels>
    <Tab.Panel>
     <DemographicsCell />
    <PreferencesCell />
   </Tab.Panel>
  <Tab.Panels>
<Tab.Group />

My alarm bells are going off realizing that in this setup, PageHeaderCell and DemographicsCell will be almost identical - except that DemographicsCell will also request 2 additional fields. But both cells will hit the API for the same 4 fields at the same time which seems completely unnecessary.

The smarter thing to do would intuitively be to combine the PageHeader and Demographics into a single cell, which requests all 6 fields - and then the PageHeader would only use 4 of 6 fields. But then how do I show the Tab Content (Details and Timeline) in between these sections while the data loads? This seems very messy:

PageCell.tsx

export const Loading = () => (
<h1>PageHeader</h1>
<LoadingSpinner />
<Tab.Group>
 ...
  <Tab.List>Details</Tab.List>
  <Tab.List>Timeline</Tab.List>
  ...
  <Tab.Panels>
    <Tab.Panel>
      <h2>Demographics</h2>
     <LoadingSpinner />
   </Tab.Panel>
</Tab.Group>
)

export const Success = ({ data }) => (
<h1>PageHeader</h1>
<PageHeaderComponent data={data} />
<Tab.Group>
 ...
  <Tab.List>Details</Tab.List>
  <Tab.List>Timeline</Tab.List>
  ...
  <Tab.Panels>
    <Tab.Panel>
      <h2>Demographics</h2>
      <DemographicsComponent data={data} />
     ...
  </Tab.Panel>
</Tab.Panels>
</Tab.Group>
)

Now I have very unwieldy and redundant Loading and Success functions. Is there a smarter way to do this?

Perhaps the renderProps pattern might help here. [ref: Render Props Pattern] (search for “Children as a function”)

Something like this?

<UserDataCell>
{userData => (
   <>
      <PageHeader value={userData} />
      <TabGroup />
      <Demographics value={userData} />
      <Preferences value={userData} />
   </>
)}
</UserDataCell>

and in your UserDataCell

export const Success = ({
  userData,
}: UserDataProps) => (
    {props.children(userData)}
);
1 Like

One thing to bear in mind with GraphQL and Cells is that you don’t need a one-to-one mapping of a cell to a table query … your Cells can make a request for nested data and then pass that entire data payload to regular components to render.

Ie, you do not need a GraphQL request for each:

  • page header
  • demographics
  • preferences

You can make one graphql request for:

query MySingleQuery {
  pageHeader {
    name
    homeTown
    demographics {
      age
     ethnicity
    }
   preferences {
     actionMoviesRating
     romanticComediesRating
    mysteryRating
   }
  }
}

Note: I am assuming this is an authenticated request and the data is for the currentUser. But if this is for any profile, then it’s a little different.

And then I’d make a customService that is like “profile” and have that fetch the currentUser and query and include all three tables for the tables and have a new SDL type called “profile” that combines all the data for that api request payload.

query MyProfile {
  profile {
    name
    homeTown
    demographics {
       age
       ethnicity
     }
    preferences {
     actionMoviesRating
     romanticComediesRating
     mysteryRating
    }
  }
}

That way you have just one query and one cell and can have one loading spinner or skeleton for the page.

1 Like