0.0.0-alpha.1 • Published 5 months ago

react-bag v0.0.0-alpha.1

Weekly downloads
-
License
ISC
Repository
-
Last release
5 months ago

react-lean

The react-lean is a minimalist state management react library as an alternative to Redux and Mobx focused in lean coding and declarative component supporting asynchronous state, rollback handlers, loading flag, publish-subscriber, etc (only compatible with babeljs transpiler).

React state management challenge

As react state management problem is covers by three issues: verbosity, complexity and strictivity.

PROBLEMSFEATURES
VERBOSITYBoilerplate code and over-work coding
COMPLEXITYHuge detail concerns and long learning curve
STRICTIVITYIts high complexity demands large complex apps use cases.

To deal with that problem and maintains the unidirectional data flow approach of React, it demands not just a new tool or library, but a new pattern.

Features

  • lean state management
  • async fetching support
  • rollback/undo feature
  • publisher-subscriber
  • loading screen flag

Patterns

The flux data-flow pattern is an architectural pattern alternative to MVC and MVVM (data binding). Its main difference it its unidirectional approach with claimed better performance in comparison with MVC or MVVM. This react-lean lib follows a data flow that I named just "reflow", as bellow. The tool step is a set of library functions that changes data and data updates the view.

NAMEDIAGRAM
MVCmvc
MVVMmvvm
FLUXflux
CCCaflux

In CCC pattern, a store-component encapsulates a global object as its store, meanwhile its children state-components uses triggers a stage-commander that acts like a special "controller", but not between model and view, but between a state-component and a store-container.

A performance concern may come up with that root rendering propagating throughout multiple components, instead a single render component, but the virtual DOM react algorithm guarantees that only its differences with real DOM will be propagated. Conceptually, it is a overall rendering, but in practice, that algorithm ensure an individual case-by-case rendering component.

The stage-commander is a set of functionalities that covers three kind of component state changings: sync, async and resync. A sync state changing is the most basic state management and in React Hooks is represented by useState, meanwhile, async state changing demands useEffect hook. At last, what I call "resync state changing" is a pub-sub pattern behavior that decouples event trigger to event listener, and allowing subscribing/unsubscribing events.

In Flux/Redux there are a kind of two patterns: CQRS (reducer as handler, actions as commands, selector as queries) and pub-sub pattern (type name as 'topic', mappers as subscribing topics). It is not surprising its huge extra-work with that double pattern implementation. Although, with CCC/React-Lean, we have a kind of unidirectional MVC (as Flux), with its better performance results and, at same time, and optional pub-sub resource. Then, meanwhile flux-redux demands some scale to starting worthy due its high implementation costs and high complex data-flow, react-lean is adaptable with any application scale.

Purpose

The main purpose for a lean state management is promote a leaner and cleaner code implementing with pure functional components (or just declarative components),

  • Class Component: WORSE...
class Counter extends React.Component {    
 constructor(props) { super(props); this.state = { count: 0 }; }

 render() {
   return (
     <div>
       <p>count: {this.state.count} times</p>
       <button onClick={() => this.setState({ count: this.state.count + 1 })}>
         Click
       </button>
     </div>
   );
 }
}
  • Functional Component: BETTER... (with react hooks)
const Counter = props => {
 const [count, setCount] = React.useState(0);

 return <div>
     <p>count: {count}</p>
     <button onClick={() => setCount(count + 1)}>Click</button>
   </div>
}
  • Declarative Component: BEST... (with react-lean tools)
const Counter = props => 
   <div>
     <p>count: {state.count}</p>
     <button onClick={() => setState(x => x.count++)}>Click</button>
   </div>

Usage

The next examples is created with React Native.

Sync state management

a) storing: store{object}, state, setState(function)

Storing states is very simple, it starts with store props in Provider and is changed by a setState function, but not that this setState receives a function parameter as in follow example.

  • App.js
import React from 'react'
import { Provider } from 'react-lean'
import Hello from './hello'

const helloWorldStore = { name:string }

const App = props => 
   <Provider store={helloWorldStore}> 
      <Hello />
   </Provier>
  • hello.js
import React from 'react'
import { View, Text, TextInput } from 'react-native'
import { state, setState } from 'react-lean'

const Hello = props => 
   <View>
      <Text>Hello {state.name || "World"}!</Text>
      <TextInput value={state.name} onChangeText={texting} />
   </View>

const texting = text => setState(s => s.name = text);

b) undoing: letState(function), onBack{object}

The letState allowed to rollback behavior by saving the current state a triggering the back button handler in onBack props on Provider, as exemplified in next example.

  • App.js
import React from 'react'
import { Provider } from 'react-lean'
import Todo from './todo'

const handler = {handler:BackHandler, trigger:'hardwareBackPress'};
const todos = { list:[], text:"", done:false }

export default App = props =>
   <Provider store={todos} onBack={handler} >
      <Todo title="TODO list" />
   </Provider>
  • todo.js
import React from 'react'
import { View, Text } from 'react-native'
import { Input, Icon, Checkbox } from 'react-native-elements'
import { state, setState } from 'react-lean'

const Todo = props => 
   <View>
      <Text>{props.title}</Text>
      <Input value={state.text} onChangeText={texting} /> 
      <Button title="Add" onPress={adding} />
      <TodoList />
   </View>

const TodoList = props => state.list.map((x,i) => <TodoItem key={i} {...x} />)
                   
