State management that tailored for react, it is simple, predictable, progressive and efficient.

Overview

English | 简体中文

⚡️ State management that tailored for react, it is simple, predictable, progressive and efficient.

🐮 Introduction

Concent is an amazing state management tool, supported by a healthy middleware ecosystem and excellent devtools. It is a predictable, zero-invasive, progressive, high-performance react development framework!

Concent encourages simplicity. It saves you the hassle of creating boilerplate code and gives powerful tools with a moderate learning curve, suitable for both experienced and inexperienced developers alike.

Features

💻 Playground

Templates

Install by npx command

$ npx create-react-app my-app --template concent-ts

or clone its source code by git command

$ git clone https://github.com/concentjs/cra-project-concent-ts

A best practise project building by concent & typescript.

$ git clone https://github.com/tnfe/concent-pro        (dev with webpack)
$ git clone https://github.com/tnfe/vite-concent-pro   (dev with vite)

Key features snippet

Online case

👨🏽‍Docs

Visit official website https://concentjs.github.io/concent-doc to learn more.

📦 Quick start

Make sure you have installed nodejs

Install

$ npm i --save concent

or yarn command

$ yarn add concent

Minimal example

See how easy it is to use concent to manage react state.

import { run, register, useConcent } from 'concent';

// 1️⃣ Configure models
run({
  counter: {// declare a moudle named 'counter'
    state: { num: 1, numBig: 100 }, // define state
  },
  // you can also put another module here.
});

// 2️⃣  Now the react component can work with concent
@register('counter') // 👈 decorate your component with register
class DemoCls extends React.Component{
  // commit state to store and broadcast to other refs which also belong to counter module
  inc = ()=> this.setState({num: this.state.num + 1})
  render(){
    // here if read num, it means current ins render dep keys is ['num']
    return <button onClick={this.inc}>{this.state.num}</button>
  }
}
function DemoFn(){
  const { state, setState } = useConcent('counter'); // 👈 call useConcent hook in fn component
  const inc = ()=> setState({num: state.num + 1});
  return <button onClick={inc}>{state.num}</button>
}

Complete example

Move logic to reducer and define computed,watch,lifecycle
try edit this demo 👉 better js demo 👉 better ts demo

import { run, register, useConcent, defWatch } from 'concent';

run({
  counter: {
    state: { num: 1, numBig: 100 },
    computed: {
      numx2: ({ num }) => num * 2, // only num changed will trigger this fn
      numx2plusBig: ({ numBig }, o, f) => f.cuVal.numx2 + numBig // reuse computed reslult
    },
    reducer: {
      initState: () => ({ num: 8, numBig: 800 }),
      add: (payload, moduleState, actionCtx) => ({ num: moduleState.num + 1 }),
      addBig: (p, m, ac) => ({ numBig: m.numBig + 100 }),
      asyncAdd: async (p, m, ac) => {
        await delay(1000);
        return { num: m.num + 1 };
      },
      addSmallAndBig: async (p, m, ac) => {
        // hate string literal? see https://codesandbox.io/s/combine-reducers-better-7u3t9
        await ac.dispatch("add"); 
        await ac.dispatch("addBig");
      }
    },
    watch: {
      numChange: defWatch(({ num }, o) => console.log(`from ${o.num} to ${num}`), {immediate:true}),
      numChangeWithoutImmediate: ({ num }, o) => console.log(`from ${o.num} to ${num}`),
    },
    lifecycle: {
      // loaded: (dispatch) => dispatch("initState"), // [optional] triggered when module loaded
      // initState: async (moduleState) => {/** async logic */ return{num:666}}, // [optional] allow user load state async
      // initStateDone: (dispatch) => dispatch("addSmallAndBig"), // [optional] call any reducer fn after initState done
      mounted: (dispatch) => dispatch("initState"), // [optional] triggered when the first ins of counter module mounted
      willUnmount: (dispatch) => dispatch("initState") // [optional] triggered when the last ins of counter module unmount
    }
  }
});

@register("counter")
class DemoCls extends React.Component {
  render() {
    // mr is short of moduleReducer, now you can call counter module's all reducer fns by mr
    return <button onClick={this.ctx.mr.add}>{this.state.num}</button>;
  }
}

function DemoFn() {
  const { moduleComputed, mr } = useConcent("counter");
  return <button onClick={mr.add}>numx2plusBig: {moduleComputed.numx2plusBig}</button>;
}

🎲 Eco-lib examples

Use with react router

Details see here react-router-concent,expose history,you can call it anywhere in your app to enjoy the imperative navigation jump.

react-router-concent online demo

Use with redux-dev-tool

Details see here concent-plugin-redux-devtool,track your state changing history。 redux-dev-tool

