New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@airma/react-state

Package Overview
Dependencies
Maintainers
1
Versions
98
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@airma/react-state

the purpose of this project is make useReducer more simplify

  • 15.4.0
  • Source
  • npm
  • Socket score

Version published
Maintainers
1
Created
Source

npm NPM downloads standard

@airma/react-state

@airma/react-state is a simple state management tool for react app with typescript ( javascript ), you can use it to replace useReducer.

@airma/react-state works like that:

import React from 'react';
import {render} from 'react-dom'
import {useModel} from '@airma/react-state';

function App(){
    const {count, increase, decrease} = useModel((state:number)=>{
        const baseState = state >= 0? state : 0;
        return {
            count: baseState,
            increase(){
                return baseState + 1;
            },
            decrease(){
                return baseState - 1;
            }
        };
    },0);
    return (
        <div>
            <button onClick={decrease}>-</button>
            <span>{count}</span>
            <button onClick={increase}>+</button>
        </div>
    );
}

render(<App/>, document.getElementById('root'));

What you need to do is providing a callback which accepts a state (any type) param, and returns an object or array instance. Then choose a hook API like useModel to generate instance by calling the callback. Calling methods from instance can generate a new state, and @airma/react-state will use this new state as a new param to refresh instance by recalling callback again. This callback function is a model in @airma/react-state.

It looks like useReducer, but more free for usage, you can forget dispatch now.

As a standard for state management libraries, global state sharing is so important, that makes @airma/react-state to do a support. A fixed global store object for state sharing is so terrible, that makes your component fixed by this store, and difficult for reusing again. @airma/react provides a new way for relieving the pain of fixed global store, and makes a good typescript support for it.

Use requireModels API can create a global model factory. Pass the factory to RequiredModelProvider Component can persist a new store. We can use useRequiredModel API and global model factory to retrieve a scope model instance from the closest parent RequiredModelProvider Component which contains the factory.

import React,{memo} from 'react';
import {render} from 'react-dom'
import {
  requireModels,
  RequiredModelProvider,
  useRequiredModel
} from '@airma/react-state';

const counter = (count:number = 0) =>{
  return {
    count,
    isNegative: count<0,
    increase:()=>count+1,
    decrease:()=>count-1
  };
};

const modelFactory =  requireModels((factory)=>({
  counter:factory(counter)
}));

const Increase = memo(()=>{
  const {count,increase} = useRequiredModel(modelFactory.counter);
  return (
    <button onClick={increase}>{count}++</button>
  );
});

const Decrease = memo(()=>{
  const {count,decrease} = useRequiredModel(modelFactory.counter);
  return (
    <button onClick={decrease}>{count}--</button>
  );
});

const CountValue = memo(()=>{
  const {count,isNegative} = useRequiredModel(modelFactory.counter);
  return (
    <span style={isNegative?{color:'red'}:undefined}>{count}</span>
  );
});

function Counter({index}:{index:number}){
    return (
      <div>
        counter:{index}
        <RequiredModelProvider value={modelFactory}>
          <div>
            <Decrease/>
            <CountValue/>
            <Increase/>
          </div>
        </RequiredModelProvider>
      </div>
    );
}

render(<Counter index={1}/>, document.getElementById('root'));

As you can see, when click the button in Decrease/Increase component, the CountValue changes. The components with same store factory share state with each other.

If we use Counter component twice in one component, what happens? The two Counter component will share the state?

......

render(
  <div>
    <Counter index={1}/>
    <Counter index={2}/>
  </div>
)

The state about two Counter is different, they have different state, for they have different RequiredModelProvider instance, so the Counter is very easy for reusage, and not sharing state with each other.

If we have another more closer RequiredModelProvider hold a different factory, does useRequiredModel go to a wrong model?

function Counter({index}:{index:number}){
    return (
      <div>
        counter:{index}
        <RequiredModelProvider value={modelFactory}>
          <div>
            <RequiredModelProvider value={otherFactory}>
              <Decrease/>
            </RequiredModelProvider>
            <CountValue/>
            <Increase/>
          </div>
        </RequiredModelProvider>
      </div>
    );
}

