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

428 Views Asked by At

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

3

There are 3 best solutions below

5
Nikolas Rieble On BEST ANSWER

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.

5
stasdes On

you can pass the component reference like this:

import {
  ...,
  TooltipProps,
  LegendProps,
} from "recharts";

const CustomTooltip = ({ active, payload, label }: TooltipProps<number, string>) ...

const CustomLegend = (props: LegendProps) => ( ...

  <XAxis dataKey="time" tick={CustomXAxisTick} />
  <YAxis tick={CustomYAxisTick} />
  <Tooltip content={CustomTooltip} />
  <Legend align="center" content={<CustomLegend />} />
0
GoodMan On

You can consider the following solution. Although I didn't test this code. But I was able to pass all of the checks in the playground. I passed your custom props after copying the props from rechart.

I also use the as operator to tell typescript to treat our values as the props we defined.

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

<Tooltip content={(props) => <CustomTooltip {...props as ToolTipProps}/>} />

<Legend align="center" content={(props) => <CustomLegend {...props as unknown as LegendProps} />} />