Coder Social home page Coder Social logo

rintoj / statex Goto Github PK

View Code? Open in Web Editor NEW
68.0 8.0 18.0 1.22 MB

StateX is a state management library for modern web applications with unidirectional data flow and immutable uni-state (just like redux)

License: MIT License

JavaScript 6.01% TypeScript 93.99%

statex's Introduction

StateX

StateX is a state management library for modern web applications with unidirectional data flow and immutable uni-state. StateX is a predictable state container just like REDUX. It helps you implement a unidirectional data flow (Flux architecture) in an easy and elegant way without much boilerplate code. The main objective is to provide an implementation that has minimal touch points, while providing all the benefits of Redux. StateX uses rxjs library at its heart, hence promises efficient data flow. StateX is inspired by refluxjs and redux.

To enable seamless integration, StateX has specific APIs for Angular (2 or above) and React.

Note: StateX was originally written for angular - angular-reflux and later modified for react - react-reflux. Both of these packages are now migrated to StateX

Architecture

Flux is an architecture for unidirectional data flow. By forcing the data to flow in a single direction, Flux makes it easy to reason how data-changes will affect the application depending on what actions have been issued. The components themselves may only update application-wide data by executing an action to avoid double maintenance nightmares.

Flow

  • STATE - contains application wide data. Technically this is a single immutable JavaScript object containing every data that an application needs.

  • STORES - contain business logic - how an action should transform the application wide data represented by STATE

  • VIEWS - Views must react to the change in STATE. So an event is triggered when STATE changes, which VIEWS can consume to update itself with the new data.

  • ACTIONS - are dispatched whenever a view needs to change application state. The actions contain payload to help store complete the updates.

Install

npm install statex --save

Usage

StateX works with any modern JavaScript framework, however there are minor differences to how it is implemented for each framework.

This guide includes instructions to integrate StateX with the following combinations of frameworks and features

Framework Language Decorator
Angular TypeScript YES (Recommended)
Angular TypeScript NO
React TypeScript YES (Recommended)
React ES6 YES
React ES6 NO

Use @angular/cli to get started with Angular

Use react-ts to get started with React & TypeScript

Use create-react-app to get started with React & ES6

or use one of the following examples

Examples

5 Simple Steps

1. Define State

To get the best out of TypeScript, declare an interface that defines the structure of the application-state. This is optional if you don't want to use TypeScript.

export interface Todo {
  id?: string
  title?: string
  completed?: boolean
}

export type Filter = 'ALL' | 'ACTIVE' | 'COMPLETED'

export interface AppState {
  todos?: Todo[]
  filter?: Filter
}

2. Define Action

Define actions as classes with the necessary arguments passed on to the constructor. This way we will benefit from the type checking; never again we will miss-spell an action, miss a required parameter or pass a wrong parameter. Remember to extend the action from Action class. This makes your action listenable and dispatch-able.

Using TypeScript

import { Action } from 'statex';

export class AddTodoAction extends Action {
  constructor(public todo: Todo) {
    super()
  }
}

Using ES6

import { Action } from 'statex';

export class AddTodoAction extends Action {
  constructor(todo) {
    super()
    this.todo = todo
  }
}

3. Create Store & Bind Action

Stores are the central part of a Flux architecture. While most of the logics for a store are same, some of the minor details vary from framework to framework.

Angular with Decorator (Recommended)

Use @action decorator to bind a reducer function with an Action. The second parameter to the reducer function (addTodo) is an action (of type AddTodoAction); @action uses this information to bind the correct action. Also remember to extend this class from Store.

import { Injectable } from '@angular/core'
import { action, Store } from 'statex/angular'

@Injectable()
export class TodoStore extends Store {

  @action()
  addTodo(state: AppState, payload: AddTodoAction): AppState {
    return { todos: state.todos.concat([payload.todo]) }
  }
}

Angular without Decorator

import { Injectable } from '@angular/core'

@Injectable()
export class TodoStore {

  constructor() {
    new AddTodoAction(undefined).subscribe(this.addTodo, this)
  }

