Modx is a lightweight library to help developer use redux in a simple way.
Just create a model
, you can use it in global redux state
,
Class Component
and Function Component
, also easy to write
Unit Test
for model
with jest
.
$ npm install @olajs/modx --save
$ yarn add @olajs/modx
- Create model
- Simple use
- Using in React with react-redux
- Using in Class Component
- Using in Function Component
- Using async logic
- useModel & withModel
- shareModel
// modelA.ts
import { createModel } from '@olajs/modx';
export default createModel({
namespace: 'modelA',
state: {
counter: 0,
},
reducers: {
plus: (state) => ({ counter: state.counter + 1 }),
minus: (state) => ({ counter: state.counter - 1 }),
},
});
// store.ts
import { createStore } from '@olajs/modx';
import modelA from './modelA';
const store = createStore({}, [modelA]);
const { namespace } = modelA;
console.log(store.getState()[namespace]);
// { counter: 0 }
store.dispatch({ type: `${namespace}/plus` });
console.log(store.getState()[namespace]);
// { counter: 1 }
store.dispatch({ type: `${namespace}/plus` });
console.log(store.getState()[namespace]);
// { counter: 2 }
store.dispatch({ type: `${namespace}/minus` });
console.log(store.getState()[namespace]);
// { counter: 1 }
// App.tsx
import React from 'react';
import { useGlobalModel } from '@olajs/modx';
import modelA from './modelA';
function App() {
const { state, dispatchers } = useGlobalModel(modelA);
return (
<div>
{state.counter}
<br />
<button onClick={() => dispatchers.plus()}>plus</button>
<br />
<button onClick={() => dispatchers.minus()}>minus</button>
</div>
);
}
export default App;
// main.tsx
import React from 'react';
import ReactDom from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from '@olajs/modx';
import modelA from './modelA';
import App from './App';
const store = createStore({}, [modelA], { devTools: true });
ReactDom.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementByid('app'),
);
// withSingleModel.tsx
import React from 'react';
import { withSingleModel, UseModelResult } from '@olajs/modx';
import modelA from './modelA';
type Props = UseModelResult<typeof modelA>;
class WithSingleModel extends React.PureComponent<Props, any> {
render() {
const { state, dispatchers } = this.props.singleModel;
return (
<div>
{state.counter}
<br />
<button onClick={dispatchers.plus}>plus</button>
<br />
<button onClick={dispatchers.minus}>minus</button>
</div>
);
}
}
export default withSingleModel(modelA)(WithSingleModel);
// useSingleModel.tsx
import React from 'react';
import { useSingleModel } from '@olajs/modx';
import modelA from './modelA';
function UseSingleModel() {
const { state, dispatchers } = useSingleModel(modelA);
return (
<div>
{state.counter}
<br />
<button onClick={() => dispatchers.plus()}>plus</button>
<br />
<button onClick={() => dispatchers.minus()}>minus</button>
</div>
);
}
export default UseSingleModel;
// modelB.ts
import { createModel } from '@olajs/modx';
export default createModel({
namespace: 'modelB',
state: {
counter: 0,
},
reducers: {
plus: (state) => ({ counter: state.counter + 1 }),
minus: (state) => ({ counter: state.counter - 1 }),
},
effects: {
plusAsync(timeout: number) {
const { prevState } = this;
console.log(prevState); // { counter: xxx }
setTimeout(() => {
this.plus();
}, timeout);
},
minusAsync(timeout: number) {
const { prevState } = this;
console.log(prevState); // { counter: xxx }
setTimeout(() => {
this.minus();
}, timeout);
},
},
});
// useSingleModelB.tsx
import React from 'react';
import { useSingleModel } from '@olajs/modx';
import modelB from './modelB';
function useSingleModelB() {
const { state, dispatchers } = useSingleModel(modelB);
return (
<div>
{state.counter}
<br />
<button onClick={() => dispatchers.plusAsync(3000)}>plus</button>
<br />
<button onClick={() => dispatchers.minusAsync(3000)}>minus</button>
</div>
);
}
export default useSingleModelB;
Simple way of useGlobalModel/withGlobalModel
(global state) and useSingleModel/withSingleModel
(component state) methods.
When use useModel
hooks (since modx@2.1.2), modx
will use model
in global state first, if not exists, modx
will create a local state for it.
import React from 'react';
import { useModel } from '@olajs/modx';
import modelA from './modelA'; // global state
import modelB from './modelB'; // local state
function UseModelExample() {
const { state: stateA, dispatchers: dispatchersA } = useModel(modelA);
const { state: stateB, dispatchers: dispatchersB } = useModel(modelB);
return (
<div>
counterA: {stateA.counter}, counterB: {stateB.counter}
<br />
<button onClick={() => dispatchersA.plusAsync(3000)}>plusA</button>
<br />
<button onClick={() => dispatchersA.minusAsync(3000)}>minusA</button>
<br />
<button onClick={() => dispatchersB.plusAsync(3000)}>plusB</button>
<br />
<button onClick={() => dispatchersB.minusAsync(3000)}>minusB</button>
</div>
);
}
export default UseModelExample;
since @olajs/modx@3.x
In some situation, more than one component will share some states which are not in global state.
Then you can use useShareModel/withShareModel
,shareModel
will create each modelConfig
file
per store. So use useShareModel/withShareModel
in different components with same modelConfig
file
will get same store object, states and dispatchers of this store will be shared in these components.
For example:
-
Comp1
andComp2
share states -
selector
function used to prevent useless re-render
import React from 'react';
import { useShareModel } from '@olajs/modx';
import model from './modelA';
function Comp1() {
// only re-render when 'counter' changed
const selector = (state) => ({ counter: state.counter });
const { state } = useShareModel(model, selector);
return <div> {state.counter}</div>;
}
function Comp2() {
// only re-render when 'counting' changed
const selector = (state) => ({ counting: state.counting });
const { state } = useShareModel(model, selector);
console.log('share Comp3 rendered');
return <div>{state.counting}</div>;
}
See /example directory to find more usage