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.