  addTodo(state: AppState, payload: AddTodoAction): AppState {
    return { todos: state.todos.concat([payload.todo]) }
  }
}

This store will be instantiated by Angular's dependency injection.

React - TypeScript with Decorators (Recommended)

Use @action decorator to bind a reducer function with an Action. The second parameter to the reducer function (addTodo) is an action (of type AddTodoAction); @action uses this information to bind the correct action.

import { AppState } from '../state';
import { AddTodoAction } from '../action';
import { action, store } from 'statex/react';

@store()
export class TodoStore {

  @action()
  addTodo(state: AppState, payload: AddTodoAction): AppState {
    return { todos: state.todos.concat([payload.todo]) }
  }
}

Stores must bind each action with the reducer function at the startup and also must have a singleton instance. Both of these are taken care by @store decorator.

React - ES6 with Decorators

import { AddTodoAction } from '../action';
import { action, store } from 'statex/react';

@store()
export class TodoStore {

  @action(AddTodoAction)
  addTodo(state, payload) {
    return { todos: state.todos.concat([payload.todo]) }
  }
}

@action takes an optional parameter - the action class. Always remember to add @store() to the class.

React - ES6 without Decorators

import { AddTodoAction } from '../action';

export class TodoStore {

  constructor() {
    new AddTodoAction().subscribe(this.addTodo, this)
  }

  addTodo(state, payload) {
    return { todos: state.todos.concat([payload.todo]) }
  }
}

new TodoStore()

Remember to instantiate the store at the end.

4. Dispatch Action

No singleton dispatcher! Instead StateX lets every action act as dispatcher by itself. One less dependency to define, inject and maintain.

new AddTodoAction({ id: 'sd2wde', title: 'Sample task' }).dispatch();

5. Consume Data

Use @data decorator and a selector function (parameter to the decorator) to get updates from application state. The property gets updated only when the value returned by the selector function changes from previous state to the current state. Additionally, just like a map function, you could map the data to another value as you choose.

We may, at times need to derive additional properties from the data, sometimes using complex calculations. Therefore @data can be used with functions as well.

See framework specific implementation.

Angular with Decorator (Recommended)

While creating an Angular component, remember to extend it from DataObserver. It is essential to instruct Angular Compiler to keep ngOnInit and ngOnDestroy life cycle events, which can only be achieved by implementing OnInit and OnDestroy interfaces. DataObserver is responsible for subscribing to state stream when the component is created and for unsubscribing when the component is destroyed. The selector function must also be kept as external functions accessible to outside modules, therefore add export to every selector function.

import { data, DataObserver } from 'statex/angular';

export const selectState = (state: AppState) => state
export const selectTodos = (state: AppState) => state.todos
export const computeHasTodos = (state: AppState) => state.todos && state.todos.length > 0

@Component({
    ....
})
export class TodoListComponent extends DataObserver {

  @data(selectTodos)     // mapping a direct value from state
  todos: Todo[];

  @data(computeHasTodos) // mapping a different value from state
  hasTodos: boolean;

  @data(selectState)     // works with functions to allow complex calculations
  todosDidChange(state: AppState) {
    // you logic here
  }

}

Angular without Decorator

StateX can also be used without decorators as shown below, however this is not a recommended way. At most care must be taken to unsubscribe all the events on destroy.

import { State } from 'statex'
import { Subscription } from 'rxjs/Subscription'

export const selectTodos = (state: AppState) => state.todos
export const selectFilter = (state: AppState) => state.filter

@Component({
  ...
})
export class AppComponent implements OnInit, OnDestroy {

  todos: Todo[]
  filter: Filter
  subscriptions: Subscription[] = []

  ngOnInit() {
    this.subscriptions.push(
      State.select(selectTodos).subscribe(todos => this.todos = todos)
    )
    this.subscriptions.push(
      State.select(selectFilter).subscribe(filter => this.filter = filter)
    )
  }