The model in Decrease component is still from <RequiredModelProvider value={modelFactory}> store instance, unless the otherFactory is the same object with modelFactory, so the model finding is credible, it will not be affected by another source from a more closer RequiredModelProvider.

If there is no RequiredModelProvider which provides a right model store, what happens?

function Counter({index}:{index:number}){
    return (
      <div>
        counter:{index}
          <div>
            <RequiredModelProvider value={otherFactory}>
              <Decrease/>
            </RequiredModelProvider>
            <CountValue/>
            <Increase/>
          </div>
      </div>
    );
}

As we can see, the components works with their own local state. So, useRequiredModel works a local model from model factory if it can not find a correct model factory.

API

useModel

It is used to create a model instance, which persist a local state, and you can call the instance method to change state, and refresh the instance for next render.

  • model - A callback accepts newest state, and generate a model instance. Call the method from this instance can refresh instance.
  • state - A default state for model refresh. It is optional, and default with undefined.
  • option - An optional config, you can set {refresh: true}, and the instance will be refreshed by the param state change.
type AirModelInstance = Record<string, any>;

type AirReducer<S, T extends AirModelInstance> = (state:S)=>T;

function useModel<S, T extends AirModelInstance, D extends S>(
    model: AirReducer<S, T>,
    state?: D,
    option?: {refresh:boolean}
): T

useTupleModel

It is used to create a tuple array [state, instance], you can call the instance method to change state, and refresh [state, instance] for next render.

  • model - A callback accepts newest state, and generate a model instance. Call the method from this instance can refresh instance.
  • state - A default state for model refresh.
  • onChangeOrOption - An optional param. If it is a callback, useTupleModel goes to a controlled mode, it only accepts state change, and uses this callback to change state outside, you can use useControlledModel to do this too. If it is an option config, you can set {refresh: true}, and the instance will be refreshed by the param state change.
type AirModelInstance = Record<string, any>;

type AirReducer<S, T extends AirModelInstance> = (state:S)=>T;

function useTupleModel<S, T extends AirModelInstance, D extends S>(
    model: AirReducer<S, T>,
    state: D,
    onChangeOrOption?: ((s:S)=>any)|{refresh:boolean}
): [S, T]

With this api, you can split state and methods like:

const [count, {increase, decrease}] = useTupleModel((state:number)=>{
    return {
        increase(){
            return state + 1;
        },
        decrease(){
            return state - 1;
        }
    };
},0);

useControlledModel

It is used to create a model instance, which links to an outside state, call method from instance, will trigger the onChange callback, and send next state outside through this callback, when the outside state changes, the instance refreshes.

  • model - A callback accepts newest state, and generate a model instance. Call the method from this instance can refresh instance.
  • state - A outside state, when it changes, the instance refreshes.
  • onChange - A callback for change outside state.
type AirModelInstance = Record<string, any>;

type AirReducer<S, T extends AirModelInstance> = (state:S)=>T;

function useControlledModel<
  S,
  T extends AirModelInstance,
  D extends S
>(model: AirReducer<S, T>, state: D, onChange: (s: S) => any): T

With this API, your model can be more reusable. For example, you can use it to a conrolled Component with a {value,onChange} props.

// model.ts
export const counter = (count:number)=>{
    return {
        count,
        increase(){
            return count + 1;
        },
        decrease(){
            return count - 1;
        }
    };
};

//......

// component.ts
import {useControlledModel} from '@airma/react-state';
import {counter} from './model';

const MyComp = ({
  value, 
  onChange
  }:{
    value:number, 
    onChange:(v:number)=>void
  })=>{
  const {
    count, 
    increase, 
    decrease
  } = useControlledModel(counter, value, onChange);
  return ...... 
}

function App(){
  const [value, setValue] = useState<number>(0);

  return (
    <div>
      <MyComp value={value} onChange={setValue}/>
      <div>{value}</div>
    </div>
  );
}

useRefreshModel

It is used to create a model instance, which persist a local state and response to a outside state, you can call the instance method to change state, and refresh the instance for next render.

  • model - A callback accepts newest state, and generate a model instance. Call the method from this instance can refresh instance.
  • state - A outside state to refresh instance.
