Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving React Developer Experience #10831

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0d7f8d2
React wrapper: Drop settings prop from HotTable (#10785)
NOtherDev Feb 19, 2024
faf0c9f
React wrapper: Wrap internal utilities into a context provided from H…
NOtherDev Feb 19, 2024
c7c7b1f
Merge branch 'develop' into feature/improved-react
evanSe Feb 20, 2024
b0cc886
React wrapper: Change the way renderer might be passed to HotColumn a…
NOtherDev Feb 21, 2024
0377646
React wrapper: Change the way editor might be passed to HotColumn and…
NOtherDev Feb 21, 2024
05880af
React wrapper: Rewrite HotTable & HotColumn into functional component…
NOtherDev Feb 22, 2024
b92a133
React wrapper: Add a check that only HotColumn instances are children…
NOtherDev Feb 22, 2024
005d287
Remove unnecessary and no longer valid test elements (#10806)
NOtherDev Feb 22, 2024
df1a480
React wrapper: Enable TypeScript strict mode & ensure proper strong t…
NOtherDev Feb 26, 2024
59d149c
Merge branch 'develop' into feature/improved-react
NOtherDev Feb 28, 2024
1078e04
Fix for react-redux.md docs compilation (#10832)
NOtherDev Feb 28, 2024
2973720
Merge branch 'develop' into feature/improved-react
NOtherDev Feb 28, 2024
d093c11
Merge branch 'develop' into feature/improved-react
evanSe Mar 7, 2024
49cdb35
React wrapper: Simplify useHotEditor API (#10845)
NOtherDev Mar 11, 2024
cf4112c
React wrapper: Sync updated docs with new structure (#10848)
NOtherDev Mar 11, 2024
5a535ed
Merge remote-tracking branch 'upstream/develop' into feature/improved…
NOtherDev Mar 11, 2024
8819a96
Merge branch 'develop' into feature/improved-react
evanSe Apr 16, 2024
442dd51
update to new wrapper
evanSe Apr 16, 2024
6565309
Merge branch 'develop' into feature/improved-react
evanSe Apr 29, 2024
a7e857a
Merge branch 'develop' into feature/improved-react
evanSe Apr 29, 2024
f88a10f
fix types package
evanSe Apr 29, 2024
7932225
Merge branch 'develop' into feature/improved-react
evanSe Jun 19, 2024
c4479f3
Merge branch 'develop' into feature/improved-react
evanSe Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changelogs/10789.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "React wrapper: Wrap internal utilities into a context provided from HotTable to its children",
"type": "changed",
"issueOrPR": 10789,
"breaking": false,
"framework": "react"
}
8 changes: 8 additions & 0 deletions .changelogs/10791.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "Change the way renderer might be passed to HotColumn and HotTable to use render props approach",
"type": "changed",
"issueOrPR": 10791,
"breaking": true,
"framework": "react"
}
8 changes: 8 additions & 0 deletions .changelogs/10793.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "Change the way editor might be passed to HotColumn and HotTable to use render props approach",
"type": "changed",
"issueOrPR": 10793,
"breaking": true,
"framework": "react"
}
8 changes: 8 additions & 0 deletions .changelogs/10799.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "Rewrite HotTable & HotColumn into functional components",
"type": "changed",
"issueOrPR": 10799,
"breaking": true,
"framework": "react"
}
8 changes: 8 additions & 0 deletions .changelogs/10805.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "Add a check that only HotColumn instances are children of HotTable",
"type": "added",
"issueOrPR": 10805,
"breaking": false,
"framework": "react"
}
8 changes: 8 additions & 0 deletions .changelogs/10813.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "Enable TypeScript strict mode & ensure proper strong typing everywhere",
"type": "changed",
"issueOrPR": 10813,
"breaking": false,
"framework": "react"
}
8 changes: 8 additions & 0 deletions .changelogs/11593.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"issuesOrigin": "private",
"title": "React wrapper: Drop settings prop from HotTable",
"type": "removed",
"issueOrPR": 11593,
"breaking": true,
"framework": "react"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This tutorial will give you a comprehensive understanding of how the whole proce

## Component-based editors

You can use React components to create custom editors. To do so, you'll need to create a component compatible with Handsontable's editor class structure. The easiest way to do so is to extend `BaseEditorComponent` - a base editor component exported from `@handsontable/react`.
You can use React components to create custom editors. To do so, you'll need to create a component that provides a set of Handsontable's editor class functions that you need to override with your custom behaviors using `useHotEditor` hook that is exported from `@handsontable/react`.

