Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions www/src/docs/exampleComponents/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ComponentType, ReactNode } from 'react';
import { ToolType } from '../../components/Playground/ToolFrame.tsx';

export type ChartExample = {
export type ChartExample<ControlsType = any> = {
/**
* The React component that represents the example.
* It should be a functional or class component that renders a chart.
* It does not accept any props.
* It may accept props coming from its associated controls
*/
Component: ComponentType;
Component: ComponentType<ControlsType>;
/**
* This component renders knobs, controls, and various other activities that change the chart
*/
Controls?: ComponentType<{ onChange: (values: ControlsType) => void }>;
/**
* The source code of the example.
*/
Expand All @@ -20,7 +25,7 @@ export type ChartExample = {
* Extra information about the example.
*/
description?: ReactNode;
defaultTool?: 'source' | 'devtools' | 'controls';
defaultTool?: ToolType;
defaultToolTab?: string;
};

Expand Down
1 change: 1 addition & 0 deletions www/src/views/APIViewNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ function ApiExamples({ examples, componentName }: ApiExamplesProps) {
<CodeEditorWithPreview
Component={example.Component}
sourceCode={example.sourceCode}
Controls={example.Controls}
stackBlitzTitle={`Recharts API example: ${componentName} - ${example.name || `Example ${i + 1}`}`}
analyticsLabel={`${componentName}-api-example-${i}`}
defaultTool={example.defaultTool}
Expand Down
47 changes: 16 additions & 31 deletions www/src/views/ExamplesView.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
import React from 'react';
import Helmet from 'react-helmet';
import { allExamples } from '../docs/exampleComponents';
import './ExampleView.css';
import { RouteComponentProps, withRouter } from '../routes/withRouter';
import { ComponentExamples } from '../docs/exampleComponents/types.ts';
import { ChartExample } from '../docs/exampleComponents/types.ts';
import { CodeEditorWithPreview } from '../components/CodeEditorWithPreview';
import { NotFoundView } from './NotFoundView.tsx';

type ExampleComponent = {
cateName: string;
exampleName: string;
ExampleComponent: React.ComponentType;
sourceCode: string;
description?: React.ReactNode;
};

const parseExampleComponent = (compName: string): ExampleComponent | null => {
const typeList = Object.keys(allExamples);
const res = typeList.filter(key => {
const entry: ComponentExamples = allExamples[key];

return !!entry.examples[compName];
});
const allExamplesFlattened: ReadonlyArray<[string, ChartExample]> = Object.values(allExamples)
.map(e => e.examples)
.flatMap(Object.entries);

if (res && res.length) {
const example = allExamples[res[0]].examples[compName];
return {
cateName: res[0],
exampleName: example.name,
ExampleComponent: example.Component,
sourceCode: example.sourceCode,
description: example.description,
};
}
return null;
const parseExampleComponent = (exampleName: string): ChartExample | undefined => {
return allExamplesFlattened.find(examples => examples[0] === exampleName)?.[1];
};
Comment on lines +9 to 15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Lookup key changed from example name to record key (can break URL resolution).

The parser now matches params.name against Object.entries(...)[0] (record key). But URL identity is documented as example name, so valid example URLs can resolve to NotFoundView when key and example.name diverge.

🔧 Proposed fix
-const allExamplesFlattened: ReadonlyArray<[string, ChartExample]> = Object.values(allExamples)
-  .map(e => e.examples)
-  .flatMap(Object.entries);
+const allExamplesFlattened: ReadonlyArray<ChartExample> = Object.values(allExamples)
+  .flatMap(e => Object.values(e.examples));

 const parseExampleComponent = (exampleName: string): ChartExample | undefined => {
-  return allExamplesFlattened.find(examples => examples[0] === exampleName)?.[1];
+  return allExamplesFlattened.find(example => example.name === exampleName);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allExamplesFlattened: ReadonlyArray<[string, ChartExample]> = Object.values(allExamples)
.map(e => e.examples)
.flatMap(Object.entries);
if (res && res.length) {
const example = allExamples[res[0]].examples[compName];
return {
cateName: res[0],
exampleName: example.name,
ExampleComponent: example.Component,
sourceCode: example.sourceCode,
description: example.description,
};
}
return null;
const parseExampleComponent = (exampleName: string): ChartExample | undefined => {
return allExamplesFlattened.find(examples => examples[0] === exampleName)?.[1];
};
const allExamplesFlattened: ReadonlyArray<ChartExample> = Object.values(allExamples)
.flatMap(e => Object.values(e.examples));
const parseExampleComponent = (exampleName: string): ChartExample | undefined => {
return allExamplesFlattened.find(example => example.name === exampleName);
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@www/src/views/ExamplesView.tsx` around lines 9 - 15, The lookup in
parseExampleComponent uses the record key (entries()[0]) instead of the
documented example identity (the ChartExample.name), which can mis-resolve URLs;
update parseExampleComponent to compare params.name to the ChartExample.name
(the second element of each tuple) rather than the record key: use
allExamplesFlattened and parseExampleComponent to destructure each tuple to
(key, example) and match example.name (or example?.name) against exampleName,
returning the example object when it matches.


type ExamplesViewImplProps = RouteComponentProps;
Expand All @@ -41,7 +20,11 @@ function ExamplesViewImpl({ params }: ExamplesViewImplProps) {
const page = params?.name;
const exampleResult = parseExampleComponent(page);

const title = exampleResult?.exampleName ?? page;
if (!exampleResult) {
return <NotFoundView />;
}

const title = exampleResult?.name ?? page;

return (
<div className="page page-examples">
Expand All @@ -54,9 +37,11 @@ function ExamplesViewImpl({ params }: ExamplesViewImplProps) {
<div className="example-inner-wrapper">
<CodeEditorWithPreview
key={page}
Component={exampleResult.ExampleComponent}
Component={exampleResult.Component}
sourceCode={exampleResult.sourceCode}
stackBlitzTitle={`Recharts example: ${exampleResult.cateName} - ${exampleResult.exampleName}`}
Controls={exampleResult.Controls}
defaultTool={exampleResult.defaultTool}
stackBlitzTitle={`Recharts example: ${exampleResult.name}`}
analyticsLabel={page}
/>
</div>
Expand Down
3 changes: 3 additions & 0 deletions www/test/docs/exampleComponents/exampleComponents.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ describe('Example Components', () => {
});

it('should all have unique names', () => {
/*
* All example names must be unique because in the URL they are only identified by the example name.
*/
const names = new Set<string>();
Object.values(allExamples).forEach(({ examples }) => {
Object.values(examples).forEach(example => {
Expand Down
Loading