Skip to main content

Custom Component Advanced Usage

This document explains the advanced features and patterns for custom components.

Complex State Management​

For complex components, you may need to manage multiple states:

import { ReactElement } from 'react';
import { CustomComponentRenderProps, useComponentState, useLogger } from '@hops/custom-component';

function ComplexComponent(props: CustomComponentRenderProps<OwnProps>): ReactElement {
// Managing multiple states
const [value, setValue] = useComponentState<string>('value', props.defaultValue);
const [isValid, setIsValid] = useComponentState<boolean>('isValid', true);
const [errors, setErrors] = useComponentState<string[]>('errors', []);
const [history, setHistory] = useComponentState<string[]>('history', []);

// Local state (not accessible externally)
const [localState, setLocalState] = useState<string>('');

// State update logic
const updateValue = (newValue: string) => {
setValue(newValue);

// Validation
const validationErrors = validate(newValue);
setIsValid(validationErrors.length === 0);
setErrors(validationErrors);

// Update history
setHistory((prev) => [...prev, newValue].slice(-5)); // Keep only the 5 most recent items
};

// Component UI rendering...
}

Integration with Workflows​

The state of custom components can be used in workflows:

  1. State managed with useComponentState can be used as input for workflows
  2. Workflow outputs can be passed as component properties

For example, a data entry form component can be used as follows:

import { memo, ReactElement } from 'react';
import { CustomComponentRenderProps, useComponentState, useLogger } from '@hops/custom-component';
import { FormControl, Button, Typography } from '@hops/design-system';

interface DataEntryFormOwnProps {
workflowResult: {
message?: string;
};
}

function DataEntryForm(props: CustomComponentRenderProps<DataEntryFormOwnProps>): ReactElement {
// Form data can be used in workflows for save operations
const [formData, setFormData] = useComponentState<Record<string, any>>('formData', {});

// props.workflowResult can be used to display workflow results

return (
<FormControl>
{/* Form fields */}
<Button onClick={() => setFormData(/* Form data */)}>Save</Button>

{/* Display workflow results */}
{props.workflowResult && <Typography>{props.workflowResult.message}</Typography>}
</FormControl>
);
}

export default memo(DataEntryForm);

Internal Event Handling​

For internal event handling, you can implement it as follows:

import { memo, ReactElement } from 'react';
import { CustomComponentRenderProps, useComponentState } from '@hops/custom-component';
import { FormControl, Button, Input, FormLabel, Typography } from '@hops/design-system';

interface DataEntryFormOwnProps {
initialValue?: string;
}

function DataEntryForm(props: CustomComponentRenderProps<DataEntryFormOwnProps>): ReactElement {
const [formValue, setFormValue] = useComponentState<string>('formValue', props.initialValue || '');
const [submitted, setSubmitted] = useComponentState<boolean>('submitted', false);

// Handle input change event
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormValue(e.target.value);
setSubmitted(false);
};

// handle submit event
const handleSubmit = () => {
if (formValue.trim()) {
setSubmitted(true);
}
};

return (
<div>
<FormControl>
<FormLabel>Data Entry</FormLabel>
<Input value={formValue} onChange={handleInputChange} placeholder="Enter something..." />
</FormControl>

<Button onClick={handleSubmit}>Save</Button>

{submitted && <Typography>Saved Value: {formValue}</Typography>}
</div>
);
}

export default memo(DataEntryForm);

Performance Optimization​

Use React's commonly used optimization techniques.

Memoization​

Wrap all possible components with memo to prevent unnecessary re-rendering:

export default memo(MyComponent);

useMemo and useCallback​

Optimize expensive operations or functions using useMemo and useCallback:

// Memoize expensive calculations
const processedData = useMemo(() => {
return expensiveDataProcessing(props.data);
}, [props.data]);

// Memoize event handlers
const handleChange = useCallback(
(value: string) => {
setValue(value);
validate(value);
},
[setValue, validate],
);
tip

When writing complex custom components, it's good to separate them into smaller sub-components. This makes code management and debugging easier.