This guide is the authoritative reference for generating Ignite UI React functional component samples programmatically. Every construct a generated sample may need is covered below with:
- When to use it — the trigger condition in the source class
- Class template — the exact pattern that appears in class-based originals
- Functional template — the exact generated output
- Rules — edge cases, ordering requirements, and common mistakes to avoid
Companion document: FUNCTIONAL_REFACTORING_README.md — a sample-by-sample mapping of what was refactored and why.
- File skeleton
- Imports
- Module registration
- State
- Refs — component or DOM element accessed imperatively
- Cross-component wiring — instance as a prop
- Cross-component wiring — imperative assignment after render
- Lazy getters and data sources
- ComponentRenderer
- Event handlers
- componentDidMount (one-time setup)
- componentWillUnmount (cleanup)
- setInterval management
- Async data loading
- Native / web-component event listeners
- Static or inline data
- Pure helper functions
- Aggregate functions (Pivot Grid)
- One-time side effects (icon registration)
- mountedRef guard (prevent post-unmount updates)
- Hook import checklist
- Complete generated skeleton
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
// ... more imports
export default class Sample extends React.Component<any, any> {
constructor(props: any) {
super(props);
// state init, .bind() calls
}
public render(): JSX.Element {
return ( /* JSX */ );
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Sample/>);import React, { /* only the hooks you need */ } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
// ... same component imports
// Module-level registrations, constants, pure functions go here
// (see sections 3, 16, 17, 18, 19)
export default function Sample() {
// hooks go here (useState, useRef, useMemo, useCallback, useEffect)
return ( /* same JSX, `this.` removed */ );
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Sample/>);Rules
- The component name stays the same.
- The
ReactDOM.createRootline is identical. constructor,super(props), and all.bind()calls are deleted entirely.
import React from 'react';import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react';Rules
- Only import the hooks that are actually used (see Section 21 for a decision tree).
- Do not import hooks that are not needed — unused imports generate lint warnings.
Module registration already lives at module scope in most originals. No change is needed — keep it identical.
const mods: any[] = [
IgrDataPieChartModule,
IgrItemLegendModule,
];
mods.forEach((m) => m.register());or
IgrFinancialChartModule.register();
IgrLegendModule.register();// identical — stays at module scope
const mods: any[] = [
IgrDataPieChartModule,
IgrItemLegendModule,
];
mods.forEach(m => m.register());Rules
- Never move registration inside the component function body — it would re-run on every render.
- The
(m) => m.register()arrow can optionally be shortened tom => m.register().
Trigger: the class has this.state = { ... } in the constructor and this.setState(...) calls in methods.
constructor(props: any) {
super(props);
this.state = {
chartType: 'Auto',
isLoading: false,
items: [],
};
}
// inside a method:
this.setState({ chartType: 'Bar' });
this.setState({ isLoading: true, items: [] }); // multiple keys at once// One useState per logical piece of state
const [chartType, setChartType] = useState('Auto');
const [isLoading, setIsLoading] = useState(false);
const [items, setItems] = useState<any[]>([]);
// inside a handler:
setChartType('Bar');
setIsLoading(true);
setItems([]);Rules
- Split state into independent pieces; do not put everything in one
useState({})object unless the fields are always updated together. - When multiple related fields are always updated atomically (e.g.
{ dataInfo, dataPoints }), a singleuseStateobject with a spread-update is acceptable:const [info, setInfo] = useState({ dataInfo: '500', dataPoints: 500 }); // update: setInfo(prev => ({ ...prev, dataInfo: '1K', dataPoints: 1000 }));
this.setStatemerges keys; the functional setter replaces the value — always spread when updating an object state.
Trigger: the class stores an Ignite UI component or DOM element instance as a plain field (public chart: IgrCategoryChart) and accesses it via this.chart.someMethod().
public chart: IgrCategoryChart;
constructor(props: any) {
super(props);
this.onChartRef = this.onChartRef.bind(this);
}
public onChartRef(chart: IgrCategoryChart) {
if (!chart) { return; }
this.chart = chart;
}
// in render():
<IgrCategoryChart ref={this.onChartRef} ... />
// later usage:
this.chart.replayTransitionIn();const chartRef = useRef<IgrCategoryChart>(null);
// in JSX:
<IgrCategoryChart ref={chartRef} ... />
// later usage:
chartRef.current?.replayTransitionIn();Rules
- Use
useRef(notuseState) because reading the ref never needs to trigger a re-render. - Pass the ref object directly as the
refprop — React sets.currentto the component instance on mount and back tonullon unmount automatically. - Access the element as
chartRef.current(may benull— use optional chaining?.). - Do not call
this.setState({})after assigning the ref — theuseRefequivalent never triggers re-renders. - Do not wrap in a
useCallbackcallback ref just to assignchartRef.current— that is redundant. Only use a callback ref (see Section 13) when the mounting event itself must trigger additional side effects such as starting an interval.
Trigger: a callback ref calls this.setState({}) purely to trigger a re-render, so that a newly-available component instance (e.g. a legend) can be passed as a prop to another component (legend={this.legend}).
private legend: IgrItemLegend;
private legendRef(r: IgrItemLegend) {
this.legend = r;
this.setState({}); // force re-render so legend becomes available as a prop
}
private chart: IgrDataPieChart;
private chartRef(r: IgrDataPieChart) {
this.chart = r;
this.setState({});
}
constructor(props: any) {
super(props);
this.legendRef = this.legendRef.bind(this);
this.chartRef = this.chartRef.bind(this);
}
// in render():
<IgrItemLegend ref={this.legendRef} ... />
<IgrDataPieChart ref={this.chartRef} legend={this.legend} ... />// Store the instance in STATE — the setter causes the re-render that delivers
// the instance as a prop on the next paint.
const [legend, setLegend] = useState<IgrItemLegend | null>(null);
// in JSX:
<IgrItemLegend
ref={(r: IgrItemLegend) => { if (r) setLegend(r); }}
orientation="Horizontal"
/>
<IgrDataPieChart
legend={legend ?? undefined} // absent until legend is set
...
/>Rules
- Use
useState, notuseRef, because the component instance must be available as a JSX prop — which requires a re-render to propagate. - The inline ref callback
(r) => { if (r) setSomething(r); }is intentional. Do not wrap it inuseCallback— the identity change is harmless for ref callbacks and avoids extra complexity. - Use
legend ?? undefinedto convertnull(initial state) toundefinedso the prop is simply absent until the legend mounts. - A second
useStatefor the chart instance is only needed if the chart ref is also used as a prop elsewhere. If only the legend needs wiring, only the legend needsuseState.
Trigger: two callback refs call each other's instance imperatively (this.chart.legend = this.legend) rather than passing the instance as a prop.
public chart: IgrFinancialChart;
public legend: IgrLegend;
public onChartRef(chart: IgrFinancialChart) {
if (!chart) { return; }
this.chart = chart;
if (this.legend) {
this.chart.legend = this.legend;
}
}
public onLegendRef(legend: IgrLegend) {
if (!legend) { return; }
this.legend = legend;
if (this.chart) {
this.chart.legend = this.legend;
}
}const chartRef = useRef<IgrFinancialChart>(null);
const legendRef = useRef<IgrLegend>(null);
// Runs after every render; wires the two together once both refs are populated.
// No dependency array — intentionally runs every render to catch late mounts.
useEffect(() => {
if (chartRef.current && legendRef.current) {
chartRef.current.legend = legendRef.current;
}
});
// in JSX:
<IgrLegend ref={legendRef} ... />
<IgrFinancialChart ref={chartRef} ... />Rules
- Omit the dependency array (
[]) fromuseEffectso it runs after every render. This guarantees the wiring fires regardless of which ref becomes available first. - Use
useRef(notuseState) because the assignment is imperative — nothing in JSX depends on the value, so no re-render is needed. - This pattern is preferred when the component's API requires imperative property assignment (i.e. the property is not a declarative JSX prop).
Trigger: a class has a private backing field (_foo: FooClass = null) and a public getter with a null-check that initialises on first access.
private _energyGlobalDemand: EnergyGlobalDemand = null;
public get energyGlobalDemand(): EnergyGlobalDemand {
if (this._energyGlobalDemand == null) {
this._energyGlobalDemand = new EnergyGlobalDemand();
}
return this._energyGlobalDemand;
}
// used as:
dataSource={this.energyGlobalDemand}// useMemo with [] runs the factory exactly once — equivalent to the lazy getter.
const energyGlobalDemand = useMemo(() => new EnergyGlobalDemand(), []);
// used as:
dataSource={energyGlobalDemand}Rules
- Always pass
[]as the dependency array — the data source should be created once and never recreated. useMemodoes not guarantee permanent memoization in strict React internals, but in practice with an empty deps array it is stable for the component's lifetime.- If the data source class takes constructor arguments that can change, list them in the deps array.
Trigger: the class has a lazy getter for a ComponentRenderer instance that registers description modules via context.
private _componentRenderer: ComponentRenderer = null;
public get renderer(): ComponentRenderer {
if (this._componentRenderer == null) {
this._componentRenderer = new ComponentRenderer();
var context = this._componentRenderer.context;
PropertyEditorPanelDescriptionModule.register(context);
DataPieChartDescriptionModule.register(context);
ItemLegendDescriptionModule.register(context);
}
return this._componentRenderer;
}
// used as:
componentRenderer={this.renderer}const renderer = useMemo(() => {
const r = new ComponentRenderer();
const ctx = r.context;
PropertyEditorPanelDescriptionModule.register(ctx);
DataPieChartDescriptionModule.register(ctx);
ItemLegendDescriptionModule.register(ctx);
return r;
}, []);
// used as:
componentRenderer={renderer}Rules
- Use
useMemo(() => ..., [])— identical reasoning to lazy getters in Section 8. - Use
const ctx = r.context(notvar context) to stay consistent with modern TypeScript style. - The list of
registercalls must be identical to the class version.
Trigger: the class has methods used as event callbacks, bound with .bind(this) in the constructor.
constructor(props: any) {
super(props);
this.onTransitionInModeChanged = this.onTransitionInModeChanged.bind(this);
this.onReloadChartClick = this.onReloadChartClick.bind(this);
}
public onTransitionInModeChanged(e: any) {
this.setState({ transitionInMode: e.target.value });
}
public onReloadChartClick() {
this.chart.replayTransitionIn();
}
// in render():
<select onChange={this.onTransitionInModeChanged}>...</select>
<button onClick={this.onReloadChartClick}>Reload Chart</button>const onTransitionInModeChanged = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
setTransitionInMode(e.target.value);
}, []);
const onReloadChartClick = useCallback(() => {
chartRef.current?.replayTransitionIn();
}, []);
// in JSX:
<select onChange={onTransitionInModeChanged}>...</select>
<button onClick={onReloadChartClick}>Reload Chart</button>Rules
- Always wrap event handlers in
useCallbackto keep handler identity stable across renders (prevents unnecessary child re-renders). - The dependency array
[]is correct when the handler only reads fromuseRefvalues oruseStatesetter functions (both are stable references). - If the handler reads a
useStatevalue (not the setter), add it to the deps array, or use the functional updater form of the setter (prev => ...) to avoid stale closures. this.is removed from all internal usages;this.chart.method()becomeschartRef.current?.method().
Trigger: the class has a componentDidMount method.
public componentDidMount() {
this.setupSomething();
}useEffect(() => {
setupSomething();
}, []); // [] = run once after first render, equivalent to componentDidMountRules
- The empty dependency array
[]is the exact functional equivalent ofcomponentDidMount. - If the setup relies on state or prop values that could change, list them in the array.
- Put the
useEffectnear the top of the component body, after alluseRef/useStatedeclarations, before thereturn.
Trigger: the class has a componentWillUnmount method that tears down timers, listeners, etc.
public componentWillUnmount() {
if (this.interval >= 0) {
window.clearInterval(this.interval);
this.interval = -1;
}
}useEffect(() => {
// setup ...
return () => {
// cleanup — runs on unmount (and before re-running on dep changes)
if (intervalRef.current >= 0) {
window.clearInterval(intervalRef.current);
intervalRef.current = -1;
}
};
}, [/* same deps as setup */]);Rules
- The cleanup function is always the return value of the same
useEffectthat performed the setup. This collocates setup and teardown, making it impossible for one to exist without the other. - The cleanup runs both on unmount and before the effect re-runs when dependencies change — this is more correct than
componentWillUnmountwhich only ran on unmount.
Trigger: the class stores an interval ID as a class field and manages it across multiple methods.
public interval: number = -1;
public chart: IgrCategoryChart;
public data: any[];
public dataIndex: number = 0;
public refreshMilliseconds: number = 10;
public onChartRef(chart: IgrCategoryChart) {
this.chart = chart;
this.setupInterval();
}
public setupInterval(): void {
if (this.interval >= 0) {
window.clearInterval(this.interval);
}
this.interval = window.setInterval(() => this.tick(), this.refreshMilliseconds);
}
public tick(): void {
// mutate this.data, call this.chart.notifyInsertItem(...)
}
public componentWillUnmount() {
window.clearInterval(this.interval);
}// Mutable values that must survive re-renders but not trigger them live in refs
const chartRef = useRef<IgrCategoryChart>(null);
const dataRef = useRef<any[]>(initialData);
const dataIndexRef = useRef<number>(0);
const refreshMsRef = useRef<number>(10);
const intervalRef = useRef<number>(-1);
const mountedRef = useRef<boolean>(true); // guards against post-unmount updates
const setupInterval = useCallback(() => {
if (intervalRef.current >= 0) {
window.clearInterval(intervalRef.current);
intervalRef.current = -1;
}
intervalRef.current = window.setInterval(() => {
if (!mountedRef.current) return; // component already unmounted — bail
setState(prev => {
// Read the latest state via `prev` to avoid stale closures.
// Mutate data refs and call imperative chart API here.
// Return `prev` unchanged if no state update is needed.
return prev;
});
}, refreshMsRef.current);
}, []);
// Wire chart ref → start interval; cleanup on unmount
const onChartRef = useCallback((chart: IgrCategoryChart) => {
if (!chart) return;
chartRef.current = chart;
setupInterval();
}, [setupInterval]);
useEffect(() => {
return () => {
mountedRef.current = false;
if (intervalRef.current >= 0) {
window.clearInterval(intervalRef.current);
intervalRef.current = -1;
}
};
}, []);
// Usage in JSX:
// <IgrCategoryChart ref={onChartRef} ... />Rules
- Never store the interval ID in
useState— this would trigger a re-render every time the interval is created or cleared. - Never read
useStatevalues directly inside the interval callback (stale closure). Instead: use the functional updatersetState(prev => ...)to read the latest state, or store the value in a ref (refreshMsRef.current). - Use
mountedRefto guard against callingsetStateon an unmounted component (React 18 no longer warns, but it can still cause logic errors). - When
refreshMillisecondschanges, callsetupInterval()again — the new interval replaces the old one.
Trigger: the class calls an async data-fetching method in the constructor (or componentDidMount) and stores the result in state.
constructor(props: any) {
super(props);
this.state = { data: [] };
this.initData();
}
public initData() {
SomeService.getData().then((result: any[]) => {
this.setState({ data: result });
});
}const [data, setData] = useState<any[]>([]);
useEffect(() => {
SomeService.getData().then((result: any[]) => {
setData(result);
});
}, []); // [] = fetch once on mountRules
- The
[]dependency array runs the fetch exactly once — equivalent to a constructor-time call. - If you need to cancel the fetch on unmount (e.g.
AbortController), return a cleanup function from the effect. - Do not call
initData()or equivalent from the constructor — functional components have no constructor.
Trigger: the class uses componentDidMount to attach addEventListener calls to DOM elements or refs (common with Ignite UI web components that emit igcInput, igcChange, etc.).
public componentDidMount() {
this.infoForm.addEventListener('igcInput', this.onInput);
this.addressForm.addEventListener('igcInput', this.onInput);
}
public componentWillUnmount() {
this.infoForm?.removeEventListener('igcInput', this.onInput);
this.addressForm?.removeEventListener('igcInput', this.onInput);
}const infoFormRef = useRef<HTMLFormElement>(null);
const addressFormRef = useRef<HTMLFormElement>(null);
const handleInput = useCallback(
() => checkActiveStepValidity(linear),
[linear, checkActiveStepValidity]
);
// In JSX – attach via React's onInput prop so no manual listener management is needed:
<form ref={infoFormRef} onInput={handleInput}>...</form>
<form ref={addressFormRef} onInput={handleInput}>...</form>Rules
- Prefer React's synthetic
onInput/onChangeprops on native<form>(or other container) elements instead of manually callingaddEventListenerinside auseEffect. Ignite UI web components fire standard DOMinput/changeevents that bubble up through the shadow DOM, so they are caught by the parent<form>'s React event handler automatically. - Define the handler as a
useCallbackat component scope with the relevant state values in its dependency array so it always closes over the latest values. - Only fall back to
useEffect+addEventListenerwhen the target element is not rendered by React (e.g. a third-party widget mounted outside the React tree) or when you need to attach to a non-standard event name that has no React equivalent.
Trigger: the class has an initData() or equivalent method that builds a static (never-changing) data array and stores it in this.state or a class field.
public data: any[];
constructor(props: any) {
super(props);
this.initData();
}
public initData() {
this.data = [
{ Year: '2009', Europe: 31, China: 21, USA: 19 },
{ Year: '2010', Europe: 43, China: 26, USA: 24 },
// ...
];
}
// in render():
dataSource={this.data}// Moved to module scope — created once per module load
const CHART_DATA = [
{ Year: '2009', Europe: 31, China: 21, USA: 19 },
{ Year: '2010', Europe: 43, China: 26, USA: 24 },
// ...
];
// in JSX:
dataSource={CHART_DATA}Rules
- Move static data outside the component function. Inside the function, it would be recreated on every render.
- Name constants with
SCREAMING_SNAKE_CASEto signal that they are module-level constants. - If the data array is built from props or state, keep it inside the component and use
useMemo.
Trigger: the class has instance methods that are pure (no this.* usage beyond what can be passed as arguments), used as tooltip renderers, formatters, or utility callbacks.
public createTooltip(series: IgrGeographicMapSeries, item: any): string {
return `<b>${item.name}</b><br/>Lat: ${item.lat}<br/>Lon: ${item.lon}`;
}
// used as:
tooltipTemplate={this.createTooltip}// Moved to module scope — no `this`, no closure over component state
function createTooltip(series: IgrGeographicMapSeries, item: any): string {
return `<b>${item.name}</b><br/>Lat: ${item.lat}<br/>Lon: ${item.lon}`;
}
// used as (reference is stable, no binding needed):
tooltipTemplate={createTooltip}Rules
- If the function does not read any state, props, or refs — move it to module scope.
- If it does read component state, wrap it in
useCallbackinstead. - Module-level functions have stable identity (never change reference), so passing them as event handler props is efficient.
Trigger: the class has public methods used as Ignite UI IgrPivotAggregator implementations.
public weightedAvg(
members: any[],
data: any[],
allData: any[],
fieldName: string,
pivotDimension: IgrPivotDimension
): any {
// aggregate logic
}
// used as:
aggregatorName: IgrPivotNumericAggregate.sum.name,
// or wired via configuration object referencing `this.weightedAvg`// Promoted to module scope (pure functions — no `this`)
function weightedAvg(
members: any[],
data: any[],
allData: any[],
fieldName: string,
pivotDimension: IgrPivotDimension
): any {
// identical aggregate logic
}
// used exactly the same way in the configuration objectRules
- Identical to Section 17 — move to module scope.
- The function signature must remain identical; do not rename parameters.
Trigger: the class calls registerIconFromText(...) or similar one-time registrations inside the constructor.
constructor(props: any) {
super(props);
registerIconFromText('add', ADD_SVG, 'material');
registerIconFromText('delete', DELETE_SVG, 'material');
}// Moved to module scope — runs once when the module is first imported
registerIconFromText('add', ADD_SVG, 'material');
registerIconFromText('delete', DELETE_SVG, 'material');
export default function Sample() {
// no registration here
}Rules
- Moving registrations to module scope means they run exactly once per module load, not once per component instantiation — which is the correct semantic.
- Do not put them inside
useEffectunless the registration genuinely depends on the DOM (rare). Module-scope execution is earlier and simpler.
When to use: any component that has async operations (fetch, setInterval, setTimeout) that call state setters. A component may unmount before the async operation completes, causing a "cannot update state on an unmounted component" style issue.
const mountedRef = useRef(true);
useEffect(() => {
return () => {
mountedRef.current = false;
};
}, []);
// Inside async callbacks or interval ticks:
if (!mountedRef.current) return;
setState(...);Rules
- Always declare
mountedRefimmediately before the firstuseEffectthat starts async work. - Check
mountedRef.currentat the top of every async callback before calling any state setter. - This ref never needs to be in a dependency array — it is intentionally mutable outside React's tracking.
Use this table to decide which hooks to import:
| Situation | Hook |
|---|---|
Component has this.state = {...} / this.setState(...) |
useState |
Component stores a component/DOM ref on this (accessed imperatively) |
useRef |
Component stores mutable non-render values on this (interval ID, counter, array) |
useRef |
Component uses a mountedRef guard |
useRef |
| Component has a lazy getter with backing field | useMemo |
Component has a ComponentRenderer lazy getter |
useMemo |
Component has static data inside initData() used as a prop |
module-level const (no hook needed) |
Component has componentDidMount or componentWillUnmount |
useEffect |
Component manages a setInterval / setTimeout |
useEffect + useRef |
| Component loads async data | useEffect |
| Component attaches native event listeners | useEffect |
| Component needs cross-component wiring via a prop | useState |
| Component needs cross-component wiring via imperative assignment | useRef + useEffect |
| Component has event handler methods bound in constructor | useCallback |
This is the full template to start from. Delete sections that do not apply.
import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
// ── Ignite UI imports (same as class original) ────────────────────────────────
import { IgrSomeComponentModule } from 'igniteui-react-charts';
import { IgrSomeComponent } from 'igniteui-react-charts';
import { IgrItemLegend, IgrItemLegendModule } from 'igniteui-react-charts';
import {
ComponentRenderer,
SomeDescriptionModule,
} from 'igniteui-react-core';
import { SomeDataSource } from './SomeDataSource';
// ── Module registration (unchanged from class original) ───────────────────────
const mods: any[] = [IgrSomeComponentModule, IgrItemLegendModule];
mods.forEach(m => m.register());
// ── Module-level constants (for static data, moved from initData()) ───────────
const STATIC_DATA = [
{ label: 'A', value: 30 },
{ label: 'B', value: 70 },
];
// ── Module-level pure functions (moved from class instance methods) ───────────
function createTooltip(series: any, item: any): string {
return `<b>${item.label}</b>: ${item.value}`;
}
// ── One-time side effects (moved from constructor) ────────────────────────────
// registerIconFromText('icon-name', SVG_STRING, 'material');
export default function Sample() {
// ── State (replaces this.state = {...} in constructor) ────────────────────
const [transitionMode, setTransitionMode] = useState('Auto');
// Use for cross-component wiring via prop:
const [legend, setLegend] = useState<IgrItemLegend | null>(null);
// ── Refs for components/DOM accessed imperatively ─────────────────────────
const componentRef = useRef<IgrSomeComponent>(null);
// Use for mutable values that must not trigger re-renders:
const intervalRef = useRef<number>(-1);
const dataRef = useRef<any[]>([]);
// Unmount guard for async operations:
const mountedRef = useRef<boolean>(true);
// ── useMemo for lazy data sources and ComponentRenderer ───────────────────
const dataSource = useMemo(() => new SomeDataSource(), []);
const renderer = useMemo(() => {
const r = new ComponentRenderer();
SomeDescriptionModule.register(r.context);
return r;
}, []);
// ── useEffect: componentDidMount (one-time setup) ─────────────────────────
useEffect(() => {
// async data fetch:
// SomeService.getData().then(result => { if (mountedRef.current) setData(result); });
// native event listeners:
// const el = domRef.current;
// el?.addEventListener('igcInput', handleInput);
// cleanup (componentWillUnmount + listener removal):
return () => {
mountedRef.current = false;
if (intervalRef.current >= 0) {
window.clearInterval(intervalRef.current);
}
// el?.removeEventListener('igcInput', handleInput);
};
}, []); // [] = run once
// ── useEffect: cross-component imperative wiring ──────────────────────────
// (omit dependency array so it runs after every render)
useEffect(() => {
if (componentRef.current && legend) {
(componentRef.current as any).legend = legend;
}
});
// ── useCallback: event handlers (replaces bound methods) ─────────────────
const onTransitionModeChanged = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
setTransitionMode(e.target.value);
},
[]
);
const onReplayClick = useCallback(() => {
componentRef.current?.replayTransitionIn();
}, []);
// ── Render (replaces public render(): JSX.Element) ────────────────────────
return (
<div className="container sample">
{/* Cross-component wiring via prop (Section 6) */}
<IgrItemLegend
ref={(r: IgrItemLegend) => { if (r) setLegend(r); }}
orientation="Horizontal"
/>
{/* Standard ref (Section 5) */}
<IgrSomeComponent
ref={componentRef}
dataSource={dataSource}
legend={legend ?? undefined}
componentRenderer={renderer}
/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Sample/>);| Class | Functional |
|---|---|
extends React.Component<any, any> |
function Sample() {} |
constructor(props) { super(props); } |
(deleted) |
this.handler = this.handler.bind(this) |
(deleted — use useCallback) |
this.state = { k: v } |
const [k, setK] = useState(v) |
this.setState({ k: newV }) |
setK(newV) |
this.setState(prev => ...) |
setK(prev => ...) |
public field: T (stored component instance — used as prop) |
const [field, setField] = useState<T | null>(null) |
public field: T (stored ref — accessed imperatively) |
const fieldRef = useRef<T>(null) |
public counter: number = 0 (mutable, no render) |
const counterRef = useRef<number>(0) |
get lazy() { if (!_f) _f = new F(); return _f; } |
const lazy = useMemo(() => new F(), []) |
get renderer() { ... register modules ... } |
const renderer = useMemo(() => { ...; return r; }, []) |
componentDidMount() |
useEffect(() => { ... }, []) |
componentWillUnmount() |
useEffect(() => { return () => { cleanup }; }, []) |
this.handler = (e) => { ... } (class field arrow) |
const handler = useCallback((e) => { ... }, [deps]) |
public method(args) { /* pure */ } |
Module-level function function method(args) {} |
initData() { this.data = [...] } |
Module-level const DATA = [...] |
registerIconFromText(...) in constructor |
Module scope before component |
this.chart.someMethod() |
chartRef.current?.someMethod() |
legend={this.legend} (may be undefined) |
legend={legend ?? undefined} |
ref={this.onChartRef} (bound callback, only assigns field) |
ref={chartRef} (useRef object — React handles assignment automatically) |
ref={this.onChartRef} (bound callback that also triggers side effects) |
callback ref via useCallback (see Section 13) |