PB138: React and State Management

presented by Lukas Grolig

What is state management?

  • Something that stores data for your components so you don‘t have to pass them from the parent.
  • It also handles actions that happen in your components.
  • Shares state across multiple components

What is Flux?

  • It is architure for building client-side websites. It is utiling unidirectional data flow between component. It‘s pattern implemented by state management frameworks.

action

store

dispatcher

view

Object describing what happened

Component routing data to stores (usually by registered callbacks)

Holds some part of state and maintains related logic

Different implementations of Flux

React Context

Context provides a way to pass data through the component tree without having to pass props down manually at every level

Creating context

const ThemeContext = React.createContext('light');

First create context and provide default value. We suggest to export context and import it in components later.​

Providing values in context

class App extends React.Component { 
    render() { 
        return ( 
            <ThemeContext.Provider value="dark"> 
                <Toolbar /> 
            </ThemeContext.Provider> 
        ); 
    } 
}

The provider will pass a value to all components in the tree below. No matter how deep.

Or pass state from any component to the top. 

 

Consuming values from context

class MyComponent extends React.Component { 
    render() { 
        return ( 
            <ThemeContext.Consumer> 
	        {(context) => {
                    <Toolbar theme={context} /> 
                }}
            </ThemeContext.Consumer> 
        ); 
    } 
}

Component consuming context is wrapped by it.

MobX

  • It is a simple, scalable, and battle-tested state management solution.
  • MobX uses an object-oriented approach to state management.
  • Realy easy to use.

Data flow in MobX is similar as in Flow

@Action

@Observer

ViewComponent

Store

@Reaction

calls

notifies

How to implement a store

Observable

class TickerStore { 
    @observable tick = 0;
}

@observable is High order function. Wrap another function with it (function currying) or use decorator like above.

Computed property

class OrderLine { 
	@observable price = 0; 
	@observable amount = 1; 
	constructor(price) { this.price = price; } 
	@computed get total() { return this.price * this.amount; } 
}

Computed value uses other observables to calculate some valueon top of them.

Observer

@observer 
class Ticker extends React.Component { 
    render() { 
        return (
            <span>Ticks passed: { this.props.tickerStore.tick } </span> 
        ) 
    } 
};

Observer enables a component to listen to store changes. The store is passed in props. Usually, it is also injected. If you want to use an injection add inject decorator.

 

 

Action

class Ticker { 
	@observable tick = 0;
	@action
	inkrement = () => { this.tick++ // 'this' will always be correct }; 
} 
const ticker = new Ticker();
setInterval(ticker.increment, 1000);

If some function in store modifies state, mark it as @action.

Reaction

const reaction = reaction( () =>
	todos.map(todo => todo.title), titles => console.log("reaction:", titles.join(", ")) 
);

Put this code into your store. When something in the store changes than your reaction is called. Autorun is like reaction, but you don't specify on what props you listen.  

 

MobX suggestion: DevTools

import DevTools from 'mobx-react-devtools‘; 
const App = () => ( <div> ... <DevTools /> </div> );

This will show data, changes, render times

npm install mobx-react-devtools

Redux

Redux is a predictable state container for JavaScript apps. As the requirements for JavaScript single-page applications have become increasingly complicated, our code must manage more state than ever before.

This state can include server responses and cached data, as well as locally created data that has not yet been persisted to the server.

UI state is also increasing in complexity, as we need to manage active routes, selected tabs, spinners, pagination controls, and so on.

State is read-only

The only way to change the state is to emit an action, an object describing what happened.

store.dispatch({
	type: 'COMPLETE_TODO‘,
	index: 1
})

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers.

function todos(state = [], action) {
	switch (action.type) {
		case 'COMPLETE_TODO‘:
			return state.map((todo, index) => { ... })
		default:
			return state
}

Combined into reducer

import { combineReducers, createStore } from 'redux'
const reducer = combineReducers({ todos, someOtherFunction })
const store = createStore(reducer)

Combined into reducer

import { combineReducers, createStore } from 'redux'
const reducer = combineReducers({ todos, someOtherFunction })
const store = createStore(reducer)

Actions

Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

Action Creators

Action creators are exactly that—functions that create actions. It's easy to conflate the terms “action” and “action creator”, so do your best to use the proper term.

Action creator returning Action

function addTodo(text) {
	return {
		type: ADD_TODO,
		text // assigns content of parameter text to field text
	}
}

Reducers

Reducers specify how the application's state changes in response to actions sent to the store. Remember that actions only describe what happened, but don't describe how the application's state changes.

Store

The Store is the object that brings everything together. The store has the following responsibilities:
Holds application state;
Allows access to state via getState();
Allows the state to be updated via dispatch(action);
Registers listeners via subscribe(listener);
Handles unregistering of listeners via the function returned by subscribe(listener)

 

Using with React

You have to mam everything to props by creating state mapping function

 

const mapStateToProps = state => {
	return {
		todos: getVisibleTodos(state.todos, state.visibilityFilter)
	}
}

Using with React

You also pass in props what actions should happen.

const mapDispatchToProps = dispatch => {
	return {
		onTodoClick: id => {
			dispatch(toggleTodo(id))
		}
	}
}

Using with React

And finaly by using HOC store is connected with component

import { connect } from 'react-redux'

const VisibleTodoList = connect(
	mapStateToProps,
	mapDispatchToProps
)(TodoList)

export default VisibleTodoList

Questions?

Ok, that's it for today.

React State Management

By Lukáš Grolig

React State Management

  • 380