Cells and children

Thanks! I have thought about this approach, too!

I am quite happy right now with my hooks approach. I was using a simple useQuery wrapper, that would use the QUERY variable of the Cell. But I changed to an approach with Context Provider. This gives me safety in consumption of hooks, since they need to be within the provider. - I think this approch did not come to my mind, since react context is not very performant. But I implemented it now with zustand. Zustand context is already much more performant than usage of useQuery. I will share the code - could be easily adapted to a custom generator for cells, too.

Context

createCellContext.tsx

import { createContext, PropsWithChildren, useContext, useRef, useLayoutEffect } from 'react'
import { createStore, useStore } from 'zustand'

/**
 * zustand context
 * https://docs.pmnd.rs/zustand/guides/initialize-state-with-props
 * */
export function createCellContext<T extends any>() {
  interface CellContextProps {
    data: T
  }

  type CellContextStore = ReturnType<typeof createCellContextStore>

  const createCellContextStore = (initProps: CellContextProps) => {

    return createStore<CellContextProps>()((set) => ({
      ...initProps
    }))
  }

  const CellContext = createContext<CellContextStore | null>(null)
  type CellContextProviderProps = PropsWithChildren<CellContextProps>

  function CellContextProvider({ children, ...props }: CellContextProviderProps) {
    const storeRef = useRef<CellContextStore>(createCellContextStore(props))
    if (!storeRef.current) {
      storeRef.current = createCellContextStore(props)
    }

    useLayoutEffect(()=>{
      // update store, if props change
      storeRef.current.setState({data: props.data})
    },[props])

    return <CellContext.Provider value={storeRef.current}>
      {children}
    </CellContext.Provider>
  }

  function useCellContext() {
    const store = useContext(CellContext)

    if (!store) throw new Error('Missing CellContextProvider in the tree')
    const storeData = useStore(store, (state) => state.data)
    return storeData
  }
  return {
    CellContextProvider,
    useCellContext

  }
}

Cell

And here consumption in Cell

TenantCell.tsx

import type { FindTenantById } from 'types/graphql'
import type { CellFailureProps, CellSuccessProps } from '@redwoodjs/web'
import { PropsWithChildren } from 'react'
import { createCellContext } from 'src/utils/createCellContext'
const { CellContextProvider, useCellContext } = createCellContext<FindTenantById['tenant']>()

export const QUERY = gql`
  query FindTenantById($id: String!) {
    tenant: tenant(id: $id) {
     id
  }
`

export const Loading = () => <div>Loading...</div>

export const Empty = () => <div>Tenant not found</div>

export const Failure = ({ error }: CellFailureProps) => (
  <div className="rw-cell-error">{error?.message}</div>
)

export const Success = ({ tenant, children }: 
PropsWithChildren<CellSuccessProps<FindTenantById>>) => {
  return <CellContextProvider data={tenant}>
    {children}
  </CellContextProvider>
}

export const useTenantCell = useCellContext
2 Likes