Skip to content

Comments

[EXAMPLE ONLY DO NOT MERGE] Example of user-defined custom hook in theme#78

Open
cwbuecheler wants to merge 1 commit intomainfrom
CS-239/custom-hook-example
Open

[EXAMPLE ONLY DO NOT MERGE] Example of user-defined custom hook in theme#78
cwbuecheler wants to merge 1 commit intomainfrom
CS-239/custom-hook-example

Conversation

@cwbuecheler
Copy link

@cwbuecheler cwbuecheler commented Feb 9, 2026

This shows one way of implementing a custom hook that the user could define on the theme which would allow them access to the props for any given chart, and they could then manipulate those props as they like and return them.

Apologies, the typing for TS compatibility is a hack job, but speed's of the essence in this case! 😁

This is related to an issue NOQ are having where they need to hit an external API, get some stuff from it, and then use that stuff to manipulate props.results.data returned by loadData before doing anything in the chart. They have to do this for virtually every chart, with the results from the external API being different for each individual user and dataset, meaning potentially tens of thousands of different results.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added theme-level customization capability to enable dynamic prop transformation for charts.
    • Implemented data augmentation support for chart visualization.
  • Refactor

    • Restructured chart prop flow to utilize theme-based customization system.

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Walkthrough

The changes introduce a theme-based async props customization system. A new useCustomProps hook in the Theme type allows external transformation of chart properties before rendering. The BarChartDefaultPro component now integrates this hook via useEffect to dynamically manipulate props, particularly enriching data rows with calculated values based on chart dimensions.

Changes

Cohort / File(s) Summary
Theme Customization Infrastructure
embeddable.theme.ts, src/theme/theme.types.ts
Added BaseChartTypes type definition and optional useCustomProps async hook to Theme interface. Updated themeProvider to use defineTheme with hook implementation for BarChartDefaultPro, which augments data rows with a manipulatedValue derived from the dimension field.
BarChartDefaultPro Integration
src/components/charts/bars/BarChartDefaultPro/index.tsx
Integrated useCustomProps hook via useEffect to transform props before rendering. Rewired component to consume customProps throughout all logic paths, including i18n resolution, data processing, and downstream rendering dependencies.

Sequence Diagram

