Where to keep the state in 2020?

State managers overview

JavaScript community

Volodymyr Vyshko

Agenda

Redux

Is it dead? Should we use it?

State management in general

State managers overview

Choosing the right one for your application

JavaScript community

State management

JavaScript community

In general

User

Scene

Router

 State

What is the state?

State

Allocated objects

All the variables

File descriptors

Network sockets

It is basically all of the information that represents what is currently happening in the application.

What is the state management?

What is your opinion?

How data gets into/out of a store

How business logic interacts with the UI

Application architecture

How information flows through a system

Data persistence

Programming paradigms

 networking and caching

Presentation behavior

It's a very vague term! 

lets call it

Software engineering

When would I need to use a state management tool?

You have reasonable amounts of data changing over time

Many people working on the project, you want a consistent way to work with state

You don't want to reinvent the wheel again by your own

You want to use some extensions for easier debugging

If you are curious whether you should add an 'X' state manager lib to your app, you probably don't need it!

Use the basic features of your React/Vue/Angular/Etc ecosystem until you have problems

If your state management doesn't need all the Redux features, use useState, useReducer and useContext. If your state management needs Redux as one global state container with middleware, introduce Redux

useReducer + useContext 

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

May cover all your needs!

Redux

come out 2015

Flux

Flux is not a state manager, but an architecture/pattern

Flux architecture

Flux is a fancy name for the observer pattern modified a little bit to fit React

State in FP

In functional programming, functions are not supposed to modify the external state. All that they are supposed to do - return a value based solely on their input.

const createStore = (reducer, listeners = []) => {
	let state;
	const getState = () => state;

	const dispatch = (action) => {
		if (typeof action === 'function') {
			action(dispatch);
		} else {
			state = reducer(state, action);
			listeners.forEach(listener => listener());
		}
	};

	const subscribe = (listener) => {
		listeners.push(listener);
		return () => {
			listeners = listeners.filter(l => l !== listener);
		};
	};

	dispatch({});

	return { getState, dispatch, subscribe };
};

Observer

Flux

 "one-way" data flow

Redux

immutability,

reducer function composition

Multiple classes

Multiple stores

Single
store, but multiple reducers

54.5k

Redux Google trend worldwide for the last year

October 2019

October 2020

25%

50%

75%

100%

25%

Why the popularity decreased?

Learning curve

Boilerplate code

Performance

Central store

No built-in way to handle side-effects

Comparing to other libraries

State of JS 2019

Should we stop using Redux?

Is it dead ?

Use react features/hooks instead ?

Hi, I'm a Redux maintainer.

No, Context does not replace Redux, although there is some overlap. And yes, Redux will continue to be very relevant and useful for a long time. It's currently used by 50% of React apps, and lots of folks are continuing to learn it on a daily basis.

Mark Erikson

TL;DR

Is Redux dead, dying, deprecated, or about to be replaced?

No.

Redux good points

Consistent architectural pattern

Debugging capabilities

Middleware

Addons and extensibility

Cross-platform and cross-framework usage

Better performance than Conext

import { createSlice } from '@reduxjs/toolkit'

const initialState = { value: 0 }

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment(state) {
      state.value++
    },
    decrement(state) {
      state.value--
    },
    incrementByAmount(state, action) {
      state.value += action.payload
    },
  },
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

I would like to amend this: don’t use Redux until you have problems with vanilla React

Dan abramov

Don’t use Redux unless you *tried* local component state and were dissatisfied.

You don’t need Redux if your data never changes. The whole point of it is managing changes.

If your reducer looks “boring” don’t use Redux. It just adds unnecessary indirection in this case.

Don't use Redux if you wasting much CPU time or RAM resources on maintaining the immutability

Don't use redux if your application using it mainly for caching fetched resources

Don't use redux if your app mainly consists of complex forms

Don't use Redux if your containers are mostly independent

Should I keep everything in Redux store?

React state

Redux

When should I use Redux?

Use Redux when

You need a single source of truth

You want to maintain an undo history

You need a serializable state/actions

Travel between the state history in development

Provide alternative UI without disturbing too much of the business logic

Redux = proven by time, gives you a pattern