Use with plugin-loading

Details see here concent-plugin-loading,control all your reducer function's loading status easily。

concent-plugin-loading online demo


🐚 Who is using it


tcb-admin

wink

👅 License

concent is released under the MIT License. http://www.opensource.org/licenses/mit-license

Comments
  • 关于 state 和 moduleState 的疑问

    关于 state 和 moduleState 的疑问

    你好,我有一个定义在reducer中的接口返回函数 userLogin,在实例中调用

    image image

    然后分别打印 state 和 moduleState,发现结果不一样,moduleState 会把我的传参合并至 模块状态中,请问他是符合预期的吗? image

    文档中有以下这段话: 【属于某模块的组件,整个模块state将会合并到实例state上,如果组件没有对state做额外的字段扩展的话,state和 moduleState是一样的,如果做了额外的字段扩展(相当于私有的属性,不会被共享出去),那么只能从实例state上取到这些额外的字段值。】

    我是不是触发了【额外的字段扩展】,怎么理解【额外的字段扩展】呢?希望可以详细了解下 state 和 moduleState 的区别

    opened by Handpear 10
  • lazy导致的两次渲染

    lazy导致的两次渲染

    这里有一个bug,github地址是:https://github.com/henryzp/concent-test-demo

    image

    我的疑问是tab切换时,为什么tabContainer组件会render两次。。

    export async function changeTabComplex(
      key: string,
      moduleState: TihaiState,
      actionCtx: IAC,
    ) {
      await actionCtx.dispatch(changeActiveTabKey, key);
      await actionCtx.dispatch(getTableList, { key, current: 1, pageSize: 10 });
      await actionCtx.dispatch(getTableList, { key, current: 2, pageSize: 10 });
    }
    
    export async function changeTab(
      key: string,
      moduleState: TihaiState,
      actionCtx: IAC,
    ) {
      await actionCtx.lazyDispatch(changeTabComplex, key);
    }
    

    触发的是changTab

    期望的结果是render一次。。。

    opened by henryzp 7
  • React Native项目中报错

    React Native项目中报错

    尝试在RN中使用,但是加载编译就失败了

    error: Error: Unable to resolve module `react-dom` from `node_modules\concent\src\core\base\append-dispatcher.js`: react-dom could not be found within the project.
    
    opened by roytan883 7
  • 在当前实例 如何 获取同模块其他实例的state 或 其他模块实例的state 数据呢?

    在当前实例 如何 获取同模块其他实例的state 或 其他模块实例的state 数据呢?

    在当前实例 如何 获取同模块其他实例的state 或 其他模块实例的state 数据呢?

    举个例子:我有个 foo 模块,有 实例 foo1 foo2 foo3 ,和 bar 模块 有实例 bar1 bar2 bar3 现在 在 foo1 实例 获取 foo2 foo3 和 bar1 bar2 的state 数据

    opened by liuxingpluck 6
  • 关于types感应

    关于types感应

    我参考例子 https://codesandbox.io/s/concent-guide-xvcej?file=/src/types/store.d.ts

    在放数据模型的文件夹写了一个concent.d.ts

    import {StateType, ReducerType, ComputedValType, cst, MODULE_VOID, ICtx, IAnyFn} from 'concent';
    
    import appState from "./state";
    import * as appReducer from './reducer';
    import * as appComputed from './computed';
    
    export type AppState = StateType<typeof appState>;
    export type AppReducer = ReducerType<typeof appReducer>;
    export type AppComputed = ComputedValType<typeof appComputed>;
    
    export type RootState = {
        [cst.MODULE_GLOBAL]: {},
        [cst.MODULE_DEFAULT]: {},
        [cst.MODULE_VOID]: {},
        app: AppState,
    }
    
    export type RootRd = {
        app: AppReducer,
    }
    
    export type RootCu = {
        app: AppComputed,
    }
    
    export type ICtxM<P, S, M, Se = {}> = S extends IAnyFn ?
        ICtx<RootState, RootRd, RootCu, P, ReturnType<S>, M, MODULE_VOID, Se> :
        ICtx<RootState, RootRd, RootCu, P, S, M, MODULE_VOID, Se>;
    

    reducer.js有一个函数

    export function makeNewFirstName({name}, state, ctx) {
      console.log(`reducer-23 name: `, name)
      console.log(`reducer-23 state: `, state)
      console.log(`reducer-23 ctx: `, ctx)
      return {firstName: name}
    }
    

    但是我在fn控件上使用的时候

    appCtx.moduleReducer.makeNewFirstName({name: 'AAA-' + Date.now()})
    

    虽然调用成功,但是webstorm还是提示makeNewFirstName函数不识别,没法点函数自己跳过去。

    请问这是有哪里没弄对吗?

    opened by roytan883 6
  • Maximum call stack size exceeded

    Maximum call stack size exceeded

    RangeError: Maximum call stack size exceeded at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14) at Object.set (make-ob-state.js:14)

    opened by komaedaXnagito 6
  • 一些初次使用的反馈

    一些初次使用的反馈

    问题1:this.ctx.state和this.ctx.reducer语义的歧义

    在使用this.ctx.state的时候,拿到的是本模块myModule的state,这个没问题很直观 但是同样的用this.ctx.reducer,拿到的却是全局的reducer,里面有 {"$$default":xxx,"$$global": yyy,"myModule": zzz} this.ctx.reducer为什么不是自己模块的reducer?如果需要访问全局的,弄一个this.ctx.rootReducer不是更直观吗?

    问题2: 通过dispatch传入字符串函数名的用法感觉很古老,不好用: this.ctx.dispatch('doLogin', { userName: xxx, password: yyy }); 连参数都还要手动再把名字写一次{name:xxx, password:yyy}), 最方便不应该是this.ctx.reducer.doLogin(xxx, yyy)这样吗?

    问题3: 目前使用起来最痛苦的是对于模块(因为模块models代码在ui之外的其它目录,仅仅是run的时候注册进去的,ide无感),因为没法ide智能感知代码,state和reducer使用的时候全靠手动写。不像平时用this.state或者hooks里面,都可以直接感知到,敲代码很方便

    opened by roytan883 5
  • 无法与 Next 12.1.0 一起使用

    无法与 Next 12.1.0 一起使用

    版本: "concent": "^2.17.5", "next": "^12.1.0",

    问题描述: Next 开发模式下,启动可以不报错但是无法显示页面,刷新页面后控制台报 document is not defined

    此 demo 可以复现该问题 https://codesandbox.io/s/angry-meitner-bhjl87

    看了一下next 在 ssr 的时候拿到了 concent 挂载的 window 对象, 导致 next 内部判断 isBrowser 为 true

    lQLPDhtBG-hVOpHNAxbNBOKwnXPV2kX8DJwCO1kIVYBJAA_1250_790 0921327D-6762-4F2E-851A-5439585E6467
    opened by Shunjun 4
  • concent 文档修改建议

    concent 文档修改建议

    因为 我们的系统 ,是有很多 tab控件的,每个tab控件在点击生成的时候,会去生成一个界面,常规上来说,这个界面 是 固定基于某种模块生成界面的, 但有些 是特殊 tab界面,是动态 请求一个 react 控件 所属的js,该 界面 可能得模块是 :dynamic

    那么 在 最开始 run的时候,这个 dynamic 模块 是还没有注册的,因为这种 dynamic 模块还是挺多的,估计有二十多个,要是 每个都写好在 store里,就太繁琐啦

    所以 希望最好 提供一个 多次调用 run 的api 功能

    opened by liuxingpluck 4
  • How to get state in setup function

    How to get state in setup function

    I want to update state via setState in my custom function, but previous state is need.

    export default function setup(ctx) {
      const { initState, computed, watch, setState, sync, invoke } = ctx
    
      initState({
        judgedMap: {},
      })
    
    
      const judge = (id, v) => {
        setState({
          judgedMap: {
            ...state.judgedMap,
            [id]: v,
          },
        })
      }
     return {
        judge,
      }
    }
    
    setState((pre) => {
          console.log(pre)
          return pre
    })
    

    is not work!

    opened by AnzerWall 4
  • watch timing

    watch timing

    //state.js
    const state = {
      isLogin: false,
    }
    export default state
    
    //watch.js
    import { history, } from 'react-router-concent';
    const watch = {
      isLoginChange: {
        fn: (newState, oldState, fnCtx) => {
          newState ? history.replace('/') : history.replace('/login')
        },
        depKeys: ["isLogin"],
      },
    }
    export default watch
    
    //run.js
    import {run, configure} from 'concent';
    import {configureRoute} from 'react-router-concent';
    
    import state from './state'
    import watch from './state'
    import * as reducer from './reducer';
    
    run({
      app: {
        state,
        reducer,
        watch,
      }
    })
    configureRoute()
    

    get those error:

    ------------ CC WARNING ------------ 0.chunk.js:245584:20
    retKey[isLogin] item type error 0.chunk.js:245584:20
    
    opened by roytan883 4
  • 使用中感到难受疑惑的点

    使用中感到难受疑惑的点

    实例中的computed及watch由于签名为字符串数组,是否可以支持props呢,在使用setup中又想监听某些props的变化,而这个限制貌似只有将监听通过内置hook写在组件内部,给人一种很不优雅的感觉。本质上都是监听,却又要通过组件内部来触发settings更新state达到效果。 虽然对于模块的状态在实例中监听可能会多次触发,但某些情况下的衍生数据对于写入模块显得有些小题大做。 理想状态: computed监听state、props、refComputed、moduleState、moduleComputed watch监听state、props、refComputed、moduleState、moduleComputed

    enhancement 
    opened by itmanyong 6
