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 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 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
c6fc760
Improve DX of `useHotEditor` (#11152)
evanSe Oct 1, 2024
5e8ff78
Merge remote-tracking branch 'origin/develop' into feature/improved-r…
jansiegel Oct 1, 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
Prev Previous commit
Next Next commit
React wrapper: Enable TypeScript strict mode & ensure proper strong t…
…yping everywhere (#10813)

* Enable TypeScript strict mode & ensure proper strong typing everywhere; get rid of any typing.

* more ts

* more

* last

* rollback

* changelog

* revert
  • Loading branch information
NOtherDev authored Feb 26, 2024
commit df1a480e220b790cf3bff07b085ad9e8a97b8293
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"
}
18 changes: 9 additions & 9 deletions wrappers/react/src/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { HotTableProps } from './types';

let bulkComponentContainer = null;
let bulkComponentContainer: DocumentFragment | null = null;

/**
* Warning message for the `autoRowSize`/`autoColumnSize` compatibility check.
Expand Down Expand Up @@ -50,7 50,7 @@ export const DEFAULT_CLASSNAME = 'hot-wrapper-editor-container';
*
* @param {...*} args Values which will be logged.
*/
export function warn(...args) {
export function warn(...args: any[]) {
if (typeof console !== 'undefined') {
console.warn(...args);
}
Expand Down Expand Up @@ -131,13 131,13 @@ function hasChildElementOfType(children: React.ReactNode, type: 'hot-renderer' |
* @param {React.ComponentType} Editor Editor component or render function.
* @returns {React.ReactPortal} The portal for the editor.
*/
export function createEditorPortal(doc: Document, Editor: HotTableProps['editor'] | undefined): React.ReactPortal | null {
if (typeof doc === 'undefined' || !Editor) {
export function createEditorPortal(doc: Document | null, Editor: HotTableProps['editor'] | undefined): React.ReactPortal | null {
if (!doc || !Editor) {
return null;
}

const editorElement = <Editor />;
const containerProps = getContainerAttributesProps(editorElement.props, false);
const containerProps = getContainerAttributesProps({}, false);

containerProps.className = `${DEFAULT_CLASSNAME} ${containerProps.className}`;

Expand All @@ -157,7 157,7 @@ export function createEditorPortal(doc: Document, Editor: HotTableProps['editor'
* @param {HTMLElement} [cachedContainer] The cached container to be used for the portal.
* @returns {{portal: React.ReactPortal, portalContainer: HTMLElement}} An object containing the portal and its container.
*/
export function createPortal(rElement: React.ReactElement, ownerDocument: Document = document, portalKey: string, cachedContainer?: HTMLElement): {
export function createPortal(rElement: React.ReactElement, ownerDocument: Document | null = document, portalKey: string, cachedContainer?: HTMLElement): {
portal: React.ReactPortal,
portalContainer: HTMLElement,
} {
Expand All @@ -182,12 182,12 @@ export function createPortal(rElement: React.ReactElement, ownerDocument: Docume
* Get an object containing the `id`, `className` and `style` keys, representing the corresponding props passed to the
* component.
*
* @param {Object} props Object containing the react element props.
* @param {HotTableProps} props Object containing the React element props.
* @param {Boolean} randomizeId If set to `true`, the function will randomize the `id` property when no `id` was present in the `prop` object.
* @returns An object containing the `id`, `className` and `style` keys, representing the corresponding props passed to the
* component.
*/
export function getContainerAttributesProps(props, randomizeId: boolean = true): {id: string, className: string, style: object} {
export function getContainerAttributesProps(props: HotTableProps, randomizeId: boolean = true): {id?: string, className: string, style: React.CSSProperties} {
return {
id: props.id || (randomizeId ? 'hot-' Math.random().toString(36).substring(5) : undefined),
className: props.className || '',
Expand All @@ -214,7 214,7 @@ export function superBound<T>(that: T): T {
const proto = Object.getPrototypeOf(Object.getPrototypeOf(that));
const superBoundObj = {} as T;

Object.getOwnPropertyNames(proto).forEach((key) => {
(Object.getOwnPropertyNames(proto) as (keyof T)[]).forEach((key: keyof T) => {
if (typeof proto[key] === 'function') {
superBoundObj[key] = proto[key].bind(that);
} else {
Expand Down
8 changes: 4 additions & 4 deletions wrappers/react/src/hotColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 22,12 @@ const HotColumn: React.FC<HotColumnProps> = (props) => {
/**
* Reference to component-based editor overridden hooks object.
*/
const localEditorHooksRef = React.useRef<HotEditorHooks>();
const localEditorHooksRef = React.useRef<HotEditorHooks | null>(null);

/**
* Reference to HOT-native custom editor class instance.
*/
const localEditorClassInstance = React.useRef<Handsontable.editors.BaseEditor>();
const localEditorClassInstance = React.useRef<Handsontable.editors.BaseEditor | null>(null);

/**
* Logic performed after mounting & updating of the HotColumn component.
Expand All @@ -42,8 42,8 @@ const HotColumn: React.FC<HotColumnProps> = (props) => {
const getSettingsProps = (): HotTableProps => {
return Object.keys(props)
.filter(key => !internalProps.includes(key))
.reduce((obj, key) => {
obj[key] = props[key];
.reduce<HotTableProps>((obj, key) => {
(obj as any)[key] = props[key];
return obj;
}, {});
};
Expand Down
2 changes: 1 addition & 1 deletion wrappers/react/src/hotColumnContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 28,6 @@ const HotColumnContextProvider: React.FC<React.PropsWithChildren<HotColumnContex
);
};

const useHotColumnContext = () => React.useContext(HotColumnContext);
const useHotColumnContext = () => React.useContext(HotColumnContext)!;

export { useHotColumnContext, HotColumnContextProvider };
12 changes: 7 additions & 5 deletions wrappers/react/src/hotEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 3,28 @@ import Handsontable from 'handsontable/base';
import { HotEditorHooks } from './types';
import { superBound } from "./helpers";

type HookPropName = (keyof Handsontable.editors.BaseEditor & keyof HotEditorHooks) | 'constructor'

/**
* Create a class to be passed to the Handsontable's settings.
*
* @param {React.RefObject<HotEditorHooks>} hooksRef Reference to component-based editor overridden hooks object.
* @param {React.RefObject} instanceRef Reference to Handsontable-native custom editor class instance.
* @returns {Function} A class to be passed to the Handsontable editor settings.
*/
export function makeEditorClass(hooksRef: React.MutableRefObject<HotEditorHooks>, instanceRef: React.MutableRefObject<Handsontable.editors.BaseEditor>): typeof Handsontable.editors.BaseEditor {
export function makeEditorClass(hooksRef: React.MutableRefObject<HotEditorHooks | null>, instanceRef: React.MutableRefObject<Handsontable.editors.BaseEditor | null>): typeof Handsontable.editors.BaseEditor {
return class CustomEditor extends Handsontable.editors.BaseEditor implements Handsontable.editors.BaseEditor {
constructor(hotInstance: Handsontable.Core) {
super(hotInstance);
instanceRef.current = this;

Object.getOwnPropertyNames(Handsontable.editors.BaseEditor.prototype).forEach(propName => {
(Object.getOwnPropertyNames(Handsontable.editors.BaseEditor.prototype) as HookPropName[]).forEach((propName) => {
if (propName === 'constructor') {
return;
}

const baseMethod = Handsontable.editors.BaseEditor.prototype[propName];
CustomEditor.prototype[propName] = function (...args) {
(CustomEditor.prototype as any)[propName] = function (this: CustomEditor, ...args: any[]) {
if (hooksRef.current?.[propName]) {
return hooksRef.current[propName].call(this, ...args);
} else {
Expand Down Expand Up @@ -87,8 89,8 @@ export const EditorContextProvider: React.FC<EditorContextProviderProps> = ({ ho
* @returns {React.RefObject} Reference to Handsontable-native editor instance.
*/
export function useHotEditor(overriddenHooks?: (runSuper: () => Handsontable.editors.BaseEditor) => HotEditorHooks, deps?: React.DependencyList): React.RefObject<Handsontable.editors.BaseEditor> {
const { hooksRef, hotCustomEditorInstanceRef } = React.useContext(EditorContext);
const runSuper = () => superBound(hotCustomEditorInstanceRef.current);
const { hooksRef, hotCustomEditorInstanceRef } = React.useContext(EditorContext)!;
const runSuper = () => superBound(hotCustomEditorInstanceRef.current!);
React.useImperativeHandle(hooksRef, () => overriddenHooks?.(runSuper) || {}, deps);
return hotCustomEditorInstanceRef;
}
8 changes: 4 additions & 4 deletions wrappers/react/src/hotTableContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 28,7 @@ export interface HotTableContextImpl {
* Return a renderer wrapper function for the provided renderer component.
*
* @param {React.ComponentType<HotRendererProps>} Renderer React renderer component.
* @returns {Handsontable.renderers.Base} The Handsontable rendering function.
* @returns {Handsontable.renderers.BaseRenderer} The Handsontable rendering function.
*/
readonly getRendererWrapper: (Renderer: React.ComponentType<HotRendererProps>) => typeof Handsontable.renderers.BaseRenderer;

Expand Down Expand Up @@ -82,7 82,7 @@ const HotTableContextProvider: React.FC<React.PropsWithChildren> = ({ children }
const portalKey = `${key}-${instanceGuid}`

if (renderedCellCache.current.has(key)) {
TD.innerHTML = renderedCellCache.current.get(key).innerHTML;
TD.innerHTML = renderedCellCache.current.get(key)!.innerHTML;
}

if (TD && !TD.getAttribute('ghost-table')) {
Expand Down Expand Up @@ -152,8 152,8 @@ const HotTableContextProvider: React.FC<React.PropsWithChildren> = ({ children }
*
* @returns HotTableContext
*/
function useHotTableContext() {
return React.useContext(HotTableContext);
function useHotTableContext(): HotTableContextImpl {
return React.useContext(HotTableContext)!;
}

export { HotTableContextProvider, useHotTableContext };
10 changes: 5 additions & 5 deletions wrappers/react/src/hotTableInner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 37,12 @@ const HotTableInner = React.forwardRef<HotTableRef, HotTableProps>((props, ref)
/**
* Reference to component-based editor overridden hooks object.
*/
const globalEditorHooksRef = React.useRef<HotEditorHooks>();
const globalEditorHooksRef = React.useRef<HotEditorHooks | null>(null);

/**
* Reference to HOT-native custom editor class instance.
*/
const globalEditorClassInstance = React.useRef<Handsontable.editors.BaseEditor>();
const globalEditorClassInstance = React.useRef<Handsontable.editors.BaseEditor | null>(null);

/**
* HotTable context exposing helper functions.
Expand Down Expand Up @@ -115,7 115,7 @@ const HotTableInner = React.forwardRef<HotTableRef, HotTableProps>((props, ref)
/**
* Detect if `autoRowSize` or `autoColumnSize` is defined, and if so, throw an incompatibility warning.
*/
const displayAutoSizeWarning = (hotInstance?: Handsontable): void => {
const displayAutoSizeWarning = (hotInstance: Handsontable | null): void => {
if (
hotInstance &&
(
Expand All @@ -135,7 135,7 @@ const HotTableInner = React.forwardRef<HotTableRef, HotTableProps>((props, ref)
React.useEffect(() => {
const newGlobalSettings = createNewGlobalSettings();

__hotInstance.current = new Handsontable.Core(hotElementRef.current, newGlobalSettings);
__hotInstance.current = new Handsontable.Core(hotElementRef.current!, newGlobalSettings);

/**
* Handsontable's `beforeViewRender` hook callback.
Expand Down Expand Up @@ -189,7 189,7 @@ const HotTableInner = React.forwardRef<HotTableRef, HotTableProps>((props, ref)
*/
React.useImperativeHandle(ref, () => ({
get hotElementRef() {
return hotElementRef.current;
return hotElementRef.current!;
},
get hotInstance() {
return getHotInstance();
Expand Down
2 changes: 1 addition & 1 deletion wrappers/react/src/settingsMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 13,7 @@ export class SettingsMapper {

for (const key in properties) {
if (key !== 'children' && properties.hasOwnProperty(key)) {
newSettings[key] = properties[key];
(newSettings as any)[key] = properties[key as keyof HotTableProps];
}
}

Expand Down
Loading
Loading