This will give you a solid base to build on. Note that the editor component needs to tick all of the boxes that a regular editor does, such as defining the `getValue`, `setValue`, `open`, `close`, and `focus` methods, which are abstract in the `BaseEditor`. For more info, check the section on [creating custom editors from scratch](#how-to-create-a-custom-editor).

Expand Down
187 changes: 78 additions & 109 deletions docs/content/guides/cell-functions/cell-editor/react/example1.jsx
Original file line number Diff line number Diff line change
@@ -1,115 +1,86 @@
import React from 'react';
import { HotTable, HotColumn, BaseEditorComponent } from '@handsontable/react';
import { HotTable, HotColumn, useHotEditor } from '@handsontable/react';
import 'handsontable/dist/handsontable.full.min.css';

// an editor component
class EditorComponent extends BaseEditorComponent {
constructor(props) {
super(props);

this.mainElementRef = React.createRef();
this.containerStyle = {
display: 'none',
position: 'absolute',
left: 0,
top: 0,
background: '#fff',
border: '1px solid #000',
padding: '15px',
zIndex: 999
};
this.state = {
value: ''
};
}

setValue(value, callback) {
this.setState((state, props) => {
return { value: value };
}, callback);
}

getValue() {
return this.state.value;
}

open() {
this.mainElementRef.current.style.display = 'block';
}

close() {
this.mainElementRef.current.style.display = 'none';
}

prepare(row, col, prop, td, originalValue, cellProperties) {
// We'll need to call the `prepare` method from
// the `BaseEditorComponent` class, as it provides
// the component with the information needed to use the editor
// (hotInstance, row, col, prop, TD, originalValue, cellProperties)
super.prepare(row, col, prop, td, originalValue, cellProperties);

const tdPosition = td.getBoundingClientRect();

// As the `prepare` method is triggered after selecting
// any cell, we're updating the styles for the editor element,
// so it shows up in the correct position.
this.mainElementRef.current.style.left = tdPosition.left + window.pageXOffset + 'px';
this.mainElementRef.current.style.top = tdPosition.top + window.pageYOffset + 'px';
}

setLowerCase() {
this.setState(
(state, props) => {
return { value: this.state.value.toString().toLowerCase() };
},
() => {
this.finishEditing();
}
);
}

setUpperCase() {
this.setState(
(state, props) => {
return { value: this.state.value.toString().toUpperCase() };
},
() => {
this.finishEditing();
}
);
}

stopMousedownPropagation(e) {
const EditorComponent = (props) => {
const mainElementRef = React.useRef();
const containerStyle = {
display: 'none',
position: 'absolute',
left: 0,
top: 0,
background: '#fff',
border: '1px solid #000',
padding: '15px',
zIndex: 999
};

// A hook that takes a set of methods that your custom editor needs to override.
// It also provides partial editor API in case other functions need to access it, like `finishEditing`.
const { value, setValue, finishEditing } = useHotEditor({
onOpen() {
mainElementRef.current.style.display = 'block';
},

onClose() {
mainElementRef.current.style.display = 'none';
},

onPrepare(row, col, prop, td, originalValue, cellProperties) {
const tdPosition = td.getBoundingClientRect();

// As the `prepare` method is triggered after selecting
// any cell, we're updating the styles for the editor element,
// so it shows up in the correct position.
mainElementRef.current.style.left = tdPosition.left + window.pageXOffset + 'px';
mainElementRef.current.style.top = tdPosition.top + window.pageYOffset + 'px';
}
});

const setLowerCase = React.useCallback(() => {
setValue(value.toString().toLowerCase());

// Close the editor by the editor API method.
finishEditing();
}, [setValue, value, finishEditing]);

const setUpperCase = React.useCallback(() => {
setValue(value.toString().toUpperCase());

// Close the editor by the editor API method.
finishEditing();
}, [setValue, value, finishEditing]);

const stopMousedownPropagation = React.useCallback((e) => {
e.stopPropagation();
}
}, []);

render() {
return (
<div
style={this.containerStyle}
ref={this.mainElementRef}
onMouseDown={this.stopMousedownPropagation}
id="editorElement"
>
<button onClick={this.setLowerCase.bind(this)}>
{this.state.value.toLowerCase()}
</button>
<button onClick={this.setUpperCase.bind(this)}>
{this.state.value.toUpperCase()}
</button>
</div>
);
}
}
return (
<div
style={containerStyle}
ref={mainElementRef}
onMouseDown={stopMousedownPropagation}
id="editorElement"
>
<button onClick={setLowerCase}>
{value?.toLowerCase()}
</button>
<button onClick={setUpperCase}>
{value?.toUpperCase()}
</button>
</div>
);
};

const data = [
['Obrien Fischer'],
['Alexandria Gordon'],
['John Stafford'],
['Regina Waters'],
['Kay Bentley'],
['Emerson Drake'],
['Dean Stapleton']
['Obrien Fischer'],
['Alexandria Gordon'],
['John Stafford'],
['Regina Waters'],
['Kay Bentley'],
['Emerson Drake'],
['Dean Stapleton']
];

export const ExampleComponent = () => {
Expand All @@ -121,14 +92,12 @@ export const ExampleComponent = () => {
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
>
<HotColumn width={250}>
{/* add the `hot-editor` attribute to mark the component as a Handsontable editor */}
<EditorComponent hot-editor />
</HotColumn>
{/* add the `editor` prop to set the component as a Handsontable editor */}
<HotColumn width={250} editor={EditorComponent} />
</HotTable>
);
};

/* start:skip-in-preview */
ReactDOM.render(<ExampleComponent />, document.getElementById('example1'));
/* end:skip-in-preview */
/* end:skip-in-preview */
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Set together, a renderer, [editor](@/guides/cell-functions/cell-editor/cell-edit
Handsontable's React wrapper lets you create custom cell renderers using React components.
Although it's possible to use class-based react components for this purpose, we strongly suggest using functional components, as using the `state` of a class-based component would re-initialize on every Handsontable render.

To mark a component as a Handsontable renderer, simply add a `hot-renderer` attribute to it.
To provide a component as a Handsontable renderer, use `renderer` prop of either `HotTable` or `HotColumn`.

::: tip

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,12 @@ export const ExampleComponent = () => {
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation">
<HotColumn width={250}>
{/* add the `hot-renderer` attribute to mark the component as a Handsontable renderer */}
<RendererComponent hot-renderer />
</HotColumn>
{/* add the `renderer` prop to set the component as a Handsontable renderer */}
<HotColumn width={250} renderer={RendererComponent} />
</HotTable>
);
};

/* start:skip-in-preview */
ReactDOM.render(<ExampleComponent />, document.getElementById('example1'));
/* end:skip-in-preview */
/* end:skip-in-preview */
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,13 @@ export const ExampleComponent = () => {
rowHeaders={true}
licenseKey={"non-commercial-and-evaluation"}
>
<HotColumn>
{/* add the `hot-renderer` attribute to mark the component as a Handsontable renderer */}
<CustomRenderer hot-renderer />
</HotColumn>
{/* add the `renderer` prop to set the component as a Handsontable renderer */}
<HotColumn renderer={CustomRenderer} />
</HotTable>
</HighlightContext.Provider>
);
};