Owner
cencentjs
⚡️ State management that tailored for react, it is simple、predictable、progressive and efficient.
cencentjs
Functional reactive programming library for TypeScript and JavaScript

Bacon.js A functional reactive programming lib for TypeScript JavaScript, written in TypeScript. Turns your event spaghetti into clean and declarative

baconjs 6.4k Jan 1, 2023
Recoil is an experimental state management library for React apps. It provides several capabilities that are difficult to achieve with React alone, while being compatible with the newest features of React.

Recoil · Recoil is an experimental set of utilities for state management with React. Please see the website: https://recoiljs.org Installation The Rec

Facebook Experimental 18.2k Jan 8, 2023
A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications on top of TypeScript & JavaScript (ES6, ES7, ES8) 🚀

A progressive Node.js framework for building efficient and scalable server-side applications. Description Nest is a framework for building efficient,

nestjs 53.2k Dec 31, 2022
A progressive Node.js framework for building efficient and scalable server-side applications

A light template to easily setup your backend with a simple jwt authentication with Nest.js and TypeScripit. Powered with Prisma and Hot-reload features

Junior Medehou 3 Feb 25, 2022
A progressive Node.js framework for building efficient and scalable server-side applications

A progressive Node.js framework for building efficient and scalable server-side applications. Description Nest framework TypeScript starter repository