  ngOnDestroy() {
    this.subscriptions.forEach(subscription => subscription.unsubscribe())
    this.subscriptions = []
  }
}

React - TypeScript & Decorators (Recommended)

Create Props class, add properties decorated with @data, and finally inject the Props to the container using @inject decorator.

import * as React from 'react'
import { data, inject } from 'statex/react'

class Props {
  @data((state: AppState) => state.todos)
  todos: Todo[]

  @data((state: AppState) => state.todos && state.todos.length > 0)
  hasTodos: boolean
}

interface State { }

@inject(Props)
export class TodoListComponent extends React.Component<Props, State> {

  render() {
    const todos = this.props.todos.map(
      todo => <li key={todo.id}>{todo.text}</li>
    )
    return <div> { this.props.hasTodos && <ul> {todos} </ul> } </div>
  }
}

React - ES6 & Decorators

Create Props class, add properties decorated with @data, and finally inject the Props to the container using @inject decorator.

import * as React from 'react'
import { inject } from 'statex/react'

@inject({
  todos: state => state.todos,
  hasTodos: state => state.todos && state.todos.length > 0
})
export class TodoListComponent extends React.Component {

  render() {
    const todos = this.props.todos.map(
      todo => <li key={todo.id}>{todo.text}</li>
    )
    return <div> { this.props.hasTodos && <ul> {todos} </ul> } </div>
  }
}

React - ES6 and without Decorator

import React from 'react'
import { State } from 'statex';

export class TodoListComponent extends React.Component {

  subscriptions = [];

  constructor(props) {
    super(props)
    this.state = {
      todos: [],
      hasTodos: false
    }
  }

  componentDidMount() {
    this.subscriptions.push(
      State.select(selectTodos).subscribe(todos => this.setState({ todos }))
    )
    this.subscriptions.push(
      State.select(computeHasTodos).subscribe(hasTodos => this.setState({ hasTodos }))
    )
  }

  componentWillUnmount() {
    this.subscriptions.forEach(subscription => subscription.unsubscribe())
    this.subscriptions = []
  }

  render() {
    const todos = this.state.todos.map(
      todo => <li key={todo.id}>{todo.text}</li>
    )
    return <div> { this.state.hasTodos && <ul> {todos} </ul> } </div>
  }

}

Organizing Stores

Angular

  • Create STORES array and a class Stores (again injectable) to maintain stores.
import { Injectable } from '@angular/core';
import { TodoStore } from './todo.store';

@Injectable()
export class Stores {
  constructor( private todoStore: TodoStore) { }
}

export const STORES = [
  Stores,
  TodoStore
];

When you create a new store remember to inject to the Stores's constructor and add it to the STORES array.

  • Add STORES to the providers in app.module.ts.
import { STORES } from './store/todo.store';

@NgModule({
  providers: [
    ...STORES
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • And finally, inject Stores into your root component (app.component.ts)
@Component({
  ....
})
export class AppComponent {
  constructor(private stores: Stores) { }
}

React - TypeScript / ES6

Create index.ts in stores folder and import all stores. You must do this every store you create.

import './todo-store'

Import stores into application (app.tsx), so that application is aware of the stores. This has to be done once at the beginning of the setup. Next time you create a new store, it must only be added to store/index.ts

import './stores'
...
export class AppComponent extends React.Component<{}, {}> {
  ...
}

Reducer Functions & Async Tasks

Reducer functions can return either of the following

  • A portion of the application state as plain object
@action()
add(state: AppState, payload: AddTodoAction): AppState {
  return {
    todos: (state.todos || []).concat(payload.todo)
  }
}
  • A port of the application state using async and await (RECOMMENDED)
@action()
async add(state: AppState, payload: AddTodoAction): Promise<AppState> {
  const response = await asyncTask();
  return (currentState: AppState) => ({
    todos: (currentState.todos || []).concat(response.todo)
  })
}

Please note: the state might change by the time the asyncTask() is completed. So it is recommended to return a function that will receive the current state as shown above. Do all calculations based on currentState instead of state

  • A portion of the application state wrapped in Promise, if it needs to perform an async task.
@action()
add(state: AppState, payload: AddTodoAction): Promise<AppState> {
  return new Promise((resolve, reject) => {
    asyncTask().then(() => {
      resolve((currentState: AppState) => ({
        todos: (currentState.todos || []).concat(payload.todo)
      }))
    })
  })
}
  • A portion of the application state wrapped in Observables, if the application state needs update multiple times over a period of time, all when handling an action. For example, you have to show loader before starting the process, and hide loader after you have done processing, you may use this.
import { Observable } from 'rxjs/Observable'
import { Observer } from 'rxjs/Observer'

@action()
add(state: AppState, payload: AddTodoAction): Observable<AppState> {
  return Observable.create((observer: Observer<AppState>) => {
    observer.next({ showLoader: true })
    asyncTask().then(() => {
      observer.next((currentState: AppState) => ({
        todos: (currentState.todos || []).concat(payload.todo),
        showLoader: false
      }))
      observer.complete()
    })
  })
}

Initialize State & Enable HotLoad

You can initialize the app state using the following code.

import { initialize } from 'statex'

initialize(INITIAL_STATE, {

  // set hot load to true to save and resume state between reloads
  hotLoad: process.env.NODE_ENV !== 'production',

  // show reducer errors; turn this off for production builds for performance reasons
  showError: process.env.NODE_ENV !== 'production',
  
  // (electron only option) see electron section for details 
  cache: '.my-app-cache.json'
  
  // set this to uniquely identify your app in a common domain; in effect only if "cache" is not defined"
  domain: 'my-app'
})

If you set hotLoad to true, every change to the state is preserved in localStorage and re-initialized upon refresh. If a state exists in localStorage INITIAL_STATE will be ignored. This is useful for development builds because developers can return to the same screen after every refresh. Remember that the screens must react to state (reactive UI) in-order to achieve this. domain is an optional string to uniquely identify your application. showError, if set to true, displays console errors when the actions are rejected.

Angular

...
import { INITIAL_STATE } from './../state'
import { environment } from '../environments/environment'
import { initialize } from 'statex/angular'

initialize(INITIAL_STATE, {
  hotLoad: !environment.production,
  showErro: !environment.production,
  domain: 'my-app'
})

@NgModule({
  ....
  bootstrap: [AppComponent]
})
export class AppModule { }

React

...
import { INITIAL_STATE } from './../state'
import { initialize } from 'statex/react'

initialize(INITIAL_STATE, {
  hotLoad: process.env.NODE_ENV !== 'production',
  showError: process.env.NODE_ENV !== 'production',
  domain: 'my-app'
})

import { AppComponent } from './app'
ReactDOM.render(<AppComponent />, document.getElementById('root'))

Electron

If you are building an electron app (using Angular or React), you can overcome the size limitation of localStorage using cache option. Set this property to a valid file name so that Statex will save the state in a local file instead of local storage. Remember to import initialize function from statex/electron.

...
import * as os from 'os'
import { INITIAL_STATE } from './../state'
import { initialize } from 'statex/electron'

initialize(INITIAL_STATE, {
  hotLoad: process.env.NODE_ENV !== 'production',
  showError: process.env.NODE_ENV !== 'production',
  cache: path.resolve(os.tmpdir(), 'my-app-cache.json')
})

Immutable Application State

To take best use of React's and Angular's change detection strategies we need to ensure that the state is indeed immutable. This module uses seamless-immutable for immutability.

Since application state is immutable, the reducer functions will not be able to update state directly; any attempt to update the state will result in error.ย Therefore a reducer function should either return a portion of the state that needs change (recommended) or a new application state wrapped in ReplaceableState, instead.

@action()
selectTodo(state: AppState, payload: SelectTodoAction): AppState {
  // merge with the existing state
  return {
    selectedTodo: payload.todo
  }
}

@action()
resetTodos(state: AppState, payload: ResetTodosAction): AppState {
  // replace the current state completely with the new one
  return new ReplaceableState({
    todos: [],
    selectedTodo: undefined
  })
}

Migrating

Migrating from angular-reflux

  • Replace angular-reflux package with statex
npm uninstall react-reflux --save
npm install statex --save
  • Replace imports from angular-reflux to statex/angular
// from
import { data, DataObserver } from 'angular-reflux'

// to
import { data, DataObserver } from 'statex/angular'
  • Change @BindAction() to @action()
// from
@BindAction()
addTodo(state: AppState, action: AddTodoAction): AppState {
  ...
}

// to
@action()
addTodo(state: AppState, action: AddTodoAction): AppState {
  ...
}
  • Change @BindData() to @data()
// from
@Component({...})
export class AppComponent extends DataObserver {
  @BindData(selectTodos)
  todos: Todo[]
}

// to
@Component({...})
export class AppComponent extends DataObserver {
  @data(selectTodos)
  todos: Todo[]
}

Migrating from react-reflux

To migrate to StateX replace the package react-reflux with statex.

npm uninstall react-reflux --save
npm install statex --save

And change every import statement from react-reflux to statex/react. That's all

// from
import { data, inject } from 'react-reflux'

// to
import { data, inject } from 'statex/react'

About

Hope StateX is helpful to you. Please make sure to checkout my other projects and articles. Enjoy coding!

Contributing

Contributions are very welcome! Just send a pull request. Feel free to contact me or checkout my GitHub page.

Author

Rinto Jose (rintoj)

Follow me: GitHub | Facebook | Twitter | Google+ | Youtube

Versions

Check CHANGELOG

License

The MIT License (MIT)

Copyright (c) 2017 Rinto Jose (rintoj)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

statex's People

Contributors

a139383 avatar aswin-s avatar dw8reaper avatar rintoj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

statex's Issues

How to Use Production Version of Seamless-Immutable

The seamless-immutable library is slowing my application. It has a production version that should be much faster (at the expense of safety). I've tried building my Angular application using the -prod flag, but it's still using the development version of seamless. I believe to use the production version one needs to import it explicitly: rtfeldman/seamless-immutable#50

Can we change statex to use the production version of seamless at the appropriate times or add a configuration to not use seamless?

Support for Angular 6?

I've been having issues with my tests after upgrading my project to Angular 6 and rxjs 6. I'm just wondering if this lib has been tested with those newer frameworks.

Can't seem to use classes for items in the state

@rintoj

I have the following state:

export interface AppState {
  batchId?: string;
  events?: EventLogModel[];
  isLoading?: boolean;
  hasError?: boolean;
}

events is an array of EventLogModel objects:

export class EventLogModel {
  private id: string;
  private event_type: string;
  private occurred_on: string;
  private producedDomain: string;

  constructor(values: Object = {}) {
    Object.assign(this, values);
  }

  public getId(): string {
    return this.id;
  }

  public getEventType(): string {
    return this.event_type;
  }

  public getOccurredOn(): string {
    return this.occurred_on;
  }

  public getProducedDomain(): string {
    return this.producedDomain;
  }

  /**
   * If the event_type contains the word "reject", then mark it as an error.
   * This is used for the ListView status directive
   * @return {string} "error" or "success"
   */
  public determineStatus(): string {
    return this.event_type.toLowerCase().includes('reject') ? 'error' : 'success';
  }

  /**
   * If the eventConsumers array is greater than 0, then allow the ListView
   * to be expanded.
   * @return {boolean} true or false depending on the length of eventConsumers
   */
  public determineExpandable(): boolean {
    return this.eventConsumers.length > 0;
  }
}

After going through the action:

  @action()
  public searchBatches(state: AppState, payload: SearchBatchesAction): Observable<AppState> {
    return Observable.create((observer: Observer<AppState>) => {
      observer.next({ isLoading: true, hasError: false });

      this.eventListService.fetchEvents(payload.bId).subscribe(
        (e: Array<EventLogModel>) => {
          observer.next({
            batchId: payload.bId,
            events: e,
            isLoading: false
          });
          observer.complete();
        },
        error => {
          observer.next({
            events: [],
            isLoading: false,
            hasError: true
          });
          observer.complete();
        }
      );
    });
  }

and after being in the component:

export const eventList = (state: AppState) => state.events;

@Component({
  selector: 'pi-event-timeline',
  templateUrl: './event-timeline.component.html',
  styleUrls: ['./event-timeline.component.scss']
})
export class EventTimelineComponent extends DataObserver implements OnInit, OnDestroy {
  @data(eventList) public events: Array<EventLogModel>;

   public testEventsFunc(): void {
        console.log(`${this.events[0] instanceof EventLogModel}`); //this will print "false"
        for (let event of events) {
            console.log(`${event.getId()}`); //Will fail
        }
   }
}

I'm certain it's something with statex or something I'm doing wrong with statex because I'm using the same code for the service in another application; I'm in the process of using two apps that are identical in functionality: one with statex and the other with no real design pattern. This issue only occurs in the former example.

All of this works if I rewrite the EventLogModel to be an interface instead--but I shouldn't need to.

Am I doing something wrong?

Devtools

Hey, are there any devtools for StateX?

Module not found error when running unit tests

I get the below error when running unit tests in my application:

$ ng test --browsers=ChromeHeadless --single-run=true --progress=false --sourcemaps=false
01 03 2018 12:45:19.022:ERROR [karma]: Error: Module not found: Error: Can't resolve 'fs' in '/Users/user/code/myProject/node_modules/statex/dist/core'

This doesn't seem to affect ng serve; if I'm using [email protected], the issue isn't present.

Edit: I think this is because when I was going through my project and updating our dependencies, yarn says there is a 1.1.5 version available. I see you have a version 1.1.5, but it's not in master--so maybe that has something to do with it? [email protected] works fine.

Nested State Replacement

Currently there is no nested state for the reducer functions/methods, like the createReducer function in Redux Framework. Updating a single subpath in action methods can be tedious and prone to errors especially in case of state structure refactors. Is there a plan for a nested state support?

How do you use an Observable's error handling?

@rintoj

I have something like this:

  @action()
  public searchBatches(state: AppState, payload: SearchBatchesAction): Observable<AppState> {
    return Observable.create((observer: Observer<AppState>) => {
      observer.next({ hasError: false, isLoading: true });

      this.eventListService.fetchEvents(payload.batchId).subscribe(
        (e: Array<EventLogModel>) => {
          observer.next({
            batchId: payload.batchId,
            events: e || [],
            isLoading: false
          });
        },
        error => {
          console.log('errored out');
          observer.next({
            events: [],
            isLoading: false,
            hasError: true
          });
        }
      );
      observer.complete();
    });
  }

but if there is an error, it never goes inside the error block. How am I supposed to do this?

[Question] Multiple States or deep nested States

By design there is only one state.

If i use nested states i often get TS errors for possible undefined values. To workaround it i use if checks but that's repetive way.

I didn't found a way to have multiple states like:

@store()
export class FooStore {
  @action()
  foo(state:FooState, action: FooAction): FooState { ... }

  @action()
  bar(state:BarState, action: BarAction): BarState { ... }
}

Any suggestions?

Selectors Broken in *ngFor Components

I have a component with a simple state selector. When I instantiate that component using an *ngFor directive the selector only works in the first created component and not in any of the rest.

A test case is attached. The test case is a variation on the example TODO list app. I added a state variable called global that is referenced both on the list component and on each of it's children. The children are instantiated using an *ngFor and the selector doesn't work in most of the children.

statex_selector_issue.zip

Unit test examples

Would it be possible to see todo-ng-ts with some unit testing? I'd be interested to see how statex can improve unit testing.

Refrain from using browser specific tech

React is used in quite a few places that do not happen to be in a browser. Remove references and/or detect and swap out when unavailable, anything that directly relies on browser specific things. In this case, specifically, localStorage.

I would like to use statex/react stuff both in nodejs for react-blessed as well as with things like react-native

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.