Still good in many cases

Not a silver bullet, but

MobX

come out 2015 (later then Redux)

Mutable state

OOP Style

Unopinionated

MobX

NPM downloads

22.6k

import { makeObservable, observable, action } from "mobx"

class Counter {
    count = 0;

    constructor() {
        makeObservable(this, {
            count: observable,
            increase: action,
            decrease: action
        });
        this.text = text
    }

    increase() {
        this.count++;
    }
    
    decrease() {
    	this.count--;
    }
}

Using decorators is no longer the norm in MobX 6

How it works

TLDR: In short, actions modify the state, which triggers reactions.

Declarative MVVM

By default, MobX uses proxies to make arrays and plain objects observable.

new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error("Отказано в доступе");
    } else {
      let value = target[prop];
      return (typeof value === 'function') ? value.bind(target) : value; // (*)
    }
  },
  set(target, prop, val) { // перехватываем запись свойства
    if (prop.startsWith('_')) {
      throw new Error("Отказано в доступе");
    } else {
      target[prop] = val;
      return true;
    }
}

MobX Developer Tools

Pros

  1. Less boilerplate code
  2. Less learning curve
  3. Performance (default)
  4. Uses reactive paragimas

 

  1. Too much freedom
  2. Debugging & predictability 
  3. Smaller community
  4. Less info/extensions 

MobX

Cons

Pros & Cons

So when to use MobX?

Team prefers OOP/Reactive paradigmas

Medium-sized projects

Lots of data and mutations

No IE support

Now imagine a symbiosis of

MobX state tree

Immutability

Tree model representation

Snapshots and "time traveling"

Powered by MobX

Opinionated

MST

"saga"- like built-in async actions

MST Dev tools

MST sample

MST

const NoteModel = types.model({
    text: types.string,
    bg: types.maybe(
        types.string
    ),
    id: types.string,
    checked: types.boolean,
}).actions(self => ({
    toggle() {
        self.checked = !self.checked
    },
    setText(text) {
        self.text = text
    },
    setColor(color) {
        self.bg = color;
    }
}))

const RootStore = types.model({
    items: types.array(NoteModel),
    selected: types.maybeNull(
        types.string
    )
})
.views(self => ({
    get selectedNote() {
        return self.selected && self.items.find(i => i.id === self.selected)
    }
}))
.actions(self => ({
    add(text) {
        self.items.push(NoteModel.create({ text, checked: false, id: uuid() }));
    },
    deleteChecked() {
        self.items = self.items.filter(el => !el.checked);
    },
    setSelected(data) {
        self.selected = data && data.id;
    }
}));

const NotesApp = observer(({ data }) => (
    <div className="App">
        <NotesList
            items={data.items}
            onAdd={text => data.add(text)}
            onDelete={() => data.deleteChecked()}
            onSelect={item => data.setSelected(item)}
        />
        <Stats items={data.items} />
        <SelectedItemDrawer
            selected={data.selectedNote}
            onClose={() => data.setSelected(null)}
        />
    </div>
));


const store = RootStore.create({
    items: [
        NoteModel.create({ text: 'Test test', id: uuid(), checked: false }),
        NoteModel.create({ text: 'Test', id: uuid(), checked: false, bg: '#119900' }),
    ],
    selected: null,
})

const Container = () => <NotesApp data={store} />


Can I use it for large-scale apps?

YES!

but...

there are some aspects you should know...

It works slower than redux on wide model trees. To solve the issue, you need to turn of the immutability of some nodes

It has some problems with TS integration and performance. Harder to debug than redux.

Less information on the internet, smaller community. 

MST

come out 2017

Trends

13.3k

Imagine you are writing a UI for a plane control room

Engine 1:  50% power

Engine 2:  50% power

Flaps 1:  straight

Flaps 2:  7% rotation

Total speed: 460kmp/h

Chassis: folded

You can define a state of the plane and a set of transitions which can't be skipped

Xstate implements a state machine pattern

A state machine is a finite set of states that can transition to each other deterministically due to events.

  • Adherence to the W3C SCXML Specification and David Harel's original statecharts formalism
  • Promote an Actor model event-based architecture
  • Compatibility with all frameworks and platforms
  • Ability to completely serialize machine definitions to JSON (and SCXML)
  • Pure, functional Machine(...) API
  • Zero dependencies

