use Concent, release react hook's maximum energy
Tags: react, javascript, concent
Published: 2019-12-22 09:18:43

Hi, my dear friend, I am fantasticsoul, today I want to talk about React's lifecycle method evolution。

If you have any question about `Concent`, you can read the articles below to know more about it.
- [concent online demo updated, challenge vue3 composition api & setup!](https://dev.to/fantasticsoul/concent-online-demo-updated-challenge-vue3-composition-api-setup-g7j)
- [Concent, born for build large scale and high performance react app](https://dev.to/fantasticsoul/concent-born-for-build-large-scale-and-high-performance-react-app-3l01)

Star [Concent](https://github.com/concentjs/concent) if you are interested in it, I will appreciate it greatly.

## how we manage our effect code before hooks born
Before hook is born, we usually put our effect code in `componentDidMount`,`componentDidUpdate`,`componentWillUnmount`, a typical example may like this:
```js
class SomePage extends Component{
    state = { products: [] }
    componentDidMount(){
        api.fetchProducts()
        .then(products=>this.setState({products}))
        .catch(err=> alert(err.message));
    }
}
```
If we have many filter condition to query the product, the code may like this:
```js
class SomePage extends Component{
    state = { products: [], type:'', sex:'', addr:'', keyword:'' }
    
    componentDidMount(){
        this.fetchProducts();
    }
    
    fetchProducts = ()=>{
        const {type, sex, addr, keyword} = this.state;
        api.fetchProducts({type, sex, addr, keyword})
        .then(products=>this.setState({products}))
        .catch(err=> alert(err.message));
    }
    
    changeType = (e)=> this.setState({type:e.currentTarget.value})
    
    changeSex = (e)=> this.setState({sex:e.currentTarget.value})
    
    changeAddr = (e)=> this.setState({addr:e.currentTarget.value})
    
    changeKeyword = (e)=> this.setState({keyword:e.currentTarget.value})
    
    componentDidUpdate(prevProps, prevState){
        const curState = this.state;
        if(
            curState.type!==prevState.type ||
            curState.sex!==prevState.sex || 
            curState.addr!==prevState.addr || 
            curState.keyword!==prevState.keyword 
        ){
            this.fetchProducts();
        }
    }
    
    componentWillUnmount(){
        // here clear up
    }
    
    render(){
        const { type, sex, addr, keyword } = this.state;
        return (
            <div className="conditionArea">
                <select value={type} onChange={this.changeType} >{/**some options here*/}</select>
                <select value={sex} onChange={this.changeSex}>{/**some options here*/}</select>
                <input value={addr} onChange={this.changeAddr} />
                <input value={keyword} onChange={this.changeKeyword} />
            </div>
        );
    }
}
```
And some people don't want so many change*** in code, they will write code like this:
```js
class SomePage extends Component{
    changeKey = (e)=> this.setState({[e.currentTarget.dataset.key]:e.currentTarget.value})
    
    // ignore other logic......

    render(){
        const { type, sex, addr, keyword } = this.state;
        return (
            <div className="conditionArea">
                <select data-key="type" value={type} onChange={this.changeKey} >
                    {/**some options here*/}
                </select>
                <select data-key="sex" value={sex} onChange={this.changeKey}>
                    {/**some options here*/}
                </select>
                <input data-key="addr" value={addr} onChange={this.changeKey} />
                <input data-key="keyword" value={keyword} onChange={this.changeKey} />
            </div>
        );
    }
}
```
And if the component will also been updated while some props changed, code may like this:
```js
class SomePage extends Component{
    static getDerivedStateFromProps (props, state) {
        if (props.tag !== state.tag) return {tag: props.tag}
        return null
    }
}
```
> Because `componentWillReceiveProps` is deprecated, here we use `getDerivedStateFromProps ` instead of `componentWillReceiveProps` 

As a result, we have quickly completed the use of traditional life cycle method, next, we welcome hook to the stage, to see the revolutionary experience it bring us。

## With hook, we can write less code to do more things.
Hook tell us forget the confused `this` in class component, it give you a new way to manage your effect logic, now let rewrite our code with function component.
```js
const FnPage = React.memo(function({ tag:propTag }) {
  const [products, setProducts] = useState([]);
  const [type, setType] = useState("");
  const [sex, setSex] = useState("");
  const [addr, setAddr] = useState("");
  const [keyword, setKeyword] = useState("");
  const [tag, setTag] = useState(propTag);//use propTag as tag's initial value

  const fetchProducts = (type, sex, addr, keyword) =>
    api
      .fetchProducts({ type, sex, addr, keyword })
      .then(products => setProducts(products))
      .catch(err => alert(err.message));

  const changeType = e => setType(e.currentTarget.value);
  const changeSex = e => setSex(e.currentTarget.value);
  const changeAddr = e => setAddr(e.currentTarget.value);
  const changeKeyword = e => setKeyword(e.currentTarget.value);

  // equal componentDidMount&componentDidUpdate
  useEffect(() => {
    fetchProducts(type, sex, addr, keyword);
  }, [type, sex, addr, keyword]);
  // any dependency value change will trigger this callback.
  
  useEffect(()=>{
      return ()=>{// clear up
          // equal componentWillUnmout
      }
  }, []);//put an zero length array, to let this callback only execute one time after first rendered.
  
  useEffect(()=>{
     // equal getDerivedStateFromProps
     if(tag !== propTag)setTag(tag);
  }, [propTag, tag]);

  return (
    <div className="conditionArea">
      <select value={type} onChange={changeType}>
        {/**some options here*/}
      </select>
      <select value={sex} onChange={changeSex}>
        {/**some options here*/}
      </select>
      <input value={addr} onChange={changeAddr} />
      <input value={keyword} onChange={changeKeyword} />
    </div>
  );
});
```
Look at these code above, it is really cool, and the more interesting thing is：a hook can call another hook nested, that means we can put some of our code into a customized hook and reuse it anywhere!
```js
function useMyLogic(propTag){
    
    // we ignore these code 
    // you just know many useState and useEffect were copied to here
    // .......
    
    return {
      type, sex, addr, keyword, tag,
      changeType,changeSex,changeAddr, changeKeyword,
    };
}

const FnPage = React.memo(function({ tag: propTag }) {
  const {
    type, sex, addr, keyword, tag,
    changeType,changeSex,changeAddr, changeKeyword,
   } = useMyLogic(propTag);
  // return your ui
});
```
And if state change process have many steps, we can use `useReducer` to hold these code, then your customized hook will be more clean.

Here is (Dan Abramov's online example)[https://codesandbox.io/s/xzr480k0np]
```js
const initialState = {
  count: 0,
  step: 1,
};

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === 'tick') {
    return { count: count + step, step };
  } else if (action.type === 'step') {
    return { count, step: action.step };
  } else {
    throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: 'tick' });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);

  return (
    <>
      <h1>{count}</h1>
      <input value={step} onChange={e => {
        dispatch({
          type: 'step',
          step: Number(e.target.value)
        });
      }} />
    </>
  );
}
```

Now we see how hook will change our code organization，but is it really enough for us? next let's see how `Concent` change your hook using way!

## With useConcent, release react hook's maximum energy
Before we talk about `useConcent`(a api supplied by Concent), we point out some shortcomings of hooks。

- hook will generate many many temporary closure method no matter you care about it or not, it puts a lot of pressure on GC, and at the same time you should use some method like `useCallback` to avoid some traps or redundant render behavior.
- `useReducer` is just a pure function, your async logic code must been placed in your customized hook
- the hook way is totally different with class lifecycle way, it means function component can not share logic with class component.

Then let's how `Concent` resolve these 3 problems elegantly!

Let's welcome `setup` feature to the stage, it will give you a new way to think and write react component.

Here we define a setup function first.
```js
const setup = ctx => {
  console.log('setup will only been executed one time before first render');
  const fetchProducts = () => {
    const { type, sex, addr, keyword } = ctx.state;
    api.fetchProducts({ type, sex, addr, keyword })
      .then(products => ctx.setState({ products }))
      .catch(err => alert(err.message));
  };

  ctx.effect(() => {
    fetchProducts();
  }, ["type", "sex", "addr", "keyword"]);//here only pass state key
  /** equal as：
    useEffect(() => {
      fetchProducts(type, sex, addr, keyword);
    }, [type, sex, addr, keyword]);
  */

  ctx.effect(() => {
    return () => {// clean up
      // equal as componentWillUnmout
    };
  }, []);
  /** Previous writing in function component：
    useEffect(()=>{
      return ()=>{// clean up
        // do some staff
      }
    }, []);
  */

  // attention here, effectProps is reactive to props value change,effect is reactive to state value change
  ctx.effectProps(() => {
    const curTag = ctx.props.tag;
    if (curTag !== ctx.prevProps.tag) ctx.setState({ tag: curTag });
  }, ["tag"]);//only need props key
  /** Previous writing in function component：
  useEffect(()=>{
    if(tag !== propTag)setTag(tag);
  }, [propTag, tag]);
 */

  return {// these return methods will been collected to ctx.settigns
    fetchProducts,
    changeType: ctx.sync('type'),
  };
};
```
By the way, the more amazing thing about setup is ctx give you another interface like `on`,`computed`,`watch` to enhance your component capability in setup function block, here I give you two online demos.
[a standard concent app](https://codesandbox.io/s/concent-guide-xvcej)
[about computed&watch](https://stackblitz.com/edit/concent-register-hook-comp)

Then we can use the setup function.
```js
import { useConcent } from 'concent';

//state function definition, pass it to useConcent
const iState = () => ({ products:[], type: "", sex: "", addr: "", keyword: "", tag: "" });

const ConcentFnPage = React.memo(function({ tag: propTag }) {
  // useConcent returns ctx，here deconstruct ctx directly
  const { state, settings, sync } = useConcent({ setup, state: iState });
  // attention here we use sync, but if you purchase high performance
  // I suggest you use settings.changeType, or write input like this
  // <input data-ccsync="addr" value={addr} onChange={sync} />
  // because sync('**') will generate a new method in every render period

  const { products, type, sex, addr, keyword, tag } = state;
  const { fetchProducts } = settings;

  // now we can use any method in settings
  return (
    <div className="conditionArea">
      <h1>concent setup compnent</h1>
      <select value={type} onChange={sync('type')}>
        <option value="1">1</option>
        <option value="2">2</option>
      </select>
      <select value={sex} onChange={sync('sex')}>
        <option value="1">male</option>
        <option value="0">female</option>
      </select>
      <input value={addr} onChange={sync('addr')} />
      <input value={keyword} onChange={sync('keyword')} />
      <button onClick={fetchProducts}>refresh</button>
      {products.map((v, idx)=><div key={idx}>name:{v.name} author:{v.author}</div>)}
    </div>
  );
});
```

Setup allows you define static method, that means your component's every render period will not generate so many temporary closure function and call some many use***, let's see the effect below:
![](https://github.com/fantasticsoul/assets/blob/master/img/Dec-07-2019%2017-12-56.gif?raw=true)

Up to now we resolve the first problem: many many temporary closure method generated in every render period. how can we resolve the second problem: separate the async logic code to a single file(we can call it logic file)

Use `invoke` can easily do this, let's see how it works.
```js
//code in logic.js
export async function complexUpdate(type, moduleState, actionCtx){
    await api.updateType(type);
    return { type };
}

// code in setup function
import * as lc from './logic';

const setup = ctx=>{
    //other code ignored....
    return {
        upateType: e=> ctx.invoke(lc.complexUpdate, e.currentTarget.value);
    }
}
```
Is this more cute to write and read? you may see the third param `actionCtx` in function params list, it can allow you combine other function easily.
```js
//code in logic.js
export async function complexUpdateType(type, moduleState, actionCtx){
    await api.updateType(type);
    return { type };
}

export async function complexUpdateSex(sex, moduleState, actionCtx){
    await api.updateSex(sex);
    return { sex };
}

export async function updateTypeAndSex({type, sex}, moduleState, actionCtx){
    await actionCtx.invoke(complexUpdateType, type);
    await actionCtx.invoke(complexUpdateSex, sex);
}

// code in setup function
import * as lc from './logic';

const setup = ctx=>{
    //other code ignored....
    return {
        upateType: e=> {
            // 为了配合这个演示，我们另开两个key存type，sex^_^
            const {tmpType, tmpSex} = ctx.state;
            ctx.invoke(lc.updateTypeAndSex, {type:tmpType, sex:tmpSex}};
        }
    }
}
```

I believe using this way to write code is more readable and maintainable，and you may ask we pass a state definition function to `useConcent`, it is a private state for the function Component, how we promote it to a shared state.

Yeah, if you have this question, you asked the right person, Concent can do it very quickly and easily, with a very less code changes。

### setup 1, config modules
```js
import { useConcent, run } from "concent";
import * as lc from './logic';

run({
    product:{
        state: iState(), 
        // here we can not config lc as reducer
        // but I suggest you do it, then you can call method with ctx.moduleReducer.xxx directly
        reducer: lc,
    }
});
```

### setup 2, pass moudle name to `useConcent`
```js
const ConcentFnModulePage = React.memo(function({ tag: propTag }) {
  // attention, here we don't pass the state to it any more, just flag current component belong to product module, then Concent will inject all product module's state to ctx.state
  const { state, settings, sync } = useConcent({ setup, module:'product' });
  const { products, type, sex, addr, keyword, tag } = state;
  const { fetchProducts } = settings;
    
  // code ignored here, they are 100% the same....
  );
});
```
So now we have 2 components, one is `ConcentFnPage`, the other is `ConcentFnModulePage`, `ConcentFnPage` can still work very well, the little different in code level between these 2 components is `ConcentFnPage` have own private state, `ConcentFnModulePage` mark module as `product`, so all the instances of `ConcentFnModulePage` will share the state! let's initialize 2 instance of `ConcentFnPage` and 2 instance of `ConcentFnModulePage`, and see the effect below:
![](https://github.com/fantasticsoul/assets/blob/master/img/Dec-07-2019%2019-23-44.gif?raw=true)


The problem 2 is resolved, and we only remain the last problem: how can the function component and class component share the logic?

I'm so proud to announce that setup is also can be used on class component, so the last problem is not a problem any more, let me show you the code:
```js
class ConcentFnModuleClass extends React.Component{
  render(){
    const { state, settings, sync } = this.ctx;
    const { products, type, sex, addr, keyword, tag } = state;
    const { fetchProducts, fetchByInfoke } = settings;
  
    // code ignored here, they are 100% the same....
  }
}

export default register({ setup, module:'product' })(ConcentFnModuleClass);
```
let's see the effect, be attention that all the instances shared one module's state:
![](https://github.com/fantasticsoul/assets/blob/master/img/Dec-07-2019%2020-36-21.gif?raw=true)

## end
I know some of you still don't believe what happed above or want to try it self, so here is the online example link, welcome to fork and change.
[https://codesandbox.io/s/nifty-cdn-6g3hh](https://codesandbox.io/s/nifty-cdn-6g3hh)

More details see Concent [git repo](https://github.com/concentjs/concent)
or see Concent[ official doc](https://concentjs.github.io/concent-doc/)

Concent is a predictable、zero-cost-use、progressive、high performance's enhanced state management solution, star it if you are interesting in the way I tell you above, thank you so much.