const TodoItem = props =>
   <View style={{flexDirection:'row'}}>
      <Checkbox checked={props.done} onPress={checking}  />
      <Text>{props.text}</Text>
      <Icon name="delete" onPress={removing(props.key)} />
   </View>

const adding = _ => letState(s =>s.list.push(s.item))
const texting => x => setState(s => s.text = x);
const checking => _ => setState(s => s.done = !s.done)
const removing => k => setState(s => s.list = s.list.filter((x,i) => i != k))

c) parsing: getState(string)

With getState is possible get states with string representation, for example, you could access the object state.user.name as getState("user.name") as next.

const Hello = props => 
   <View>
      <Text>Hello {state.name || "World"}!</Text>
      <Text>Hello {getState("name") || "World"}!</Text>
   </View>

Fetching: inload, onload, unload

a) async reading: inload -> unload

The inload reads a async state receiving a function as argument that, since returns a void, you can avoid imperative coding just adding a getState followed by "||" in function head as bellow.

const Todo = props => getState(listing) ||
   <View>
      <Text>{props.title}</Text>
      <Input value={state.text} onChangeText={texting} /> 
      <Button title="Add" onPress={adding} />
      <TodoList />
   </View>

The listing function examplo concludes with unload function with handle number argument. That argument manages the side effects to avoid endless rendering loop.

const listing = handle => axios
   .get("http://any.api.exemple/todo")
   .then(x => setState(s => s.list = x.data))
   .catch(err => console.error(err))
   .finally(() => unload(handle));

b) async writing: onload -> unload

The onload function wraps an event function mading three thinsgs: set loading flag to false, render the component view and prevent render looping with handle number argument.

const Todo = props => getState(listing) ||
   <View>
      <Text>{props.title}</Text>
      <Input value={state.text} onChangeText={texting} /> 
      <Button title="Add" onPress={adding} />
      <TodoList />
   </View>

const adding = e => axios
   .create(someAxiosConfig)
   .post("/todo", state.item)
   .catch(console.error)
   .finally(() => unload(e))

c) async writing-reading: onload -> inload -> unload

In a very common use case, if you want to start a loading state when requesting to insert an item with API and continue to reloading with listing function, just do that, passing on the handle argument to listing function.

const adding = e => axios
   .create(someAxiosConfig)
   .post("/todo", state.item)
   .then(() => listing(e)); // insted unload(e), pass on to listing(e);

Publish-Subscribe

For complete decouple between event triggers on components and event listeners in functions, the react-lean offer an simple API for publish-subscribe pattern.

a) listening topics

A listener is a function factory that create an listener object,

const howAreYou = listener("how are you?", true,  x => console.log("Great!"));
const andYou = listener("and you?", true, x => console.log("I'm fine too!"));

b) subscribing listeners

Listeners must be subscribed inside of a component, in that case, some imperative coding is needed, letting way the pure functional component for a more conventional way.

const Hello = props => 
{   
   subscriber(howAreYou, true); // true = subscribe, false = unsubscribe
   subscriber(andYou, true); // true = subscribe, false = unsubscribe
    
   return <View>
      <Text>Hello {state.name || "World"}!</Text>
   </View>
}

c) publishing topics with payloads (optional)

In next example, these buttons triggers some topics (without payloads in that example) that will catch by subscribed listeners in effortful way.

const ExampleComponent = props =>
   <View>
      <Button title="how are you?" onPress={_ => publisher("how are you"?)} />
      <Button title="and you?" onPress={_ => publisher("and you?")} />    
   </View>

Reference

// wrapper component conteiner for state management
export class Provider {
   store:any;
   onLoadTimeout:number = 7000;
   onBack:BackButtonEventHandler;
}

// exposed readonly global state
export var state:any;

// loading flag state for handling loading screen
export var loading:boolean;

// all registered by inload function for publisher-subscriber behavior
export var subscriptions:Subscription[];

// The setState is the main change write-only function of react-lean
// ex.: setState(state => state.name = "john");
export function setState(f:changer):void;

// letState is a setState variation to rollbackin states (undo feature)
export function letState(f:changer):void;

// getState a state by string representation
// ex.: getString("user.name") as state.user.name
export function getState(f:changer):void;

// it encapsulates an event function and sets loading flag to 'true'
export function onload(f:charger):void;

// ending loading cycle and, optionally, emit a topic for subscribers
export function unload(handle:number):void;

// loading async fetching states as a managed useEffect 
export function inload(f:changer):void;

// subscribe a listener object to respond a topic event
export function subscriber(topic:string, adding:boolean):void

// publish a trigger topic with optional payload value
export function publisher(topic:string, value:any = null):void

// criate a listener object to be handled by subscriber
export function listener(topic:string, f:action, render:boolean):Listener

// changer is a function type as arguments for effectfull global setState
export interface changer { (model:any):void; }
export interface charger { (model:any):(any) => void; }

/// function type that represent a function
export interface action { ():void; }

// listener class to be used with subscriber
class Listener { topic:string; f:action; render:boolean; }

// that type represents a mapped back button to react-lean
class BackButtonEventHandler { handler:IHandler; trigger:string; }

// that type represents a subscription realized by inload function
class Subscription { topic:string;  render:boolean; action:()=>void; }

// interface for event listener for undo feature
export interface IHandler { addEventListener(handler:any, trigger:string) }
0.1.2

2 years ago

0.0.0-alpha.1

5 months ago

0.1.1

3 years ago

0.0.1

3 years ago