Goals


export const createTodoMachine = ({ id, title, completed }) =>
  createMachine(
    {
      id: "todo",
      initial: "reading",
      context: {
        id,
        title,
        prevTitle: title,
        completed
      },
      on: {
        TOGGLE_COMPLETE: {
          actions: [
            assign({ completed: true }),
            sendParent((context) => ({ type: "TODO.COMMIT", todo: context }))
          ]
        },
        DELETE: "deleted"
      },
      states: {
        reading: {
          on: {
            SET_COMPLETED: {
              actions: [assign({ completed: true }), "commit"]
            },
            TOGGLE_COMPLETE: {
              actions: [
                assign({ completed: (context) => !context.completed }),
                "commit"
              ]
            },
            SET_ACTIVE: {
              actions: [assign({ completed: false }), "commit"]
            },
            EDIT: {
              target: "editing",
              actions: "focusInput"
            }
          }
        },
        editing: {
          entry: assign({ prevTitle: (context) => context.title }),
          on: {
            CHANGE: {
              actions: assign({
                title: (_, event) => event.value
              })
            },
            COMMIT: [
              {
                target: "reading",
                actions: sendParent((context) => ({
                  type: "TODO.COMMIT",
                  todo: context
                })),
                cond: (context) => context.title.trim().length > 0
              },
              { target: "deleted" }
            ],
            BLUR: {
              target: "reading",
              actions: sendParent((context) => ({
                type: "TODO.COMMIT",
                todo: context
              }))
            },
            CANCEL: {
              target: "reading",
              actions: assign({ title: (context) => context.prevTitle })
            }
          }
        },
        deleted: {
          onEntry: sendParent((context) => ({
            type: "TODO.DELETE",
            id: context.id
          }))
        }
      }
    },
    {
      actions: {
        commit: sendParent((context) => ({
          type: "TODO.COMMIT",
          todo: context
        })),
        focusInput: () => {}
      }
    }
  );

XState has a visualizer: https://statecharts.github.io/xstate-viz which is feasible due to the declarative nature.

dev-tools

Async manipulations ?

const redditMachine = Machine({
  /* ... */
  context: {
    subreddit: null,
    posts: null
  },
  states: {
    idle: {},
    selected: {
      initial: 'loading',
      states: {
        loading: {
          invoke: {
            id: 'fetch-subreddit',
            src: invokeFetchSubreddit,
            onDone: {
              target: 'loaded',
              actions: assign({
                posts: (context, event) => event.data
              })
            },
            onError: 'failed'
          }
        },
        loaded: {},
        failed: {}
      }
    }
  },
  on: {
    /* ... */
  }
});

function invokeFetchSubreddit(context) {
  const { subreddit } = context;

  return fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then(response => response.json())
    .then(json => json.data.children.map(child => child.data));
}

Pros

  1. Declarative
  2. Visualization tools
  3. Machine definitions can be serializable to JSON
  4. Statecharts to define boundaries for handling events
  5. Actor-model-like approach

 

  1. No memoization out of the box
  2. More boilerplate than redux
  3. More learning curve
  4. Hard to use as a general state manager for many types of apps

Cons

Pros & Cons

When to use Xstate?

Use it to predict all application state transitions in advance; this will help you not trap into sudden logic corner cases.

Use it if you like state-machines :D

Recoil

come out 2020 (Still experimental)

20x times more performant then Redux

10k Github stars for few months

Recoil

Well, I know that on one tool we saw a 20x or so speedup compared to using Redux. This is because Redux is O(n) in that it has to ask each connected component whether it needs to re-render, whereas we can be O(1).

Dave McCabe

Recoil

Why new state manager ?

Better performance on such apps

Multiple updates in different DOM subtrees, without updating a common parent

Recoil

Recoil defines a directed graph orthogonal to but also intrinsic and attached to your React tree.

Recoil

Recoil

Atoms

Atoms contain the source of truth for our application state. In our todo-list, the source of truth will be an array of objects, with each object representing a todo item.

