create mobx reacting effects
This commit is contained in:
parent
5f479c1fb6
commit
2109523a52
@ -29,7 +29,6 @@
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.3",
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Evo Calculator</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@ -1,54 +1,46 @@
|
||||
import CalculationStore from "client/stores/CalculationStore";
|
||||
import withTitle from "client/hocs/withTitle";
|
||||
import { testEffectForPrice, testEffectForOne } from "client/Effects";
|
||||
import Input from "client/Elements/Input";
|
||||
import CalculationStore from 'client/stores/CalculationStore';
|
||||
import withTitle from 'client/hocs/withTitle';
|
||||
import Input from 'client/Elements/Input';
|
||||
|
||||
export default [
|
||||
{
|
||||
title: "FirstSection",
|
||||
title: 'FirstSection',
|
||||
elements: [
|
||||
{
|
||||
name: "tbxPrice",
|
||||
Component: withTitle("Price")(Input),
|
||||
name: 'tbxPrice',
|
||||
Component: withTitle('Price')(Input),
|
||||
props: {
|
||||
placeholder: "Enter price",
|
||||
sourceValueName: "price",
|
||||
Effects: [testEffectForPrice],
|
||||
},
|
||||
placeholder: 'Enter price',
|
||||
sourceValueName: 'price'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "tbxOne",
|
||||
Component: withTitle("One")(Input),
|
||||
name: 'tbxOne',
|
||||
Component: withTitle('One')(Input),
|
||||
props: {
|
||||
placeholder: "Enter one",
|
||||
sourceValueName: "one",
|
||||
Effects: [testEffectForOne],
|
||||
},
|
||||
placeholder: 'Enter one',
|
||||
sourceValueName: 'one'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "tbxSecond",
|
||||
Component: withTitle("Sum")(Input),
|
||||
name: 'total',
|
||||
Component: withTitle('Total')(Input),
|
||||
props: {
|
||||
readonly: true,
|
||||
getValue: (calculationStore: CalculationStore) => {
|
||||
const price = calculationStore.getValue("price");
|
||||
const one = calculationStore.getValue("one");
|
||||
return parseInt(price) + parseInt(one);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
computed: 'total'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "SecondSection",
|
||||
title: 'SecondSection',
|
||||
elements: [
|
||||
{
|
||||
name: "priceonAnotherTab",
|
||||
Component: withTitle("Price on another tab")(Input),
|
||||
name: 'priceonAnotherTab',
|
||||
Component: withTitle('Price on another tab')(Input),
|
||||
props: {
|
||||
sourceValueName: "price",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
sourceValueName: 'price'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import Background from "client/Elements/Background";
|
||||
import React, { useState } from "react";
|
||||
import Sections from "./Sections";
|
||||
import Background from 'client/Elements/Background';
|
||||
import React, { useState } from 'react';
|
||||
import Sections from './Sections';
|
||||
|
||||
import { Tabs } from "antd";
|
||||
import { Box } from "client/UIKit/grid";
|
||||
import { Tabs } from 'antd';
|
||||
import { Box } from 'client/UIKit/grid';
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const Calculation = () => {
|
||||
@ -14,8 +14,8 @@ const Calculation = () => {
|
||||
<TabPane tab={title} key={i}>
|
||||
{elements.map(({ Component, props }, ie) => {
|
||||
return (
|
||||
<Box width="250px" my="10px">
|
||||
<Component {...props} key={ie} />
|
||||
<Box width="250px" my="10px" key={ie}>
|
||||
<Component {...props} />
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import CalculationStore from "client/stores/CalculationStore";
|
||||
import { TEffect } from "core/types/effect";
|
||||
|
||||
export const testEffectForPrice: TEffect = async (
|
||||
calculationStore: CalculationStore
|
||||
) => {
|
||||
console.log("priceEffect");
|
||||
const price = calculationStore.getValue("price");
|
||||
if (parseInt(price) > 5000) {
|
||||
calculationStore.setValue("one", 200);
|
||||
}
|
||||
};
|
||||
|
||||
export const testEffectForOne: TEffect = async (
|
||||
calculationStore: CalculationStore
|
||||
) => {
|
||||
const one = calculationStore.getValue("one");
|
||||
if (parseInt(one) > 1000) {
|
||||
calculationStore.setValue("price", 100500);
|
||||
}
|
||||
};
|
||||
@ -1,47 +1,35 @@
|
||||
import { Input as AntInput } from "antd";
|
||||
import { useStores } from "client/hooks/useStores";
|
||||
import runEffects from "client/tools/runEffects";
|
||||
import { observer } from "mobx-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { Input as AntInput } from 'antd';
|
||||
import { useStores } from 'client/hooks/useStores';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
const Input = ({
|
||||
readonly,
|
||||
placeholder,
|
||||
sourceValueName,
|
||||
getValue,
|
||||
Effects,
|
||||
}) => {
|
||||
const Input = ({ readonly, placeholder, sourceValueName, computed }) => {
|
||||
const { calculationStore } = useStores();
|
||||
const sourceValue = calculationStore.getValue(sourceValueName);
|
||||
const [currentValue, setCurrentValue] = useState(undefined);
|
||||
const [debouncedValue] = useDebounce(currentValue, 850);
|
||||
|
||||
// get Values
|
||||
useEffect(() => {
|
||||
if (sourceValueName) {
|
||||
if (sourceValue) {
|
||||
setCurrentValue(sourceValue);
|
||||
}
|
||||
}
|
||||
}, [sourceValue, sourceValueName]);
|
||||
const sourceValue = calculationStore.values[sourceValueName];
|
||||
|
||||
// set Value to global store and run Effects
|
||||
// get value from store
|
||||
useEffect(() => {
|
||||
if (sourceValueName && debouncedValue) {
|
||||
if (!computed) {
|
||||
setCurrentValue(sourceValue);
|
||||
}
|
||||
}, [computed, sourceValue]);
|
||||
|
||||
// set value to store
|
||||
useEffect(() => {
|
||||
if (!computed) {
|
||||
calculationStore.setValue(sourceValueName, debouncedValue);
|
||||
}
|
||||
|
||||
if (Effects && Effects.length > 0) {
|
||||
runEffects(Effects, calculationStore);
|
||||
}
|
||||
}, [debouncedValue]);
|
||||
}, [calculationStore, computed, debouncedValue, sourceValueName]);
|
||||
|
||||
return (
|
||||
<AntInput
|
||||
placeholder={placeholder}
|
||||
value={getValue ? getValue(calculationStore) : currentValue}
|
||||
onChange={(e) => {
|
||||
value={computed ? calculationStore[computed]() : currentValue}
|
||||
onChange={e => {
|
||||
if (!readonly) {
|
||||
setCurrentValue(e.target.value);
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import paths from "client/common/paths";
|
||||
import { LogoText } from "client/Elements/Text";
|
||||
import colors from "client/UIKit/colors";
|
||||
import { Box, Flex } from "client/UIKit/grid";
|
||||
import React from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import paths from 'client/common/paths';
|
||||
import { LogoText } from 'client/Elements/Text';
|
||||
import colors from 'client/UIKit/colors';
|
||||
import { Box, Flex } from 'client/UIKit/grid';
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
const Header = () => (
|
||||
<Flex style={styles.header}>
|
||||
@ -43,18 +43,17 @@ const Layout = () => (
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
height: "100%",
|
||||
height: '100%'
|
||||
},
|
||||
flex: { width: "100%" },
|
||||
flex: { width: '100%' },
|
||||
header: {
|
||||
// backgroundColor: colors.violet.shades[700],
|
||||
background:
|
||||
"linear-gradient(90deg, rgba(69,0,198,1) 0%, rgba(205,0,231,1) 60%, rgba(163,2,184,1) 100%)",
|
||||
height: "50px",
|
||||
padding: "10px 12px",
|
||||
paddingLeft: "20px",
|
||||
background: 'linear-gradient(90deg, #1C01A9 0%, #3A0185 50%, #580161 100%)',
|
||||
height: '50px',
|
||||
padding: '10px 12px',
|
||||
paddingLeft: '20px'
|
||||
// borderRadius: "0 0 10px 10px",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
import initialStatuses from "core/config/initialStatuses";
|
||||
import initialValues from "core/config/initialValues";
|
||||
import { SourceValueNames } from "core/types/values";
|
||||
import { action, observable } from "mobx";
|
||||
import { Status } from "core/types/elements";
|
||||
|
||||
class CalculationStore {
|
||||
values = observable(initialValues);
|
||||
statuses = observable(initialStatuses);
|
||||
|
||||
getValue = (sourceValueName: SourceValueNames) =>
|
||||
this.values[sourceValueName];
|
||||
setValue = action((sourceValueName: SourceValueNames, newValue: any) => {
|
||||
this.values[sourceValueName] = newValue;
|
||||
// TODO: Run effect here
|
||||
});
|
||||
|
||||
getStatus = (elementName: string) => this.statuses[elementName];
|
||||
setStatus = action((elementName: string, status: Status) => {
|
||||
this.statuses[elementName] = status;
|
||||
});
|
||||
}
|
||||
|
||||
export default CalculationStore;
|
||||
11
src/client/stores/CalculationStore/Effects/autorun.ts
Normal file
11
src/client/stores/CalculationStore/Effects/autorun.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { IAutorunEffect } from 'core/types/effect';
|
||||
|
||||
const autorunEffects: IAutorunEffect[] = [
|
||||
// calculationStore => () => {
|
||||
// if (parseInt(calculationStore.values.price) > 25) {
|
||||
// calculationStore.setValue('one', 100500);
|
||||
// }
|
||||
// }
|
||||
];
|
||||
|
||||
export default autorunEffects;
|
||||
9
src/client/stores/CalculationStore/Effects/computed.js
Normal file
9
src/client/stores/CalculationStore/Effects/computed.js
Normal file
@ -0,0 +1,9 @@
|
||||
const computedEffects = {
|
||||
total() {
|
||||
const one = parseInt(this.values.one || 0);
|
||||
const price = parseInt(this.values.price || 0);
|
||||
return one + price;
|
||||
}
|
||||
};
|
||||
|
||||
export default computedEffects;
|
||||
20
src/client/stores/CalculationStore/Effects/index.js
Normal file
20
src/client/stores/CalculationStore/Effects/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
import CommonStore from '../../CommonStore';
|
||||
import { autorun, reaction, when } from 'mobx';
|
||||
import CalculationStore from '..';
|
||||
import autorunEffects from './autorun';
|
||||
import reactionEffects from './reaction';
|
||||
import whenEffects from './when';
|
||||
|
||||
autorunEffects.map(autorunEffect =>
|
||||
autorun(autorunEffect(CalculationStore, CommonStore))
|
||||
);
|
||||
|
||||
reactionEffects.map(reactionEffectBuilder => {
|
||||
const reactionEffect = reactionEffectBuilder(CalculationStore);
|
||||
return reaction(reactionEffect.expression, reactionEffect.effect);
|
||||
});
|
||||
|
||||
whenEffects.map(whenEffectBuilder => {
|
||||
const whenEffect = whenEffectBuilder(CalculationStore);
|
||||
return when(whenEffect.predicate, whenEffect.effect);
|
||||
});
|
||||
10
src/client/stores/CalculationStore/Effects/reaction.ts
Normal file
10
src/client/stores/CalculationStore/Effects/reaction.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { IReactionEffect } from 'core/types/effect';
|
||||
|
||||
const reactionEffects: IReactionEffect[] = [
|
||||
calculationStore => ({
|
||||
expression: () => calculationStore.values.one,
|
||||
effect: value => console.log(value)
|
||||
})
|
||||
];
|
||||
|
||||
export default reactionEffects;
|
||||
10
src/client/stores/CalculationStore/Effects/when.ts
Normal file
10
src/client/stores/CalculationStore/Effects/when.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { IWhenEffect } from 'core/types/effect';
|
||||
|
||||
const whenEffects: IWhenEffect[] = [
|
||||
calculationStore => ({
|
||||
predicate: () => parseInt(calculationStore.values.one) === 5,
|
||||
effect: () => console.log(calculationStore.values.one)
|
||||
})
|
||||
];
|
||||
|
||||
export default whenEffects;
|
||||
@ -1,3 +1,35 @@
|
||||
import CalculationStore from "./CalculationStore";
|
||||
import assignProperties from 'client/tools/assignProps';
|
||||
import initialStatuses from 'core/config/initialStatuses';
|
||||
import initialValues from 'core/config/initialValues';
|
||||
import { Status } from 'core/types/elements';
|
||||
import { SourceValueNames } from 'core/types/values';
|
||||
import { observable } from 'mobx';
|
||||
import computedEffects from './Effects/computed';
|
||||
|
||||
const CalculationStore = observable(
|
||||
assignProperties(
|
||||
{
|
||||
values: initialValues,
|
||||
statuses: initialStatuses,
|
||||
|
||||
getValue(sourceValueName: SourceValueNames) {
|
||||
return this.values[sourceValueName];
|
||||
},
|
||||
getStatus(elementName: string) {
|
||||
return this.statuses[elementName];
|
||||
},
|
||||
|
||||
setValue(sourceValueName: SourceValueNames, newValue: any) {
|
||||
this.values[sourceValueName] = newValue;
|
||||
},
|
||||
setStatus(elementName: string, status: Status) {
|
||||
this.statuses[elementName] = status;
|
||||
}
|
||||
},
|
||||
computedEffects
|
||||
)
|
||||
);
|
||||
|
||||
console.log(CalculationStore);
|
||||
|
||||
export default CalculationStore;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
class CommonStore {}
|
||||
import { observable } from 'mobx';
|
||||
|
||||
const CommonStore = observable({});
|
||||
|
||||
export default CommonStore;
|
||||
|
||||
12
src/client/stores/index.js
Normal file
12
src/client/stores/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import CalculationStore from './CalculationStore';
|
||||
import CommonStore from './CommonStore';
|
||||
import './CalculationStore/Effects';
|
||||
|
||||
class RootStore {
|
||||
constructor() {
|
||||
this.calculationStore = CalculationStore;
|
||||
this.commonStore = CommonStore;
|
||||
}
|
||||
}
|
||||
|
||||
export default RootStore;
|
||||
@ -1,14 +0,0 @@
|
||||
import CalculationStore from "./CalculationStore";
|
||||
import CommonStore from "client/stores/CommonStore";
|
||||
|
||||
class RootStore {
|
||||
calculationStore: CalculationStore;
|
||||
commonStore: CommonStore;
|
||||
|
||||
constructor() {
|
||||
this.calculationStore = new CalculationStore();
|
||||
this.commonStore = new CommonStore();
|
||||
}
|
||||
}
|
||||
|
||||
export default RootStore;
|
||||
14
src/client/tools/assignProps.js
Normal file
14
src/client/tools/assignProps.js
Normal file
@ -0,0 +1,14 @@
|
||||
function assignProperties(target, ...sources) {
|
||||
sources.forEach(source => {
|
||||
Object.defineProperties(
|
||||
target,
|
||||
Object.keys(source).reduce((descriptors, key) => {
|
||||
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
|
||||
return descriptors;
|
||||
}, {})
|
||||
);
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
export default assignProperties;
|
||||
@ -1,13 +0,0 @@
|
||||
async function runEffects(effects, calculationStore) {
|
||||
if (effects && effects.length > 0) {
|
||||
for (let effect of effects) {
|
||||
try {
|
||||
await effect(calculationStore);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default runEffects;
|
||||
@ -1,3 +1,24 @@
|
||||
import CalculationStore from "client/stores/CalculationStore";
|
||||
import CalculationStore from 'client/stores/CalculationStore';
|
||||
import CommonStore from 'client/stores/CommonStore';
|
||||
import { IReactionPublic, Lambda } from 'mobx';
|
||||
|
||||
export type TEffect = (calculationStore: CalculationStore) => Promise<void>;
|
||||
type TCalculationStore = typeof CalculationStore;
|
||||
type TCommonStore = typeof CommonStore;
|
||||
|
||||
export interface IAutorunEffect {
|
||||
(CalculationStore: TCalculationStore, CommonStore?: TCommonStore): () => void;
|
||||
}
|
||||
|
||||
export interface IReactionEffect {
|
||||
(CalculationStore: TCalculationStore): {
|
||||
expression: (r: IReactionPublic) => any;
|
||||
effect: (arg: any, r: IReactionPublic) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IWhenEffect {
|
||||
(CalculationStore: TCalculationStore): {
|
||||
predicate: () => boolean;
|
||||
effect: Lambda;
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import CalculationStore from "client/stores/CalculationStore";
|
||||
import { SourceValueNames } from "core/types/values";
|
||||
|
||||
export enum Status {
|
||||
Default,
|
||||
Disabled,
|
||||
Disabled
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user