Components & hooks: optimize

This commit is contained in:
Chika 2022-05-18 21:54:29 +03:00
parent 2267ce36ad
commit 962ed35352
19 changed files with 108 additions and 153 deletions

View File

@ -36,6 +36,17 @@
"lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": ["off"],
"indent": "off",
"@typescript-eslint/indent": ["off"]
"@typescript-eslint/indent": ["off"],
"react/jsx-no-bind": [
"error",
{
"ignoreDOMComponents": false,
"ignoreRefs": false,
"allowArrowFunctions": false,
"allowFunctions": true,
"allowBind": false
}
],
"newline-before-return": "warn"
}
}

View File

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-bind */
import { observer } from 'mobx-react-lite';
import { useStatus } from 'stores/calculation/statuses/hooks';
import { getAction } from '../config/map-actions';
@ -7,7 +8,7 @@ export default function buildAction({ elementName, Component, ...props }: Builde
const actionName = getAction(elementName);
return observer(() => {
const { status } = useStatus(elementName);
const status = useStatus(elementName);
return (
<Component

View File

@ -8,8 +8,8 @@ export default function buildComputedValue({ elementName, Component, ...props }:
const computedValueName = getComputedValueName(elementName);
return observer(() => {
const { computedValue } = useComputedValue(computedValueName);
const { status } = useStatus(elementName);
const computedValue = useComputedValue(computedValueName);
const status = useStatus(elementName);
return <Component value={computedValue} status={status} {...props} />;
});

View File

@ -2,7 +2,7 @@ import { observer } from 'mobx-react-lite';
import { useOptions } from 'stores/calculation/options/hooks';
import { useStatus } from 'stores/calculation/statuses/hooks';
import { useValidation } from 'stores/calculation/validation/hooks';
import { useValue } from 'stores/calculation/values/hooks';
import { useSetValue, useValue } from 'stores/calculation/values/hooks';
import { getValueName } from '../config/map-values';
import type { BuilderProps } from './types';
@ -10,10 +10,11 @@ export default function buildOptions({ elementName, Component, ...props }: Build
const valueName = getValueName(elementName);
return observer(() => {
const { value, setValue } = useValue(valueName);
const { status } = useStatus(elementName);
const value = useValue(valueName);
const setValue = useSetValue(valueName);
const status = useStatus(elementName);
const { isValid, help } = useValidation(elementName);
const { options } = useOptions(elementName);
const options = useOptions(elementName);
return (
<Component

View File

@ -7,8 +7,8 @@ export default function buildReadonly({ elementName, Component, ...props }: Buil
const valueName = getValueName(elementName);
return observer(() => {
const { value } = useValue(valueName);
const value = useValue(valueName);
return <Component value={value} {...props} />;
return <Component value={value} readonly {...props} />;
});
}

View File

@ -1,7 +1,7 @@
import { observer } from 'mobx-react-lite';
import { useStatus } from 'stores/calculation/statuses/hooks';
import { useValidation } from 'stores/calculation/validation/hooks';
import { useValue } from 'stores/calculation/values/hooks';
import { useSetValue } from 'stores/calculation/values/hooks';
import { getValueName } from '../config/map-values';
import type { BuilderProps } from './types';
@ -9,19 +9,12 @@ export default function buildValue({ elementName, Component, ...props }: Builder
const valueName = getValueName(elementName);
return observer(() => {
const { value, setValue } = useValue(valueName);
const { status } = useStatus(elementName);
const setValue = useSetValue(valueName);
const status = useStatus(elementName);
const { isValid, help } = useValidation(elementName);
return (
<Component
value={value}
setValue={setValue}
status={status}
isValid={isValid}
help={help}
{...props}
/>
<Component setValue={setValue} status={status} isValid={isValid} help={help} {...props} />
);
});
}

View File

@ -1,5 +1,5 @@
import type { ButtonProps } from 'antd';
import { Button } from 'antd';
import { Button as AntButton } from 'antd';
import { throttle } from 'lodash';
import type { Status } from './types';
@ -9,19 +9,19 @@ type ElementProps = {
text: string;
};
export default function Element({ status, action, text }: ElementProps) {
export default function Button({ status, action, text }: ElementProps) {
const throttledAction = throttle(action, 1200, {
trailing: false,
});
return (
<Button
<AntButton
disabled={status === 'Disabled'}
loading={status === 'Loading'}
onClick={throttledAction}
>
{text}
</Button>
</AntButton>
);
}

View File

@ -1,10 +1,11 @@
import type { CheckboxProps } from 'antd';
import { Checkbox, Form } from 'antd';
import { Checkbox as AntCheckbox, Form } from 'antd';
import type { CheckboxChangeEvent } from 'antd/lib/checkbox';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
export default function Element({
export default function Checkbox({
value,
setValue,
status,
@ -12,11 +13,15 @@ export default function Element({
help,
...props
}: BaseElementProps<boolean>) {
function handleChange(e: CheckboxChangeEvent) {
setValue(e.target.checked);
}
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<Checkbox
<AntCheckbox
checked={value}
onChange={(e) => setValue(e.target.value)}
onChange={handleChange}
disabled={status === 'Disabled'}
{...props}
/>

View File

@ -1,10 +1,11 @@
import type { InputProps } from 'antd';
import { Form, Input } from 'antd';
import { Form, Input as AntInput } from 'antd';
import type { ChangeEvent } from 'react';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
export default function Element({
export default function Input({
value,
setValue,
status,
@ -12,14 +13,13 @@ export default function Element({
help,
...props
}: BaseElementProps<string>) {
function handleChange(e: ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<Input
value={value}
onChange={(e) => setValue(e.target.value)}
disabled={status === 'Disabled'}
{...props}
/>
<AntInput value={value} onChange={handleChange} disabled={status === 'Disabled'} {...props} />
</FormItem>
);
}

View File

@ -1,17 +1,10 @@
import type { InputNumberProps } from 'antd';
import { Form, InputNumber } from 'antd';
import { Form, InputNumber as AntInputNumber } from 'antd';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
const parser = (val?: string): number => {
const res = val?.replace(/[^0-9.,]+/, '');
if (!res || res === '') return 0;
return parseFloat(res);
};
export default function Element({
value = 0,
export default function InputNumber({
setValue,
status,
isValid,
@ -20,11 +13,10 @@ export default function Element({
}: BaseElementProps<number>) {
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<InputNumber
value={value}
onChange={(val) => setValue(val)}
<AntInputNumber
type="number"
onChange={setValue}
disabled={status === 'Disabled'}
parser={parser}
{...props}
/>
</FormItem>

View File

@ -1,11 +1,11 @@
import DownloadOutlined from '@ant-design/icons/lib/icons/DownloadOutlined';
import type { ButtonProps } from 'antd';
import { Button } from 'antd';
import { Button as AntButton } from 'antd';
import type { BaseElementProps } from './types';
export default function Element({ value, setValue, status, ...props }: BaseElementProps<string>) {
export default function Link({ value, setValue, status, ...props }: BaseElementProps<string>) {
return (
<Button
<AntButton
rel="noopener"
target="_blank"
href={value}

View File

@ -1,5 +1,5 @@
import type { RadioProps } from 'antd';
import { Form, Radio } from 'antd';
import type { RadioChangeEvent, RadioProps } from 'antd';
import { Form, Radio as AntRadio } from 'antd';
import type { BaseElementProps, BaseOption } from './types';
const { Item: FormItem } = Form;
@ -8,7 +8,7 @@ type ElementProps = BaseElementProps<string | null> & {
options: BaseOption[];
};
export default function Element({
export default function Radio({
value = null,
setValue,
options,
@ -17,12 +17,16 @@ export default function Element({
help,
...props
}: ElementProps) {
function handleChange(e: RadioChangeEvent) {
setValue(e.target.value);
}
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<Radio.Group
<AntRadio.Group
value={value}
options={options}
onChange={(e) => setValue(e.target.value)}
onChange={handleChange}
disabled={status === 'Disabled'}
{...props}
/>

View File

@ -1,5 +1,6 @@
import type { SelectProps } from 'antd';
import { Form, Select } from 'antd';
import { Form, Select as AntSelect } from 'antd';
import { useMemo } from 'react';
import type { BaseElementProps, BaseOption } from './types';
const { Item: FormItem } = Form;
@ -8,7 +9,7 @@ type ElementProps = BaseElementProps<string | null> & {
options: BaseOption[];
};
export default function Element({
export default function Select({
value = null,
setValue,
options,
@ -17,20 +18,26 @@ export default function Element({
help,
...props
}: ElementProps) {
const optionsWithNull = useMemo(
() => [
{
label: 'Не выбрано',
value: null,
},
...options,
],
[options]
);
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<Select
<AntSelect
value={value}
onChange={(val) => setValue(val)}
onChange={setValue}
disabled={status === 'Disabled'}
loading={status === 'Loading'}
optionFilterProp="children"
options={[
{
label: 'Не выбрано',
value: null,
},
].concat(options)}
options={optionsWithNull}
{...props}
/>
</FormItem>

View File

@ -1,10 +1,10 @@
import type { SwitchProps } from 'antd';
import { Form, Switch } from 'antd';
import { Form, Switch as AntSwitch } from 'antd';
import type { BaseElementProps } from './types';
const { Item: FormItem } = Form;
export default function Element({
export default function Switch({
value,
setValue,
status,
@ -14,12 +14,7 @@ export default function Element({
}: BaseElementProps<boolean>) {
return (
<FormItem hasFeedback validateStatus={isValid === false ? 'error' : ''} help={help}>
<Switch
checked={value}
onChange={(enabled) => setValue(enabled)}
disabled={status === 'Disabled'}
{...props}
/>
<AntSwitch checked={value} onChange={setValue} disabled={status === 'Disabled'} {...props} />
</FormItem>
);
}

View File

@ -1,13 +1,13 @@
import styled from 'styled-components';
import type { BaseElementProps } from './types';
const Text = styled.span`
const Span = styled.span`
margin-bottom: 18px;
font-size: 0.85rem;
`;
export default function Element({ value, ...props }: BaseElementProps<string>) {
return <Text {...props}>{value}</Text>;
export default function Text({ value, ...props }: BaseElementProps<string>) {
return <Span {...props}>{value}</Span>;
}
type TextProps = {

View File

@ -1,17 +1,9 @@
/* eslint-disable import/prefer-default-export */
import { computed } from 'mobx';
import { useMemo } from 'react';
import { useStore } from 'stores/hooks';
export function useOptions(elementName) {
const { $calculation } = useStore();
const options = $calculation.$options.getOptions(elementName);
const options = useMemo(
() => computed(() => $calculation.$options.getOptions(elementName)),
[$calculation.$options, elementName]
);
return {
options: options.get(),
};
return options;
}

View File

@ -1,17 +1,9 @@
/* eslint-disable import/prefer-default-export */
import { computed } from 'mobx';
import { useMemo } from 'react';
import { useStore } from 'stores/hooks';
export function useStatus(elementName) {
const { $calculation } = useStore();
const status = $calculation.$status.getStatus(elementName);
const status = useMemo(
() => computed(() => $calculation.$status.getStatus(elementName)),
[$calculation.$status, elementName]
);
return {
status: status.get(),
};
return status;
}

View File

@ -1,18 +1,12 @@
/* eslint-disable import/prefer-default-export */
import { computed } from 'mobx';
import { useMemo } from 'react';
import { useStore } from 'stores/hooks';
export function useValidation(elementName) {
const { $calculation } = useStore();
const validationResult = useMemo(
() => computed(() => $calculation.$validation.getValidation(elementName)),
[$calculation.$validation, elementName]
).get();
const validationResult = $calculation.$validation.getValidation(elementName);
return {
...validationResult,
help: validationResult.isValid === false ? 'Некорректные данные' : null,
help: validationResult?.isValid === false ? 'Некорректные данные' : null,
};
}

View File

@ -1,62 +1,30 @@
import { computed } from 'mobx';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useStore } from 'stores/hooks';
import { useDebouncedCallback } from 'use-debounce';
const WAIT = 350;
const MAX_WAIT = 1500;
export function useValue(valueName) {
const [value, setValue] = useState();
const { $calculation } = useStore();
const value = $calculation.$values.getValue(valueName);
return value;
}
export function useSetValue(valueName) {
const { $calculation } = useStore();
const setStoreValue = useCallback(
(val) => $calculation.$values.setValue(valueName, val),
[$calculation.$values, valueName]
);
const debouncedSetStoreValue = useDebouncedCallback(setStoreValue, WAIT, {
maxWait: MAX_WAIT,
});
/** Set local value to global store */
useEffect(() => {
debouncedSetStoreValue(value);
}, [debouncedSetStoreValue, value]);
const storeValue = useMemo(
() => computed(() => $calculation.$values.getValue(valueName)),
[$calculation.$values, valueName]
).get();
/** Set initial value from global store to local state (only once) */
useEffect(
() => {
setValue(storeValue);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
/** Get global store value and set it to local state */
useEffect(() => {
if (!debouncedSetStoreValue.isPending()) {
setValue(storeValue);
const debouncedSetStoreValue = useDebouncedCallback(
(value) => $calculation.$values.setValue(valueName, value),
350,
{
maxWait: 1500,
}
}, [debouncedSetStoreValue, storeValue]);
);
return {
value,
setValue,
};
return debouncedSetStoreValue;
}
export function useComputedValue(valueName) {
const { $calculation } = useStore();
const computedValue = $calculation.$values.$computed[valueName];
return {
computedValue,
};
return computedValue;
}