Gustavo Lopes 3 Oct 31, 2022
Get AI newsletter recommendations tailored to developers and startups using ChatGPT prompt.

ChatGPT Newsletter AI newsletter via ChatGPT prompt for developers and startups. Technical Details Building your own custom ChatGPT involves four step

GPTea 33 Jul 31, 2023
The LMS (Life Management System) is a free tool for personal knowledge management and goal management based on Obsidian.md.

README Documentation | 中文帮助 The LMS (Life Management System) is a tool for personal knowledge management and goal management based on Obsidian.md. It

null 27 Dec 21, 2022
A student-made, student-tailored Firefox add-on for Veracross. Provides ease of navigation in Veracross, among with other quality of life features. More features in progress.

Check out the Chrome version! This release is version 1.0.0, so the only feature it has is clickable links to the dropbox from the classpage. Any comm

Webb School CS Club 3 Nov 25, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
A functional and reactive JavaScript framework for predictable code

Cycle.js A functional and reactive JavaScript framework for predictable code Website | Packages | Contribute | Chat | Support Welcome Question Answer

Cycle.js 10.2k Jan 4, 2023
Minimalistic, opinionated, and predictable release automation tool.

Release Minimalistic, opinionated, and predictable release automation tool. General idea Think Prettier but for automated releases: minimalistic, opin

Open Source: JavaScript 173 Dec 18, 2022
Load twemoji with a predictable url.

twemoji-image-functions Cloud Functions which hosts twemoji with a predictable url. Why? You can get any twemoji urls without parsing DOM or text usin

catnose 8 Mar 26, 2022
Mailbox is the predictable states & transitions container for actors.

Mailbox (turns XState Machine into a REAL Actor) Mailbox is an NPM module built on top of the XState machine, by adding a message queue to the XState

Huan (李卓桓) 40 Aug 24, 2022
Twitter-Clone-Nextjs - Twitter Clone Built With React JS, Next JS, Recoil for State Management and Firebase as Backend

Twitter Clone This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn de

Basudev 0 Feb 7, 2022
A scalable, high-performance feature management and progressive experimentation platform

Introduction & Our Philosophy FeatBit is a scalable, high-performance Feature Management and Progressive Experimentation platform. Feature Management

null 345 Jan 1, 2023
🐻 Bear necessities for state management in React

A small, fast and scaleable bearbones state-management solution. Has a comfy api based on hooks, isn't boilerplatey or opinionated, but still just eno

Poimandres 25.5k Jan 9, 2023
🏁 High performance subscription-based form state management for React

You build great forms, but do you know HOW users use your forms? Find out with Form Nerd! Professional analytics from the creator of React Final Form.

Final Form 7.2k Jan 7, 2023
A state management library for React, heavily inspired by vuex

Vuex - But for React! ⚛ If you know vuex, you know it's as close as we get to a perfect state management library. What if we could do this in the reac

Dana Janoskova 103 Sep 8, 2022