export function useRefreshModel<S, T extends AirModelInstance, D extends S>(
  model: AirReducer<S, T>,
  state: D
): T;

When the param state changes, model instance is refreshed by the new state param.

Suggest using this API with async state mangement library like react-query.

useRefresh

It is used to watch the dependencies change, and call instance method to refresh a model instance with dependencies.

  • method - A method from model instance.
  • params - Params for the method, it is an array for method parameters.
export declare function useRefresh<T extends (...args: any[]) => any>(
  method: T,
  params: Parameters<T>
):void;

When some of the params change, the method is called with these params.

Suggest using this API with async state mangement library like react-query.

requireModels

It is used to create a model factory. You can pass a callback returns a model callback or an object with model callback properties. The callback can accept a factory hold function as param, you should use it to hold your model.

  1. requireModels((factory)=>factory(model,?state))
  2. requireModels((factory)=>({key:factory(model,?state)}))
  • requireFn - A callback accepts a factory function as param, and generate a model factory.
export declare function requireModels<
  T extends Array<any> | ((...args: any) => any) | Record<string, any>
>(requireFn: (factory: HoldCallback) => T): T;

This API is for create a model factory, it can be used with RequiredModelProvider, and make state sharing possible. You can refer to the introduction before for a detail.

RequiredModelProvider

  • value - model factory created by requireModels API.
  • children - react nodes.

return react nodes.

export declare const RequiredModelProvider: FC<{
  value: Array<any> | ((...args: any) => any) | Record<string, any>;
  children: ReactNode;
}>;

This provider is a Provider from React Context, it generates model instances from model factory, and persists them inside the Provider instance.

useRequiredModel

It is used to retrieve a model instance from model instances which are created in a closest RequiredModelProvider with the same seeking factory. If the instance can not be found, it trys to create a local instance for working.

  • model - A callback accepts a state, and generate a model instance.
  • state - a default state for a local model, when the model instance can not be found.

Refer to introduce for details.

Tips

The state property of model object is not necessary, it can be ignored, you can have some properties as you wish.

import React from 'react';
import {render} from 'react-dom'
import {useModel} from '@airma/react-state';

function App(){
    const {count, isNegative, increase, decrease} = useModel((state:number)=>{
    return {
      count: state,
      isNegative: state<0,
      increase(){
        return state + 1;
      },
      decrease(){
        return state - 1;
      }
    };
  },0);

  return (
    <div>
      <div>react state ex 1</div>
      <div>
        <button onClick={decrease}>-</button>
          <span style={isNegative?{color:'red'}:undefined}>{count}</span>
        <button onClick={increase}>+</button>
      </div>
    </div>
  );
}

render(<App/>, document.getElementById('root'));

The model function can return almost every instance extends Record<string|number, any>. Yes, you can write a tuple model yourself if you wish.

import React from 'react';
import {render} from 'react-dom'
import {useModel} from '@airma/react-state';

const toggleModel = (v:boolean):[boolean, ()=>boolean] =>[ v, ()=>!v ];

function App(){
    const [visible, toggle] = useModel(toggleModel,false);
    return (
        <div>
            <button onClick={toggle}>toggle</button>
            <span style={!visible?{display:'none'}:undefined}>
              hellow world
            </span>
        </div>
    );
}

render(<App/>, document.getElementById('root'));

As we can see it is very easy to describe state properties for usage.

Persistent methods

The methods from useModel returns is persistent, so, you can pass it to a memo component directly, it can improve your app performance.

Update data out of model function

Yes, the methods are persistent, but the model function can work with closure data too, when instance method is called, the result will be passed into the newest model function immediately to refresh instance.

Security for reducing state

The API from useTupleModel(without onChange) like useModel, useRefreshModel are secure for state update. The state is outside of react system, so every update from methods is a secure reducing process. If you want to use useState to replace its job, you have to call it like: setState((s)=>s+1).

typescript check

@airma/react-state is a typescript support library, you can use it with typescript for a better experience.

It checks if the input state type is same with the param default state type.

If the method returning type is same with param default state type, and so on.

End

We hope you can enjoy this tool, and help us to enhance it in future.

Keywords

FAQs

Package last updated on 12 Dec 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc