React/Typescript: How do I pass and "type" props down through multiple components

Viewed 178

In my React/Typescript project, I have a challenge:

  1. From the Child component I pass a prop class down:

    Child class={{ myBanner: styles.myBanner } />

    I typed class prop as follows:

     import { SerializedStyles } from '@emotion/react';
    
     import { Class as MyCustomBannerClass } from './MyBanner/MyBanner.types';
    
     type Class = Partial<Record<'root', SerializedStyles>> & {
       myBanner: MyCustomBannerClass;
     };
    
     export type Props = {
       class: Class;
     };
    

  2. Inside Child component I have <MyBanner/> component, where I also have a class prop:

    export type Class = Partial<Record<'root', SerializedStyles>>;
    
    export type Props = {
      class?: Class;
    };
    
    <MyBanner class={props.class?.myBanner} />
    

This is all working fine.
Now from within Parent component, through Child and <MyBanner/>,
I am able to override a css style in Parent from <MyBanner /> component.


The challenge:

Now I have a case:

  • Inside Child, I have another child <AnotherChild/>.

  • And within <AnotherChild />, I have <MyBanner/> component.

Question:

How do I pass and type class={{ myBanner: styles.myBanner } through both...

  1. Child:<Child class={{ myBanner: styles.myBanner } />

  2. And AnotherChild: <AnotherChild class={???} />

...and pass it down to <MyBanner class={props.class?.myBanner} />?

1 Answers

This looks like a problem that can easily be solved by using React.useContext
...in other words, the ContextAPI. Here's the official docs about it.

To tackle your issue specifically, you can useContext like this:
Parent.tsx

/** PARENT **/

import React from 'react';
import { SerializedStyles } from '@emotion/react';
import { Class as MyCustomBannerClass } from './MyBanner/MyBanner.types';

// define parent context
type Class = Partial<Record<'root', SerializedStyles>> & {
  myBanner: MyCustomBannerClass;
};

interface Props {
  parentClass?: Class,
};

export const ParentContext = React.createContext({} as Props);

export const Parent = props => {
  const parentClass = React.useRef({ myBanner: styles.myBanner });

  const contextProps = {
    parentClass: ..., // add props to be passed down
  };

  return (
    // We pass (or literally provide) "parentClass" (and more) 
    // to all the children of "Parent"
    <ParentContext.Provider value={{ ...contextProps }}>
      <Child />
    </ParentContext.Provider>
  )
};

Child.tsx

// FIRST-CHILD

import React from 'react';

// define context
type Class = Partial<Record<'root', SerializedStyles>>;

interface Props {
  childClass?: Class,
};

export const ChildContext = React.createContext({} as Props);

// define component
export const Child = props => {
  const childClass = React.useRef({ ... })

  const contextProps = {
    childClass: ..., // add props to be passed down
  };

  return (
    // Both "MyBanner" and "AnotherChild" are provided with both `class` props
    // from "Parent" and "Child" and it can go on and on...
    <ChildOneContext.Provider value={{ ...contextProps }}> 
      <MyBanner />
      <AnotherChild />
    </ChildOneContext.Provider>
  )
};

AnotherChild.tsx (You can do the same in "MyBanner")

// SECOND-CHILD

import React from 'react';
import { ParentContext } from './Parent';
import { ChildContext } from './Child';

// define component
const AnotherChild = props => { 
  const { parentClass } = React.useContext(ParentContext);
  const { childClass } = React.useContext(ChildContext);

  return (
    <div className={ParentClass}>
      <div className={childClass}></div>
    </div>
  )
};

Note: If you want a child component to override the class prop in any case, you can define the classes with useState instead of useRef as I've done in this case.


That said, at the back of your mind, remember that ContextAPI is designed to share data that can be considered “global” for a tree of React components, such as your case!

Related