Search code examples
reactjstypescriptrechartshigher-order-components

How to type a component prop to a higher order component when you don't have access to the HOC?


I'm trying to pass some custom components to the Tooltip, Legend, XAxis, and YAxis components from the recharts library.

My issue is when I pass the component like

type Props = {
  x: number;
  y: number;
  payload: { value: string };
  isGroupedByHour: boolean;
};

const CustomXAxisTick = ({ x, y, payload, isGroupedByHour }: Props) => {...}

<XAxis dataKey="time" tick={<CustomXAxisTick />} />

I get a type error on the <XAxis .../> line: Type '{ }' is missing the following properties from type 'Props': x, y, payload. Same for all the custom components.

I've tried passing the components inline like:

<Legend
                    align="center"
                    content={(props) => (
                      <div className="flex flex-shrink-0 items-center justify-center gap-4">
                        {props.payload?.map((p) => (
                          <span
                            key={p.id}
                            className={`text-xs flex items-center gap-1 `}
                          >
                            <span className="h-2 w-2 bg-current" />
                            {p.dataKey}
                          </span>
                        ))}
                      </div>
                    )}
                  />

It works, but I need to reuse the components, so I want them as separate components.

I've also tried directly using the custom components like this:

const CustomLegend = (
  <Legend
    align="center"
    content={(props) => (
      <div className="flex flex-shrink-0 items-center justify-center gap-4">
        {props.payload?.map((p) => (
          <span
            key={p.id}
            className={`text-xs text-[${p.color}] flex items-center gap-1 `}
          >
            <span className="h-2 w-2 bg-current" />
            {
              GRAPH_LABELS.decisions[
                p.dataKey as keyof typeof GRAPH_LABELS.decisions
              ]
            }
          </span>
        ))}
      </div>
    )}
  />
);


<BarChart
        data={new Array(100).fill(0).map((row, idx) => {
          return {
            time: idx,
            pass: Math.random() * 10000,
            fail: Math.random() * 10000,
            cant_decide: Math.random() * 1000,
          };
        })}
        margin={{
          top: 20,
          right: 30,
          left: 20,
          bottom: 5,
        }}
      >
        <CustomLegend />
</BarChart>

But recharts does not render this component at all.

My question is how do I properly type my custom components so that I don't get any type errors.

Link to typescript playground


Solution

  • At least for the CustomXAxisTick and the CustomYAxisTick, it simply works to adapt your code as follows:

        <XAxis dataKey="time" tick={(props)=> <CustomXAxisTick {...props}/>} />
        <YAxis tick={(props)=> <CustomYAxisTick {...props} />} />
    

    In the other two cases, this does not work because the types you defined are not compatible with the recharts types.

    Property 'active' is optional in type '{ separator?: string | undefined; wrapperClassName?: string | undefined; labelClassName?: string | undefined; formatter?: Formatter<ValueType, NameType> | undefined; ... 24 more ...; useTranslate3d?: boolean | undefined; }' but required in type 'ToolTipProps'.

    Type '{ content?: ContentType | undefined; iconSize?: number | undefined; iconType?: IconType | undefined; layout?: LayoutType | undefined; align?: HorizontalAlignmentType | undefined; ... 476 more ...; key?: Key | ... 1 more ... | undefined; }' is not assignable to type 'LegendProps'. Types of property 'payload' are incompatible. Type 'Payload[] | undefined' is not assignable to type '{ id: string; color: string; dataKey: string; }[]'. Type 'undefined' is not assignable to type '{ id: string; color: string; dataKey: string; }[]'.

    In those two cases you must adapt your types to conform to the expectations from recharts.

    I guess this thus only is half an answer, but I hope it is also half valuable, thus already sharing.