Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot wrap Line with customized component #412

Open
huozhi opened this issue Dec 27, 2016 · 34 comments
Open

Cannot wrap Line with customized component #412

huozhi opened this issue Dec 27, 2016 · 34 comments
Labels
bug General bug label
Milestone

Comments

@huozhi
Copy link

huozhi commented Dec 27, 2016

I was trying to wrap <Line /> with my customized component. Like

const CurveLine = (props) => {
  // do some transform with props here
  return (
    <Line
      ....
    />
  )
}

but the Line didn't render in the chart. Is there some way to do it correctly ? : )

@xile611
Copy link
Member

xile611 commented Dec 28, 2016

@huozhi Line will render svg nodes, you can wrap it with <Surface> or <svg>.

@xile611 xile611 closed this as completed Jan 3, 2017
@Place1
Copy link

Place1 commented Mar 27, 2018

I just ran into this issue. I'm trying to do the following:

class MyLineChart extends React.Component {
  render() {
    return (
      <Recharts.LineChart
          width={width}
          height={height}
          data={props.data}
        >
          <Recharts.CartesianGrid vertical={false} />
          <Recharts.XAxis dataKey={props.xAxisDataKey} tickLine={false} axisLine={false} />
          <Recharts.YAxis tickLine={false} axisLine={false} />
          <Recharts.Tooltip />
          {props.children}
        </Recharts.LineChart>
    );
}

export function MyLine(props) {
  return (
    <Recharts.Line
      dataKey={props.dataKey}
      stroke={props.color}
      strokeWidth={3}
      dot={false}
    />
  );
}

// ... usage

<LineChart data={data} xAxisDataKey="time">
  <Line dataKey="example" color={palette.darkGrey} />
</LineChart>

No lines render (although the tooltip does work). thoughts? btw there's no Surface component in recharts as far as I can see.

@cjies
Copy link
Contributor

cjies commented May 31, 2018

I found a workaround to customize original <Line> component by overwriting its default props.

import { LineChart, Line as ReLine } from 'recharts';

// Extends the Line component to overwrite its default props
class Line extends ReLine {
  static defaultProps = {
    ...ReLine.defaultProps,
    // Customized props
    strokeWidth: 2,
    dot: false,
  };
}

// In real use case
<LineChart data={data}>
  <Line dataKey="example" />
</LineChart>

@reneeichhorn
Copy link

I think this should be reopened as it still is an issue. The workaround from @cjies works but that's certainly not how it should be.
Wrapping components is a very standard thing to do in react. I believe that it should be fixed or at least it should be documented on how to do it in a proper way.

@xile611

@judyzaratan
Copy link

@xile611 Yes I agree I think this is still an issue. I think that workaround is just adding more code and over declaration for wrapping components. I have encountered the same issue.

I am currently using Recharts v1.0.0 and was trying to write a wrapped component that had too much prop settings on style that hindered readability for a bar chart.

<XAxis
     domain={[0, 1]}
     ticks = {[
       0, 0.25, 0.50, 0.75, 1,
     ]}
     tickFormatter={toPercent}
     tickCount={5}
     type="number"
     tickLine={false}
     tick={{fontFamily: 'Mallory', fontSize: '8px', fontWeight: 500, fill: COLOR.GREY_BAR_CHART}}
     stroke={COLOR.GREY_GRID_LINE}
/>

// creating a wrapped component does not work
export const CustomizedXAxis = () => { 
    return 
       <XAxis>
        ...
       </XAxis>
}

// usage
<BarChart>
     ...
     <CustomizedXAxis/>
</BarChart>

@tibudiyanto
Copy link

tibudiyanto commented Aug 7, 2018

Facing same issue.
My ugly workaround is to create a function that returns the wrapped component with the desired props

const ReLine = props => {
  return <Line {...props} strokeWidth={2} />;
};
// and then use it like this
<LineChart width={400} height={200} data={data}>
        {ReLine({
          type: "monotone",
          label: "value",
          dataKey: "value",
          stroke: "#137752"
        })}
</LineChart>

@sscaff1
Copy link

sscaff1 commented Mar 13, 2019

If you set the statics, defaultProps, and displayName of the returned component from the HOF then you can wrap the components.

Let's say I had a HOF that provided me a theme (from a ThemeProvider) as an example. Below is an example of wrapping the Line component:

import { Line as RCLine } from 'recharts';

const Line = ({ theme, ...props }) => {
  console.log(theme);
  return <RCLine {...props} type="monotone" dataKey="uv" stroke="#82ca9d" />;
};

const ThemedLine = withTheme(Line);

ThemedLine.defaultProps = RCLine.defaultProps;
ThemedLine.displayName = RCLine.displayName;
ThemedLine.getComposedData = RCLine.getComposedData;
ThemedLine.repeat = RCLine.repeat;
ThemedLine.renderDotItem = RCLine.renderDotItem;

I also tried this with react-redux's connect and it works fine. Hope this helps.

@kalbert312
Copy link

kalbert312 commented Sep 3, 2019

Here's another workaround:

Use the same approach that @sscaff1 is using only on the top-level chart components (BarChart, LineChart, etc), then map the children if the displayName is XAxis, YAxis etc.

EDIT: The below example doesn't work without properly subtracting the component's defaultProps from c.props... but it's close. The main advantage is that the default recharts components can be imported and there's less ugly <Component {...myPropsFn({ props }}) going on from the user.

Example:

export const fixWrapperFC = <P extends BP, BP = {}>(b: React.ComponentType<BP>, wrapperFC: React.FC<P>, defaultProps?: Partial<P>): React.FC<P> => {
	Object.assign(wrapperFC, b);
	if (defaultProps) {
		if (!wrapperFC.defaultProps) {
			wrapperFC.defaultProps = {};
		}
		Object.assign(wrapperFC.defaultProps, defaultProps);
	}
	return wrapperFC;
};

const BarChart: React.FC<BarChartProps> = fixWrapperFC(BarChartBase, (props) => {
	const { children, ...rest } = props;

        // use context hook to provide defaults (themes, etc)
	const ctx = React.useContext(MyThemeContext);

	return (
		<RCBarChart {...rest}>
			{React.Children.map(children, (c: any) => {
				if (!c || !c.type || !c.type.displayName) {
					return c;
				}
				const displayName = c.type.displayName;
				if (displayName === "XAxis") {
					return React.cloneElement(c, {
						...YOUR_DEFAULT_PROPS_HERE,
						...c.props,
					});
				} else if (displayName === "YAxis") {
					return React.cloneElement(c, {
						...YOUR_DEFAULT_PROPS_HERE,
						...c.props,
					});
				}
				return c;
			})}
		</RCBarChart>
	);
}, { <your bar chart default prop overrides here> });

@hcomnetworkers
Copy link

hcomnetworkers commented Sep 16, 2019

Why is this closed? Wrapping still does not work on anything.
I've been trying to split my graph up into its components into separate files. (Tooltip, axes, lines etc.).

@sscaff1 where does your whithTheme come from? I can't find it anywhere.

@tibudiyanto's solution does not work if you use hooks in the out-sourced component.

@JRhodes95
Copy link

I also want to vote for this to be reopened, we have used Recharts to build a distinct visual style for our charts but the code for each element is complex. Having to copy and paste it to new charts rather than build our own building blocks on top is bad practice and not how React is meant to be used.

const LimitArea = ({
  y1, y2, variant, label, labelPosition, fill, fillOpacity, alwaysShow,
}) => {
  console.log(variant);
  return (
    <ReferenceArea
      y1={y1}
      y2={y2}
      variant={variant}
      label={{
        position: 'right',
        value: label,
        fontSize: '1rem',
        fill: Colors.gray.hex,
      }}
      labelPosition={labelPosition}
      fill={fill}
      fillOpacity={fillOpacity}
      alwaysShow={alwaysShow}
    />
  );
};

Even just wrapping a reference area in a functional component prevents it from working. I'm aware I'm probably doing something wrong, but this is a common pattern in React and it is not documented how to use the Recharts components in this way.

@oziniak
Copy link

oziniak commented Feb 28, 2020

The issue still persist and @sscaff1's workaround doesn't work for me. I get the line with the sizes of 0x0 rendered for some reason.

This is such a react-y thing composing of components, and with this lib, you can't just simply do const MyLine = (props) => <Line {...props} />.

This thing should be definitely mentioned in the README and at the API docs page at the very top with bold and uppercase: WE DON'T SUPPORT WRAPPING COMPONENTS.

UPD: workaround still works, but you need to pass dataKey to MyLine, and can't hardcode it on Line;

@m3h0w
Copy link

m3h0w commented Mar 5, 2020

+1

@eduhenke
Copy link

+1, why was this closed???

@gaurav5430
Copy link

gaurav5430 commented Apr 22, 2020

+1
there should be an easy way to do this.
Please reopen

@sajera
Copy link

sajera commented Apr 25, 2020

+1

@goneglobal
Copy link

recharts sux +1

@gaurav5430
Copy link

recharts sux +1

Did you find a better alternative for your use case?
@goneglobal

@houston88
Copy link

This would be nice to fix

@Ecostack
Copy link

I am still facing the same issue, how come this has been closed?
As others mentioned, wrapping components is quite standard practise.

@vidjuheffex
Copy link

Yep, I use a ForEach component with renderprops instead of data.mapping and introducing the ForEach breaks my ability to map over Cells

@QuevedoIB
Copy link

QuevedoIB commented Nov 5, 2021

This should be reopened since is breaking the base pattern to compose react applications @xile611

@jake-daniels
Copy link

@xile611 Please, reopen this. This is a serious issue that collides with basic React approach to composition.

@rahul-desai3
Copy link

+1

1 similar comment
@flex-byoungjun
Copy link

+1

@svadalia
Copy link

svadalia commented Nov 3, 2022

+1

4 similar comments
@amaljosea
Copy link

+1

@wencakisa
Copy link

+1

@gmatto
Copy link

gmatto commented Apr 18, 2023

+1

@BeHustle
Copy link

+1

@ckifer ckifer reopened this May 30, 2023
@ckifer
Copy link
Member

ckifer commented May 30, 2023

Re-opened this (unsure why it was closed like a lot of other issues). Unfortunately, recharts items such as Line, Area, etc. rely on their parent because their parent calls the (very complex and bloated) generator function that does almost all of the logic for recharts charts. Without that function Line, etc. have basically no functionally. This is an unfortunate design flaw that hasn't aged well as React has matured. Recharts needs a refactor of huge proportions in order to be able to support standard composition. It essentially created its own rules for what can be composed and what cannot.

@lesderid
Copy link

@sscaff1's solution didn't work for me for some props (e.g. legendType), @tibudiyanto's workaround did however.

@KristofaJosh
Copy link

KristofaJosh commented Mar 21, 2024

this worked for me:

import { Bar } from 'recharts';
import type { BarProps } from 'recharts/types/cartesian/Bar';
...
export const CustomBar = ({ className, ...props }: BarProps) => {
  return (
    <Bar
      {...props}
      {...barChartDefaultProps}
      className={cn(barChartDefaultProps.className, className)}  // clsx
    />
  );
};
CustomBar.displayName = Bar.displayName;
CustomBar.getComposedData = Bar.getComposedData;
CustomBar.defaultProps = Bar.defaultProps;

referenced @tibudiyanto turned out Bar.getComposedData is required for this to work properly (context in the barChart related to xAxisId)

@ckifer ckifer added the bug General bug label label Mar 28, 2024
@jasiene
Copy link

jasiene commented Apr 23, 2024

Following @tibudiyanto's approach, it seems like the most straightforward option is to return your wrapped Pie, Bar, Line etc from a function and stick that returned component directly into your JSX as a value, ie:

const MyCustomPie = useMyCustomPie();

return (
    <PieChart>
        {MyCustomPie}
    </PieChart>
)

rather than

return (
    <PieChart>
        <MyCustomPie/>
    </PieChart>
)

I personally stuck it in a hook as there's a bunch of React specific things I need to do before returning the JSX.

@ckifer
Copy link
Member

ckifer commented Apr 24, 2024

this should be addressed once 3.x comes out

@ckifer ckifer added this to the Recharts@3.0 milestone Apr 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug General bug label
Projects
None yet
Development

No branches or pull requests