sequenceDiagram
    participant Component as BarChartDefaultPro
    participant Effect as useEffect Hook
    participant Theme as Theme System
    participant Transform as Props Transformer
    participant Render as Component Render

    Component->>Component: Mount with original props
    Component->>Effect: Trigger useEffect
    Effect->>Theme: Call theme.useCustomProps(props, 'BarChartDefaultPro')
    Theme->>Transform: Check chartName match and props shape
    Transform->>Transform: If matched: augment data with manipulatedValue
    Transform-->>Theme: Return transformed props
    Theme-->>Effect: Return customProps (transformed or original fallback)
    Effect->>Component: setState(customProps)
    Component->>Render: Render with customProps
    Render-->>Component: Derived values from customProps
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description explains the purpose and context well, but omits the formal 'Main changes' and 'Test evidence' sections specified in the template. Add 'Main changes' section detailing the three files modified and 'Test evidence' section documenting how the example was validated.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: demonstrating an example of a user-defined custom hook in the theme.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch CS-239/custom-hook-example

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 9, 2026

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@embeddable.theme.ts`:
- Around line 12-18: The current manipulation in manipulatedData uses
row[props.dimension.name] * 2 which will produce NaN because dimensions are
usually strings; change manipulatedData (the data.map callback) to use a numeric
measure instead (e.g., row[props.measure.name] or props.measures[0].name) for
arithmetic or explicitly coerce and validate the value (parseFloat/Number with
isFinite check and fallback) before assigning manipulatedValue, and add a guard
so manipulatedValue is set to a safe default when the source is not a valid
number.

In `@src/components/charts/bars/BarChartDefaultPro/index.tsx`:
- Line 41: The component initializes customProps with the incoming props causing
a brief flash of untransformed data; change the state initialization to null
(useState<BarChartDefaultProProps | null>(null)) and update all references to
handle null (render a loading placeholder or return null) until your async hook
sets customProps via setCustomProps; ensure prop type annotations and any
destructuring that uses customProps are guarded (e.g., in the render path and in
effects) so the component doesn't access fields on null.
- Around line 46-48: The Promise returned by
customPropsHook<BarChartDefaultProProps>(props, 'BarChartDefaultPro') lacks
error handling; add a .catch handler to that chain to catch rejections and call
setCustomProps(props) as a safe fallback (and optionally log the error), so the
component doesn't rely on a rejected Promise or stale state; ensure you modify
the call site where customPropsHook is invoked and touch setCustomProps only in
the success and error handlers.
- Around line 43-52: The current useEffect launching theme.useCustomProps
(customPropsHook) can suffer a race where slower promises overwrite newer state;
modify the effect in BarChartDefaultPro to use a mounted/stale flag (e.g., let
cancelled = false) and flip it in the cleanup so that after awaiting
customPropsHook<BarChartDefaultProProps>(...) you only call
setCustomProps(result || props) when not cancelled; ensure the cleanup sets the
flag to true and that you handle both the async branch and the immediate
setCustomProps(props) branch consistently to prevent stale updates.
🧹 Nitpick comments (3)
src/theme/theme.types.ts (2)

56-60: BaseChartTypes assumes a single dimension, which may not fit all chart types.

Some charts (e.g., grouped bar charts) may accept multiple dimensions or use different prop shapes. If this type is meant to be the base constraint for all charts using useCustomProps, it could be too restrictive. Worth considering whether dimension should be Dimension | Dimension[] or if the constraint should be loosened for broader applicability.


68-69: The use prefix on useCustomProps implies a React hook, but the signature is a plain async function.

In the React ecosystem, the use prefix conventionally signals a React hook (subject to the Rules of Hooks). Since this is just an async transformation function on the theme object — not a hook — the naming could mislead consumers into thinking it follows hook semantics. Something like customizeProps or transformProps would avoid that confusion.

embeddable.theme.ts (1)

9-9: The typeof props === 'object' && props !== null guard is redundant.

Since T extends BaseChartTypes (which is an object type), props will always be an object when TypeScript types are respected. This check adds noise without practical value for an example. Minor point though — no action needed if you prefer the defensive style.

Comment on lines +12 to +18
const manipulatedData = data.map((row) => {
// Example manipulation: add a new field to each row based on existing fields
return {
...row,
manipulatedValue: row[props.dimension.name] * 2, // Just an example manipulation
};
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

row[props.dimension.name] * 2 will likely produce NaN — dimension values are typically strings.

Dimension values (e.g., category names like "Q1", "Electronics") are usually strings, so multiplying by 2 yields NaN. Even as example code, this would silently corrupt the data and confuse anyone using it as a reference. Consider using a measure field for the arithmetic, or a string transformation for the dimension:

Example fix using a measure value instead
         const manipulatedData = data.map((row) => {
           return {
             ...row,
-            manipulatedValue: row[props.dimension.name] * 2, // Just an example manipulation
+            manipulatedValue: row[props.measures[0]?.name] * 2, // Example: double the first measure's value
           };
         });
📝 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 manipulatedData = data.map((row) => {
// Example manipulation: add a new field to each row based on existing fields
return {
...row,
manipulatedValue: row[props.dimension.name] * 2, // Just an example manipulation
};
});
const manipulatedData = data.map((row) => {
// Example manipulation: add a new field to each row based on existing fields
return {
...row,
manipulatedValue: row[props.measures[0]?.name] * 2, // Example: double the first measure's value
};
});
🤖 Prompt for AI Agents
In `@embeddable.theme.ts` around lines 12 - 18, The current manipulation in
manipulatedData uses row[props.dimension.name] * 2 which will produce NaN
because dimensions are usually strings; change manipulatedData (the data.map
callback) to use a numeric measure instead (e.g., row[props.measure.name] or
props.measures[0].name) for arithmetic or explicitly coerce and validate the
value (parseFloat/Number with isFinite check and fallback) before assigning
manipulatedValue, and add a guard so manipulatedValue is set to a safe default
when the source is not a valid number.


The `useCustomProps` hook should take two arguments, the props, and an additional string telling it which chart is being rendered. This allows the hook to be used for multiple charts and manipulate the props differently based on the chart type.
*/
const [customProps, setCustomProps] = useState<BarChartDefaultProProps>(props);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Initial state mirrors props, so the chart briefly renders with unmodified data before the async hook resolves.

This means users see the original data flash before the manipulated data appears. Depending on how different the transformed data is, this could be a noticeable flicker. If this is a concern, consider initializing customProps to null and showing a loading state until the hook resolves, or keeping the current behavior if the brief flash is acceptable for your use case.

🤖 Prompt for AI Agents
In `@src/components/charts/bars/BarChartDefaultPro/index.tsx` at line 41, The
component initializes customProps with the incoming props causing a brief flash
of untransformed data; change the state initialization to null
(useState<BarChartDefaultProProps | null>(null)) and update all references to
handle null (render a loading placeholder or return null) until your async hook
sets customProps via setCustomProps; ensure prop type annotations and any
destructuring that uses customProps are guarded (e.g., in the render path and in
effects) so the component doesn't access fields on null.

Comment on lines +43 to +52
useEffect(() => {
const customPropsHook = theme.useCustomProps;
if (customPropsHook) {
customPropsHook<BarChartDefaultProProps>(props, 'BarChartDefaultPro').then((result) =>
setCustomProps(result || props),
);
} else {
setCustomProps(props);
}
}, [props, theme.useCustomProps]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Race condition: stale async responses can overwrite newer state.

When props changes rapidly, multiple async useCustomProps calls are fired in parallel. There's no cleanup/cancellation, so a slow response for stale props can resolve after a newer one, overwriting customProps with outdated data. This is a well-known pitfall with async work inside useEffect.

A simple fix is to use a boolean flag that's flipped in the cleanup function:

Proposed fix with staleness guard
  useEffect(() => {
+   let stale = false;
    const customPropsHook = theme.useCustomProps;
    if (customPropsHook) {
-     customPropsHook<BarChartDefaultProProps>(props, 'BarChartDefaultPro').then((result) =>
-       setCustomProps(result || props),
-     );
+     customPropsHook<BarChartDefaultProProps>(props, 'BarChartDefaultPro')
+       .then((result) => {
+         if (!stale) setCustomProps(result || props);
+       })
+       .catch(() => {
+         if (!stale) setCustomProps(props);
+       });
    } else {
      setCustomProps(props);
    }
+   return () => { stale = true; };
  }, [props, theme.useCustomProps]);
🤖 Prompt for AI Agents
In `@src/components/charts/bars/BarChartDefaultPro/index.tsx` around lines 43 -
52, The current useEffect launching theme.useCustomProps (customPropsHook) can
suffer a race where slower promises overwrite newer state; modify the effect in
BarChartDefaultPro to use a mounted/stale flag (e.g., let cancelled = false) and
flip it in the cleanup so that after awaiting
customPropsHook<BarChartDefaultProProps>(...) you only call
setCustomProps(result || props) when not cancelled; ensure the cleanup sets the
flag to true and that you handle both the async branch and the immediate
setCustomProps(props) branch consistently to prevent stale updates.

Comment on lines +46 to +48
customPropsHook<BarChartDefaultProProps>(props, 'BarChartDefaultPro').then((result) =>
setCustomProps(result || props),
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling on the Promise — unhandled rejection if useCustomProps throws.

The PR description mentions calling an external API. If that API call fails, the rejected Promise is completely unhandled, which will trigger an unhandled promise rejection. The component would also be stuck showing stale customProps from a previous render. Adding a .catch() that falls back to the original props would keep the chart functional.

🤖 Prompt for AI Agents
In `@src/components/charts/bars/BarChartDefaultPro/index.tsx` around lines 46 -
48, The Promise returned by customPropsHook<BarChartDefaultProProps>(props,
'BarChartDefaultPro') lacks error handling; add a .catch handler to that chain
to catch rejections and call setCustomProps(props) as a safe fallback (and
optionally log the error), so the component doesn't rely on a rejected Promise
or stale state; ensure you modify the call site where customPropsHook is invoked
and touch setCustomProps only in the success and error handlers.

@cwbuecheler
Copy link
Author

I'm not going to address all the CodeRabbit stuff here because this code is not intended to be used. This whole branch can be ditched once it's determined if this is a project we want to undertake. If so, Rui or whomever will be the one to implement it, not me (most likely). It's just a quick example to help explain what we discussed w/ NOQ as a possbility.

const theme = defineTheme<Theme>(remarkableTheme, {
useCustomProps: async <T extends BaseChartTypes>(props: T, chartName: string): Promise<T> => {
// Example of a custom props hook that manipulates data from the results prop before it is used in the chart.
if (chartName === 'BarChartDefaultPro' && typeof props === 'object' && props !== null) {
Copy link
Collaborator

@mad-raccoon mad-raccoon Feb 9, 2026

Choose a reason for hiding this comment

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

with this approach all the charts BarChartDefaultPro will go throught the same process even if they dont need the extra data.

in the ideal scenario:

1 - bar chart that get the id-name from the api and them maps (does the api call)
2 - bar chart that gets the state-name from the api and them maps (does the api call)
3 - bar chart normal (doesnt do the api call because is not needed)

Copy link
Author

Choose a reason for hiding this comment

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

@mad-raccoon - This example puts the useCustomProps function in the R.Pro repo just to show it off in one place, but it wouldn't actually live there. It'd just be in the customers repo. So only customers that wanted to use the hook would define it. Sorry, that wasn't clear.

Copy link
Collaborator

Choose a reason for hiding this comment

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

i got it, but this would mean that, if defined, it would apply to all the bar charts, correct?

my concern here is to avoid this for the barcharts that dont need it

Copy link
Author

Choose a reason for hiding this comment

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

Ah, right - yeah, we may need to add some kind of additional way they can say that a specific chart should use the custom props hook. Because yes if they had five bar charts and only needed one of them to run this functionality, that'd be an issue. It'd run for all five.

Copy link
Collaborator

Choose a reason for hiding this comment

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

i have 1 idea that might do the trick
let me take some time to test it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants