DEV Community

幻魂
幻魂

Posted on

Use concent in react, share state easily and enjoy exact update。


Star Concent if you are interested in it, I will appreciate it greatly.

This is a small article to let more people know why I write the below slogan for concent

a predictable、zero-cost-use、progressive、high performance's enhanced state management solution

We all know the most 2 popular state management redux and mobx, but have we thought that is there another one can just combine with react very very naturally? what I mean is that it is just like a part of a react when we use it, no more complex concept, and write a high performance app easily and the same time.

So I start to build the project concent, its all features is optional but born for different scenes, it will come into your eyes just at a very right timing, all you have to do is just start with a very simple case and

finally you will find what awesome features that concent will offer you.

Let's start

Firstly we write a component HelloWorld, that's a very easy example ready for the most react starter.

class HelloWordComp extends React.Component{
   state = {greeting:'hello world'}
   onChange = (e)=> this.setState({greeting:e.target.value})
   render(){
       return <input value={this.state.greeting} onChange={this.onChange}/>
   }
}

After hook born, we may write it like this

function HelloWorldFn(){
    const [greeting, setter] = React.useState('hello world');
    const onChange = (e)=> setter(e.target.value);
    return <input value={greeting} onChange={onChange}/>
}

Share the state

So what should we do if we want the 2 component ins share the state, react tell us to lift up the state to props, but if there are many many nested component it will become a very big coding trouble.

Now let Concent give you its answer.

  • step 1, configure a module named hello
import { run } from 'concent';

run({
    hello: {
        state: {greeting: 'hello world'}
    }
})
  • step 2, register the target component to concent component
// for class, we can change nothing but only add a register decorator
@register('hello')
class HelloWordComp extends React.Component{...}


// for function, we use hook useConcent
function HelloWorldFn(){
    const {state, setState} = useConcent('hello');
    const onChange = (e)=> setState({greeting:e.target.value});
    return <input value={state.greeting} onChange={onChange}/>
}
  • step 3, initialize them
function App(){
   return (
       <div>
           <HelloWordComp />
           <HelloWordComp />
           <HelloWorldFn />
           <HelloWorldFn />
       </div>
   )
}

now if you type content at any one of their input box, the rest will be trigger re-rendered.

below is the all code pic
code
and let us see the effect
effect
you can also edit the demo here

Hate boring setState?

if you hate writing many setState, you can use sync series api.

function HelloWorldFn(){
    const {state, setState} = useConcent('hello');
    const onChange = (e)=> setState({greeting:e.target.value});
    return <input value={state.greeting} onChange={onChange}/>
}

// change to 
function HelloWorldFn(){
    const {state, sync} = useConcent('hello');
    return <input value={state.greeting} onChange={sync('greeting')}/>
}

Extract logic to reducer

Actually this is not the point I want to talk about, so about reducer you can see this online todo mvc demo

Dependency collection & Exact Update

The key is coming, in fact every render period component is will consume different state, but how should let react know trigger re-render or not?

Concent Component in will collect dependency in every render period, let's show what and how it happened one step by one step.

  • step 1 Let's give more field in the hello module state
run({
    hello: {
        state: {
            greeting: 'hello world',
            name: 'concent',
            addr: 'https://github.com/concentjs/concent',
        }
    }
})
  • step 2

Let's give the component a flag to decide to display name or not.

@register("hello")
class HelloWordComp extends React.Component {
  state = { greeting: "xxxxx", show: true };
  render() {
    console.log('Render HelloWordComp ' + this.props.tag);
    // this.ctx.state === this.state
    const { state, sync, syncBool} = this.ctx;
    return (
      <div>
        <input value={state.greeting} onChange={sync('greeting')} />
        {/* if show is true, input will been render */}
        {state.show ? <input value={state.name} onChange={sync('name')} />:''}
        <button onClick={syncBool('show')}>toggle show</button>
      </div>
    )
  }
}

for function component, we write it like below, it looks very very similar to class component render block.

const iState = ()=>{
  console.log('this will only been called in first render');
  return {show: true};
}

function HelloWorldFn(props) {
  console.log('Render HelloWorldFn ' + props.tag);
  const { state, sync, syncBool} = useConcent({module:'hello', state:iState});
  return (
    <div>
      <input value={state.greeting} onChange={sync('greeting')} />
      {/* if show is true, input will been render */}
      {state.show ? <input value={state.name} onChange={sync('name')} />:''}
      <button onClick={syncBool('show')}>toggle show</button>
    </div>
  )
}
  • step 3

let's initialize the component with different tag

export default function App() {
  return (
    <div>
      <HelloWordComp tag="comp1" />
      <HelloWordComp tag="comp2" />
      <HelloWorldFn tag="fn1" />
      <HelloWorldFn tag="fn2" />
    </div>
  );
}
  • step 4

let's see effect, we type content in any input box will trigger 4 ins re-render

and we toggle the 2 HelloWorldFn ins to let it don't display name, that means they lost the dependency of name state, and then we input the name in the 2 HelloWordComp ins to see what happen in the console.

see it? the 2 HelloWorldFn ins will not been trigger re-render, because
Concent know they lost the dependency of name state, they needn't been

triggered re-rendered again!

edit this example

Dislike module state and private state merged together?

From above example we see this.state(this.ctx.state) in class render block and ctx.state in function block were merged state, if you don't like this way of forming state, you can choose connect params, in fact connect allow you pass multi module names, so you can consume multi module state easily also.

@register({connect:['foo', 'bar']})
class HelloComp extends React.Component{
  render(){
    const {foo, bar} = this.ctx.connectedState;
    const {f1, f2, f3} = foo;
    const {b1, b2, b3} = bar;
  }
}

function HelloCompFn(){ 
    const ctx = useConcent({connect:['foo', 'bar']});
    const {foo, bar} = ctx.connectedState;
    const {f1, f2, f3} = foo;
    const {b1, b2, b3} = bar;
    // or write in one sentence
    // const {connectedState:{foo:{f1,f2,f3}}} = use***
}

Summary

Use concent in react, share state easily and enjoy exact update,it will help you build high performance react app and just let organize code in react way but make it more elegant.

by the way, let's see the dom hierarchy in react-dev-tool, it is very clear and less, no nested Provider or hoc.
dom

⚖️Some online comparative examples

Top comments (0)