diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 33736e7..13ac012 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -19,6 +19,7 @@ module.exports = { plugins: [ 'only-warn', 'react', + 'react-hooks', '@typescript-eslint' ], rules: { @@ -29,5 +30,7 @@ module.exports = { '@typescript-eslint/semi': ['warn', 'always'], 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error', + 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks + 'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies } }; diff --git a/package-lock.json b/package-lock.json index 285432c..8573881 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "react-svg-pan-zoom": "^3.11.0" }, "devDependencies": { + "@testing-library/dom": "^8.16.1", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^14.4.1", @@ -31,8 +32,10 @@ "eslint-config-standard-with-typescript": "^22.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-n": "^15.2.4", + "eslint-plugin-only-warn": "^1.0.3", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", "jsdom": "^20.0.0", "postcss": "^8.4.14", "sass": "^1.54.0", @@ -745,9 +748,9 @@ "dev": true }, "node_modules/@testing-library/dom": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.0.tgz", - "integrity": "sha512-uxF4zmnLHHDlmW4l+0WDjcgLVwCvH+OVLpD8Dfp+Bjfz85prwxWGbwXgJdLtkgjD0qfOzkJF9SmA6YZPsMYX4w==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", + "integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -2879,6 +2882,15 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-only-warn": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.0.3.tgz", + "integrity": "sha512-XQOX/TfLoLw6h8ky51d29uUjXRTQHqBGXPylDEmy5fe/w7LIOnp8MA24b1OSMEn9BQoKow1q3g1kLe5/9uBTvw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", @@ -2919,6 +2931,18 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -6708,9 +6732,9 @@ "dev": true }, "@testing-library/dom": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.0.tgz", - "integrity": "sha512-uxF4zmnLHHDlmW4l+0WDjcgLVwCvH+OVLpD8Dfp+Bjfz85prwxWGbwXgJdLtkgjD0qfOzkJF9SmA6YZPsMYX4w==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", + "integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", @@ -8254,6 +8278,12 @@ "semver": "^7.3.7" } }, + "eslint-plugin-only-warn": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.0.3.tgz", + "integrity": "sha512-XQOX/TfLoLw6h8ky51d29uUjXRTQHqBGXPylDEmy5fe/w7LIOnp8MA24b1OSMEn9BQoKow1q3g1kLe5/9uBTvw==", + "dev": true + }, "eslint-plugin-promise": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", @@ -8311,6 +8341,13 @@ } } }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/package.json b/package.json index 1c45135..0f0d27f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "eslint-plugin-only-warn": "^1.0.3", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", "jsdom": "^20.0.0", "postcss": "^8.4.14", "sass": "^1.54.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10ead3b..469528f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,7 @@ specifiers: eslint-plugin-only-warn: ^1.0.3 eslint-plugin-promise: ^6.0.0 eslint-plugin-react: ^7.30.1 + eslint-plugin-react-hooks: ^4.6.0 framer-motion: ^6.5.1 jsdom: ^20.0.0 postcss: ^8.4.14 @@ -62,6 +63,7 @@ devDependencies: eslint-plugin-only-warn: 1.0.3 eslint-plugin-promise: 6.0.0_eslint@8.21.0 eslint-plugin-react: 7.30.1_eslint@8.21.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.21.0 jsdom: 20.0.0 postcss: 8.4.16 sass: 1.54.3 @@ -1740,6 +1742,15 @@ packages: eslint: 8.21.0 dev: true + /eslint-plugin-react-hooks/4.6.0_eslint@8.21.0: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.21.0 + dev: true + /eslint-plugin-react/7.30.1_eslint@8.21.0: resolution: {integrity: sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==} engines: {node: '>=4'} diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index 8ad34ec..ec5a28a 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -5,6 +5,7 @@ import { ContainerModel, IContainerModel } from '../../Interfaces/ContainerModel import { findContainerById } from '../../utils/itertools'; import { getCurrentHistory } from './Editor'; import { SizePointer } from '../../Interfaces/SizePointer'; +import Properties from '../../Interfaces/Properties'; /** * Select a container @@ -274,7 +275,7 @@ export function OnPropertyChange( */ export function OnPropertiesSubmit( event: React.SyntheticEvent, - refs: Array>, + properties: Properties, fullHistory: HistoryState[], historyCurrentStep: number, setHistory: Dispatch>, @@ -291,10 +292,10 @@ export function OnPropertiesSubmit( if (parent === null) { const selectedContainerClone: IContainerModel = structuredClone(current.SelectedContainer); - for (const ref of refs) { - const input = ref.current; + for (const property in properties) { + const input = (event.target as HTMLFormElement).querySelector(`#${property}`); if (input instanceof HTMLInputElement) { - (selectedContainerClone.properties as any)[input.id] = input.value; + (selectedContainerClone.properties as any)[property] = input.value; } } setHistory(history.concat([{ @@ -315,10 +316,10 @@ export function OnPropertiesSubmit( throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); } - for (const ref of refs) { - const input = ref.current; + for (const property in properties) { + const input = (event.target as HTMLFormElement).querySelector(`#${property}`); if (input instanceof HTMLInputElement) { - (container.properties as any)[input.id] = input.value; + (container.properties as any)[property] = input.value; } } diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index b008607..f07d5e8 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -72,9 +72,9 @@ const Editor: React.FunctionComponent = (props) => { setHistory, setHistoryCurrentStep )} - OnPropertiesSubmit={(event, refs) => OnPropertiesSubmit( + OnPropertiesSubmit={(event, properties) => OnPropertiesSubmit( event, - refs, + properties, history, historyCurrentStep, setHistory, diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index 96be3fe..c0a89c6 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { motion } from 'framer-motion'; import { Properties } from '../Properties/Properties'; +import ContainerProperties from '../../Interfaces/Properties'; import { IContainerModel } from '../../Interfaces/ContainerModel'; import { getDepth, MakeIterator } from '../../utils/itertools'; import { Menu } from '../Menu/Menu'; @@ -8,13 +9,14 @@ import { MenuItem } from '../Menu/MenuItem'; import { handleDragLeave, handleDragOver, handleLeftClick, handleOnDrop, handleRightClick } from './MouseEventHandlers'; import { Point } from '../../Interfaces/Point'; + interface IElementsSidebarProps { MainContainer: IContainerModel isOpen: boolean isHistoryOpen: boolean SelectedContainer: IContainerModel | null OnPropertyChange: (key: string, value: string | number | boolean) => void - OnPropertiesSubmit: (event: React.FormEvent, refs: Array>) => void + OnPropertiesSubmit: (event: React.FormEvent, properties: ContainerProperties) => void SelectContainer: (container: IContainerModel) => void DeleteContainer: (containerid: string) => void AddContainer: (index: number, type: string, parent: string) => void diff --git a/src/Components/Properties/Properties.tsx b/src/Components/Properties/Properties.tsx index 7477542..3720188 100644 --- a/src/Components/Properties/Properties.tsx +++ b/src/Components/Properties/Properties.tsx @@ -6,7 +6,7 @@ import { INPUT_TYPES } from './PropertiesInputTypes'; interface IPropertiesProps { properties?: ContainerProperties onChange: (key: string, value: string | number | boolean) => void - onSubmit: (event: React.FormEvent, refs: Array>) => void + onSubmit: (event: React.FormEvent, properties: ContainerProperties) => void } export const Properties: React.FC = (props: IPropertiesProps) => { @@ -17,10 +17,9 @@ export const Properties: React.FC = (props: IPropertiesProps) } const groupInput: React.ReactNode[] = []; - const refs: Array> = []; Object .entries(props.properties) - .forEach((pair) => handleProperties(pair, groupInput, refs, isDynamicInput, props.onChange)); + .forEach((pair) => handleProperties(pair, groupInput, isDynamicInput, props.onChange)); const form = isDynamicInput ?
@@ -28,7 +27,7 @@ export const Properties: React.FC = (props: IPropertiesProps)
:
props.onSubmit(event, refs)} + onSubmit={(event) => props.onSubmit(event, props.properties as ContainerProperties)} > { groupInput } @@ -52,7 +51,6 @@ export const Properties: React.FC = (props: IPropertiesProps) const handleProperties = ( [key, value]: [string, string | number], groupInput: React.ReactNode[], - refs: Array>, isDynamicInput: boolean, onChange: (key: string, value: string | number | boolean) => void ): void => { @@ -69,9 +67,6 @@ const handleProperties = ( type = INPUT_TYPES[key]; } - const ref: React.RefObject = React.useRef(null); - refs.push(ref); - const isDisabled = ['id', 'parentId'].includes(key); const input = isDynamicInput ? { @@ -102,7 +96,6 @@ const handleProperties = ( ' type={type} id={key} - ref={ref} defaultValue={value} defaultChecked={checked} disabled={isDisabled} diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index 5c7012e..ffe9fb3 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -8,6 +8,7 @@ import { HistoryState } from '../../Interfaces/HistoryState'; import { PhotographIcon, UploadIcon } from '@heroicons/react/outline'; import { FloatingButton } from '../FloatingButton/FloatingButton'; import { Bar } from '../Bar/Bar'; +import Properties from '../../Interfaces/Properties'; interface IUIProps { current: HistoryState @@ -17,7 +18,7 @@ interface IUIProps { SelectContainer: (container: ContainerModel) => void DeleteContainer: (containerId: string) => void OnPropertyChange: (key: string, value: string | number | boolean) => void - OnPropertiesSubmit: (event: React.FormEvent, refs: Array>) => void + OnPropertiesSubmit: (event: React.FormEvent, properties: Properties) => void AddContainerToSelectedContainer: (type: string) => void AddContainer: (index: number, type: string, parentId: string) => void SaveEditorAsJSON: () => void