/* start:skip-in-preview */
ReactDOM.render(<ExampleComponent />, document.getElementById('example2'));
/* end:skip-in-preview */
/* end:skip-in-preview */
14 changes: 5 additions & 9 deletions docs/content/guides/columns/react-hot-column/react/example3.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,14 @@ export const ExampleComponent = () => {
{/* use the `data` prop to reference the column data */}
<HotColumn data="id" />
<HotColumn data="name" />
<HotColumn data="score">
{/* add the `hot-renderer` attribute to mark the component as a Handsontable renderer */}
<ScoreRenderer hot-renderer />
</HotColumn>
<HotColumn data="isPromoted">
{/* add the `hot-renderer` attribute to mark the component as a Handsontable renderer */}
<PromotionRenderer hot-renderer />
</HotColumn>
{/* add the `renderer` prop to set the component as a Handsontable renderer */}
<HotColumn data="score" renderer={ScoreRenderer} />
{/* add the `renderer` prop to set the component as a Handsontable renderer */}
<HotColumn data="isPromoted" renderer={PromotionRenderer} />
</HotTable>
);
};

/* start:skip-in-preview */
ReactDOM.render(<ExampleComponent />, document.getElementById('example3'));
/* end:skip-in-preview */
/* end:skip-in-preview */
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ The following example implements the `@handsontable/react` component with a [`re

## Advanced example

This example shows:
- A [custom editor](@/guides/cell-functions/cell-editor/cell-editor.md#component-based-editors) component (built with an external dependency, `HexColorPicker`). This component acts both as an editor and as a renderer.
- A [custom renderer](@/guides/cell-functions/cell-renderer/cell-renderer.md#declare-a-custom-renderer-as-a-component) component, built with an external dependency (`StarRatingComponent`).
This example shows two Redux-connected components:
- A [custom editor](@/guides/cell-functions/cell-editor.md#component-based-editors) component (built with an external dependency, `HexColorPicker`).
- A [custom renderer](@/guides/cell-functions/cell-renderer.md#declare-a-custom-renderer-as-a-component) component for stars, built with an external dependency (`StarRatingComponent`).

The editor component changes the behavior of the renderer component, by passing information through Redux (and the `connect()` method of `react-redux`).

Expand Down
Loading
Loading