Star Rating
import { HotTable, HotColumn, EditorComponent } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';import StarRatingComponent from 'react-star-rating-component';
registerAllModules();
/* start:skip-in-preview */export const data = [ { product: 'Dashboard Pro', category: 'Analytics', rating: 5, reviews: 342, price: 49 }, { product: 'Form Builder', category: 'Tools', rating: 4, reviews: 218, price: 29 }, { product: 'Chart Engine', category: 'Analytics', rating: 3, reviews: 156, price: 39 }, { product: 'Auth Module', category: 'Security', rating: 5, reviews: 89, price: 19 }, { product: 'File Manager', category: 'Storage', rating: 2, reviews: 64, price: 15 }, { product: 'Email Service', category: 'Communication', rating: 4, reviews: 275, price: 25 }, { product: 'Search Index', category: 'Tools', rating: 1, reviews: 31, price: 35 }, { product: 'Cache Layer', category: 'Infra', rating: 4, reviews: 112, price: 20 },];/* end:skip-in-preview */
const RatingCellRenderer = ({ value }) => ( <div className="rating-cell"> <StarRatingComponent name="rating-cell" value={Number(value) || 0} editing={false} /> </div>);
const ratingValidator = (value, callback) => { value = parseInt(value); callback(value >= 0 && value <= 100);};
export const RatingEditor = () => { return ( <EditorComponent> {({ value, setValue, finishEditing }) => ( <div className="rating-editor"> <StarRatingComponent name="rating" value={Number(value) || 0} onStarHover={(nextValue) => setValue(nextValue)} onStarClick={(nextValue) => { setValue(nextValue); finishEditing(); }} /> </div> )} </EditorComponent> );};
const ExampleComponent = () => { return ( <HotTable data={data} colHeaders={['Product', 'Category', 'Rating', 'Reviews', 'Price']} autoRowSize={true} rowHeaders={true} height="auto" width="100%" autoWrapRow={true} headerClassName="htLeft" licenseKey="non-commercial-and-evaluation" > <HotColumn data="product" type="text" width={200} /> <HotColumn data="category" type="text" width={120} /> <HotColumn data="rating" width={150} editor={RatingEditor} renderer={RatingCellRenderer} validator={ratingValidator} /> <HotColumn data="reviews" type="numeric" width={80} /> <HotColumn data="price" type="numeric" width={80} /> </HotTable> );};
export default ExampleComponent;import { HotTable, HotColumn, EditorComponent } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';import StarRatingComponent from 'react-star-rating-component';
registerAllModules();
/* start:skip-in-preview */
export const data = [ { product: "Dashboard Pro", category: "Analytics", rating: 5, reviews: 342, price: 49 }, { product: "Form Builder", category: "Tools", rating: 4, reviews: 218, price: 29 }, { product: "Chart Engine", category: "Analytics", rating: 3, reviews: 156, price: 39 }, { product: "Auth Module", category: "Security", rating: 5, reviews: 89, price: 19 }, { product: "File Manager", category: "Storage", rating: 2, reviews: 64, price: 15 }, { product: "Email Service", category: "Communication", rating: 4, reviews: 275, price: 25 }, { product: "Search Index", category: "Tools", rating: 1, reviews: 31, price: 35 }, { product: "Cache Layer", category: "Infra", rating: 4, reviews: 112, price: 20 },];
/* end:skip-in-preview */
const RatingCellRenderer = ({ value }: { value: unknown }) => ( <div className="rating-cell"> <StarRatingComponent name="rating-cell" value={Number(value) || 0} editing={false} /> </div>);
const ratingValidator = (value: string | number, callback: (valid: boolean) => void) => { const parsed = parseInt(String(value)); callback(parsed >= 0 && parsed <= 100);};
export const RatingEditor = () => { return ( <EditorComponent<number>> {({ value, setValue, finishEditing }) => ( <div className="rating-editor"> <StarRatingComponent name="rating" value={Number(value) || 0} onStarHover={(nextValue: number) => setValue(nextValue)} onStarClick={(nextValue: number) => { setValue(nextValue); finishEditing(); }} /> </div> )} </EditorComponent> );};
const ExampleComponent = () => { return ( <HotTable data={data} colHeaders={["Product", "Category", "Rating", "Reviews", "Price"]} autoRowSize={true} rowHeaders={true} height="auto" width="100%" autoWrapRow={true} headerClassName="htLeft" licenseKey="non-commercial-and-evaluation" > <HotColumn data="product" type="text" width={240} /> <HotColumn data="category" type="text" width={120} /> <HotColumn data="rating" width={150} editor={RatingEditor} renderer={RatingCellRenderer} validator={ratingValidator} /> <HotColumn data="reviews" type="numeric" width={80} /> <HotColumn data="price" type="numeric" width={80} /> </HotTable> );};
export default ExampleComponent;.rating-cell { display: flex; align-items: center; margin: 3px 0 0 -1px;}
.rating-editor { display: flex; align-items: center; height: 100%; box-sizing: border-box !important; border: none; border-radius: 0; box-shadow: inset 0 0 0 var(--ht-cell-editor-border-width, 2px) var(--ht-cell-editor-border-color, #1a42e8), 0 0 var(--ht-cell-editor-shadow-blur-radius, 0) 0 var(--ht-cell-editor-shadow-color, transparent); background-color: var(--ht-cell-editor-background-color, #ffffff); padding: var(--ht-cell-vertical-padding, 4px) var(--ht-cell-horizontal-padding, 8px); font-family: var(--ht-font-family, inherit); font-size: var(--ht-font-size, 14px); line-height: var(--ht-line-height, 1.5);}Overview
This guide shows how to create a star rating editor cell using react-star-rating-component with React’s EditorComponent. Perfect for product reviews, feedback forms, or any scenario where users need to select a numeric rating (e.g., 1–5 stars).
Difficulty: Beginner Time: ~15 minutes Libraries: react-star-rating-component
What You’ll Build
A cell that:
- Displays interactive star rating when editing
- Shows stars in view mode via a custom React renderer
- Supports hover preview before selection
- Stores values as numbers (1–5)
- Validates rating range (e.g., 0–100)
- Provides click-to-select functionality
- Works with React’s component-based architecture
Prerequisites
npm install @handsontable/react-wrapper react-star-rating-componentWhat you need:
- React 16.8+ (hooks support)
@handsontable/react-wrapperpackagereact-star-rating-componentpackage for the star rating UI- Basic React knowledge (hooks, JSX)
Import Dependencies
import { HotTable, HotColumn, EditorComponent } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';import StarRatingComponent from 'react-star-rating-component';registerAllModules();What we’re importing:
EditorComponent- React component for creating custom editorsHotTableandHotColumn- React wrapper componentsStarRatingComponent- Star rating UI fromreact-star-rating-componentregisterAllModules()- Registers Handsontable modules (required when using the wrapper)
Create the Editor Component
Create a React component that uses
EditorComponentwith the render prop pattern.export const RatingEditor = () => {return (<EditorComponent<number>>{({ value, setValue, finishEditing }) => (<div className="rating-editor"><StarRatingComponentname="rating"value={Number(value) || 0}onStarHover={(nextValue: number) => setValue(nextValue)}onStarClick={(nextValue: number) => {setValue(nextValue);finishEditing();}}/></div>)}</EditorComponent>);};What’s happening:
EditorComponentwraps your editor UI; thechildrenprop is a function that receives editor state.value- Current cell value (numeric rating)setValue- Function to update the valuefinishEditing- Function to save and close the editoronStarHover- Updates preview as user hovers over starsonStarClick- Saves the selected rating and closes the editor- The
rating-editordiv is inside the render prop so styling applies to the visible editor area.
Key concepts:
- Render prop pattern:
EditorComponentuses a function as children - Hover preview:
onStarHoverlets users preview before committing - Click to confirm:
onStarClicksaves and closes the editor
Add a Custom Renderer for View Mode
Use a React component as the cell renderer so stars are shown when not editing.
const RatingCellRenderer = ({ value }: { value: unknown }) => (<div className="rating-cell"><StarRatingComponentname="rating-cell"value={Number(value) || 0}editing={false}/></div>);What’s happening:
- The renderer receives
valueand displays it withStarRatingComponent editing={false}keeps the stars non-interactive in view mode- Use a unique
name(e.g."rating-cell") to avoid conflicts with the editor instance
- The renderer receives
Add a Validator (Optional)
Validate that the rating is within an allowed range (e.g., 0–100):
const ratingValidator = (value: string | number, callback: (valid: boolean) => void) => {const parsed = parseInt(String(value));callback(parsed >= 0 && parsed <= 100);};For a strict 1–5 star scale, use
parsed >= 1 && parsed <= 5instead.Add Styling
Style the cell and editor so the star rating fits and matches the grid.
.rating-cell {display: flex;align-items: center;margin: 3px 0 0 -1px;}.rating-editor {display: flex;align-items: center;height: 100%;box-sizing: border-box !important;border: none;border-radius: 0;box-shadow: inset 0 0 0 var(--ht-cell-editor-border-width, 2px)var(--ht-cell-editor-border-color, #1a42e8),0 0 var(--ht-cell-editor-shadow-blur-radius, 0) 0var(--ht-cell-editor-shadow-color, transparent);background-color: var(--ht-cell-editor-background-color, #ffffff);padding: var(--ht-cell-vertical-padding, 4px)var(--ht-cell-horizontal-padding, 8px);font-family: var(--ht-font-family, inherit);font-size: var(--ht-font-size, 14px);line-height: var(--ht-line-height, 1.5);}What’s happening:
.rating-cellaligns the stars in the cell when not editing.rating-editoruses Handsontable CSS variables for focus border, background, and padding so the editor matches the grid theme
Prepare Sample Data
Use data with a
ratingproperty (and any other columns you need). Example for a product table:export const data = [{ product: "Dashboard Pro", category: "Analytics", rating: 5, reviews: 342, price: 49 },{ product: "Form Builder", category: "Tools", rating: 4, reviews: 218, price: 29 },{ product: "Chart Engine", category: "Analytics", rating: 3, reviews: 156, price: 39 },{ product: "Auth Module", category: "Security", rating: 5, reviews: 89, price: 19 },{ product: "File Manager", category: "Storage", rating: 2, reviews: 64, price: 15 },{ product: "Email Service", category: "Communication", rating: 4, reviews: 275, price: 25 },{ product: "Search Index", category: "Tools", rating: 1, reviews: 31, price: 35 },{ product: "Cache Layer", category: "Infra", rating: 4, reviews: 112, price: 20 },];What’s happening:
- Each row has
product,category,rating,reviews, andprice - The
ratingcolumn uses the star editor and renderer; other columns can be text or numeric
- Each row has
Use in Handsontable
Wire the editor, renderer, and validator to the rating column:
const ExampleComponent = () => {return (<HotTabledata={data}colHeaders={["Product", "Category", "Rating", "Reviews", "Price"]}autoRowSize={true}rowHeaders={true}height="auto"width="100%"autoWrapRow={true}headerClassName="htLeft"licenseKey="non-commercial-and-evaluation"><HotColumn data="product" type="text" width={240} /><HotColumn data="category" type="text" width={120} /><HotColumndata="rating"width={150}editor={RatingEditor}renderer={RatingCellRenderer}validator={ratingValidator}/><HotColumn data="reviews" type="numeric" width={80} /><HotColumn data="price" type="numeric" width={80} /></HotTable>);};What’s happening:
editor={RatingEditor}- Star rating editor when the cell is activerenderer={RatingCellRenderer}- Shows stars in view modevalidator={ratingValidator}- Ensures rating is within the allowed range (e.g., 0–100)data="rating"- Binds to theratingproperty in each row
Key features:
- Stars in both view and edit mode
- Values stored as numbers (1–5)
- Validation and type-safe setup with TypeScript
How It Works - Complete Flow
- Initial Render:
RatingCellRendererdisplays stars for the current rating (e.g., 3 filled stars). - User Double-Clicks or Enter: Editor opens.
- Editor Opens:
EditorComponentshows the star picker in the cell. - Star Rating Display: Stars show current value; empty stars show remaining.
- User Interaction:
- Hover over stars →
onStarHoverupdates preview viasetValue - Click a star →
onStarClicksaves value and callsfinishEditing()
- Hover over stars →
- Validation:
ratingValidatorruns (e.g., value must be 0–100). - Save: Numeric value is saved to the cell.
- Editor Closes:
RatingCellRenderershows the updated stars in view mode.
Enhancements
Custom Star Count
Change the number of stars (e.g., 10-point scale):
<StarRatingComponentname="rating"starCount={10}value={Number(value) || 0}onStarHover={(nextValue) => setValue(nextValue)}onStarClick={(nextValue) => {setValue(nextValue);finishEditing();}}/>Custom Star Colors
Customize the appearance:
<StarRatingComponentname="rating"value={Number(value) || 0}starColor="#ffd700"emptyStarColor="#e0e0e0"onStarHover={(nextValue) => setValue(nextValue)}onStarClick={(nextValue) => {setValue(nextValue);finishEditing();}}/>Alternative: HTML-Based Renderer
The main example uses a React component (
RatingCellRenderer) for view mode. If you prefer a non-React renderer, you can userendererFactory:import { rendererFactory } from 'handsontable/renderers';const starRenderer = rendererFactory(({ td, value }) => {const rating = Number(value) || 0;const stars = '★'.repeat(rating) + '☆'.repeat(5 - rating);td.innerHTML = `<div style="font-size: 1.2em;color: #ffb400;letter-spacing: 2px;">${stars}</div>`;});// Use in HotColumn<HotColumndata="rating"width={150}editor={RatingEditor}renderer={starRenderer}/>Read Config from Cell Properties (Advanced)
Use
onPreparefor per-column configuration (e.g., star count):const RatingEditor = () => {const [starCount, setStarCount] = useState(5);const onPrepare = (_row, _column, _prop, _TD, _originalValue, cellProperties) => {if (cellProperties.starCount != null) {setStarCount(cellProperties.starCount);}};return (<div className="rating-editor"><EditorComponent<number> onPrepare={onPrepare}>{({ value, setValue, finishEditing }) => (<StarRatingComponentname="rating"starCount={starCount}value={Number(value) || 0}onStarHover={(nextValue) => setValue(nextValue)}onStarClick={(nextValue) => {setValue(nextValue);finishEditing();}}/>)}</EditorComponent></div>);};// Use with different star counts per column<HotColumn editor={RatingEditor} starCount={5} data="rating" title="Rating (1-5)" /><HotColumn editor={RatingEditor} starCount={10} data="score" title="Score (1-10)" />Handle Empty Values
Ensure the component handles undefined or null:
value={Number(value) || 0}This displays empty stars when the cell has no value.
Accessibility
The StarRatingComponent uses radio inputs. Enhance with ARIA:
<StarRatingComponent name="rating" value={Number(value) || 0} onStarHover={(nextValue) => setValue(nextValue)} onStarClick={(nextValue) => { setValue(nextValue); finishEditing(); }} aria-label="Select rating"/>Keyboard navigation:
- Tab: Navigate to editor
- Arrow keys: Navigate between stars (if supported by library)
- Enter/Space: Select star
- Escape: Cancel editing
Performance Considerations
Why This Is Fast
- Lightweight library: react-star-rating-component is small and focused
- React Virtual DOM: Efficient updates only when value changes
- Simple callbacks:
onStarHoverandonStarClickare straightforward - No unnecessary re-renders: Editor unmounts when closed
TypeScript Support
EditorComponent is fully typed. Specify the value type for numeric ratings:
<EditorComponent<number>> {({ value, setValue, finishEditing }) => { // TypeScript knows value is number | undefined // TypeScript knows setValue accepts number return ( <StarRatingComponent name="rating" value={Number(value) || 0} onStarHover={(nextValue: number) => setValue(nextValue)} onStarClick={(nextValue: number) => { setValue(nextValue); finishEditing(); }} /> ); }}</EditorComponent>Best Practices
- Coerce value to number - Use
Number(value) || 0since cell values may be strings. - Provide
nameprop - Required byreact-star-rating-componentfor radio inputs; use different names for editor and renderer (e.g."rating"and"rating-cell") to avoid conflicts. - Call
finishEditing()on click - Star click confirms the selection and closes the editor. - Use
onStarHoverfor preview - Improves UX by showing the selection before commit. - Use a custom renderer -
RatingCellRendererwithediting={false}shows stars in view mode and keeps the UI consistent. - Add a validator - Use
ratingValidatorto restrict values (e.g., 0–100 or 1–5) and give immediate feedback.
Congratulations! You’ve created a star rating editor using React’s EditorComponent and react-star-rating-component, perfect for rating selection in your data grid!