const todoListState = atom({
  key: 'todoListState',
  default: [],
});
function TodoList() {
  const todoList = useRecoilValue(todoListState);

  return (
    <>
      {/* <TodoListStats /> */}
      {/* <TodoListFilters /> */}
      <TodoItemCreator />

      {todoList.map((todoItem) => (
        <TodoItem key={todoItem.id} item={todoItem} />
      ))}
    </>
  );
}

Recoil

Selectors

A selector represents a piece of derived state. You can think of derived state as the output of passing state to a pure function that modifies the given state in some way.

const todoListFilterState = atom({
  key: 'todoListFilterState',
  default: 'Show All',
});

const filteredTodoListState = selector({
  key: 'filteredTodoListState',
  get: ({get}) => {
    const filter = get(todoListFilterState);
    const list = get(todoListState);

    switch (filter) {
      case 'Show Completed':
        return list.filter((item) => item.isComplete);
      case 'Show Uncompleted':
        return list.filter((item) => !item.isComplete);
      default:
        return list;
    }
  },
});

Developed by a Facebook team

But not a react one...

Recoil

Dynamically created state

Async Support

Code-splitting out of the box

Minimal boilerplate

Concurrent mode support

Performant

But isn't MobX already offering most of these? 

MobX

 Recoil 

vs

Mutable state

Immutable (by default)

General purpose

React only

More magic

More implicit specifications

Ready for prod

Experimental only

Should I use Recoil?

Only for learning!
It's not ready for the production

Recoil

come out 2016

Used atoms before it was mainstream!

+

Composable reactive state through Atoms and Lenses

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { Atom, F } from '@grammarly/focal'

// application state
const state = Atom.create({
  count: 0
})

// the counter component from before
const Counter = (props: { count: Atom<number> }) =>
  <F.div>
    Count: {props.count}.

    <button onClick={() => props.count.modify(x => x + 1)}>
      Click again!
    </button>
  </F.div>

// the app component which takes the whole app state from the
// `state` prop
const App = (props: { state: Atom<{ count: number }> }) =>
  <div>
    Hi, here's a counter:

    {/*
      this is where we take apart the app state and give only a part of it
      to the counter component:
    */}
    <Counter count={props.state.lens('count')} />
  </div>

Pros

  1. Good rendering performance
  2. Immutable functional approach
  3. Can be used to compose a single source of truth
  4. Built-in RX async side effects support
  5. Production-ready
  1. The learning curve, much bigger then redux
  2. Community and documentation
  3. Looks more complex then recoil & redux
  4. Includes additional dependencies libs
  5. Requires to use F.* wrapper
  6. No reactivity on third-side components

Cons

Pros & Cons

When to use Focal?

You want better performance then Redux, but concerned with MobX magic

You want to work at Grammarly

Your team likes a functional reactive approach

So which library to use??

Lets recap

Small projects, without a reasonable amount of data, that is changing over time

Built-in features into your view lib/framework

Mostly keeping GraphQL data

Apollo

Relay

Medium-big projects with wide model and many async updates

MobX

Big projects with long support and big teams and with good predictability

Redux

MST

Projects with predictable model transitions and good reliability

Pet-projects

You don't know Redux => Redux 100%

You like FP & Reactive approach => Focal

You want to be in trend => Recoil

These are starting points only!

Many aspects of your project should be considered

Questions?

JavaScript community

Thank you!

JavaScript community

Links

Is redux dead - https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet/

 

https://medium.com/@dan_abramov/the-evolution-of-flux-frameworks-6c16ad26bb31

https://github.com/reduxjs/redux/issues/1385
https://medium.com/dailyjs/functional-js-3-state-89d8cc9ebc9e
https://medium.com/better-programming/react-state-management-in-2020-719d10c816bf
https://dev.to/alexandrzavalii/recoil-ideal-react-state-management-library-1203
Recoil vs MobX https://twitter.com/mweststrate/status/1261369871230787584

XState vs Redux
https://stackoverflow.com/questions/54482695/what-is-an-actual-difference-between-redux-and-a-state-machine-e-g-xstate

JS State

By Vladimir Vyshko

JS State

  • 919