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
- Less boilerplate code
- Less learning curve
- Performance (default)
- Uses reactive paragimas
- Too much freedom
- Debugging & predictability
- Smaller community
- 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
- Declarative
- Visualization tools
- Machine definitions can be serializable to JSON
- Statecharts to define boundaries for handling events
- Actor-model-like approach
- No memoization out of the box
- More boilerplate than redux
- More learning curve
- 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
- Good rendering performance
- Immutable functional approach
- Can be used to compose a single source of truth
- Built-in RX async side effects support
- Production-ready
- The learning curve, much bigger then redux
- Community and documentation
- Looks more complex then recoil & redux
- Includes additional dependencies libs
- Requires to use F.* wrapper
- 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
- 901