redux-talk@1.0.0

Nikita Kirsanov, EPAM Systems (Ryazan)

redux-talk@1.0.0

Nikita Kirsanov

About me

Nikita Kirsanov

Nikita Kirsanov - Senior Software Engineer - EPAM Systems (Ryazan)

Agenda

The most difficult task in development?

Chat

            function newMessageHandler(newMessage) {
              var chatTab = ChatTabs.getChatTab(message.threadId);
              chatTab.appendMessage(message)
            }
        
Facebook chat

Chat with counter

Facebook chat
            function newMessageHandler(newMessage) {
              UnseenCount.increment();
             
              var chatTab = ChatTabs.getChatTab(message.threadId);
              chatTab.appendMessage(message)
             
              if (chatTab.hasFocus())
                  UnseenCount.decrement();
            }
        

What about this?

Facebook chat

Chat, threads and counter

Pasta
            function newMessageHandler(newMessage) {
              UnseenCount.increment();
             
              var chatTab = ChatTabs.getChatTab(message.threadId);
              chatTab.appendMessage(message)
             
              var messageView = Messages.getOpenView();
              var threadId = messageView.getThreadId();
              if (threadId === message.threadId)
                messageView.appendMessage(message)
             
              if (chatTab.hasFocus() || threadId === message.threadId)
                  UnseenCount.decrement();
            }
        

We might doing something wrong in general?

Wrong way

External control

External control

Internal control

Internal control

Internal control: action flow

Internal control, action flow

We are good boys, we are not mixing view with model, aren't we?

Separate view with model

Generic system

Generic system

Here is flux

Flux

Benefits

Flux

Redux

Dan Abramov

Redux evolves the ideas of Flux, but avoids its complexity by taking cues from Elm.

Dan Abramov
Redux architecture

Single source of truth

The state of your whole application is stored in an object tree within a single store.

First redux pricipal
            import { createStore } from 'redux';
             
            const store = createStore(...);
             
            store.subscribe(
              () => console.log(store.getState())
            )
        

Single store benefits

Recorder

redux-devtools

State is read-only

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

Second redux pricipal
            store.dispatch({
              type: 'ANY_ACTION',
              payload: 'some data goes here'
              error: false // to deliver error
            })
        

Changes are made with pure functions

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

Third redux pricipal
            function myReducer(state = {}, action) {
              switch (action.type) {
                case 'ACTION_I_KNOW_HOW_TO_PROCESS':
                  return { ...state, some: change }
                default:
                  return state;
            }
        

Pure functions benefits

Immutability

Counter app (markup)

            <body>
              <span class="js-value">0</span>
              <br/>
              <button class="js-increment-btn">+</button>
              <button class="js-decrement-btn">-</button>
            </body>
        
0

Counter app

            // index.js
            import { createStore } from 'redux'
            import reducer from './reducer'
             
            const store = createStore(reducer);
             
            document.querySelector('.js-increment')
              .click = () => store.dispatch('INCREMENT')
             ...
            var valueEl = document.querySelector('.js-value');
             
            store.subscribe(
              () => valueEl.innerHTML = store.getState().toString()
            );
        

Counter app

            // reducer.js
            export default function counter(state = 0, action) {
              switch (action.type) {
                  case 'INCREMENT':
                      return state + 1;
                  case 'DECREMENT':
                      return state - 1;
                  default:
                      return state;
            }
        

Derived data

            // avoid
            const messageReducer = (state, action) => {
                switch (action.type) {
                    case 'NEW_MESSAGE':
                        return {
                            ...state,
                            messages: state.messages.concat([action.payload]),
                            unreadMessages: state.unreadMessages + 1,
                        }
                  default:
                      return state;
            }
        

Derived data

            // recommended
            const messageReducer = (state, action) => {
                switch (action.type) {
                    case 'NEW_MESSAGE': ...
                    case 'MESSAGE_WAS_READ':
                        return {
                            messages: state.messages.map(message => {
                                if (message.id === action.payload) {
                                    return { ...message, read: true }
                                }
                                return message
                            }),
                        }
            }
        

reselect

            import { createSelector } from 'reselect';
             
            const messagesSelector = state => state.messages
             
            const unreadMessages = createSelector(messages,
                      messagesSelector,
                      messages => messages.filter(m => !m.read)
            )
             
            const unreadMessagesCount => createSelector(
                      unreadMessages,
                      messages => messages.length
            )
             
        

Regular API response

Normalized data (normalizr)

Middleware

It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.

With a help of it you can hold side effects separately.

Async Actions (redux-thunk)

            export function asyncAction(idOfSomething) {
              return dispatch => {
                  dispatch({ type: 'START_FETCHING_SOMETHING' });
                  fetch(`/something/${idOfSomething}`)
                      .then(response => dispatch({
                          type: 'FETCHED_SOMETHING',
                          payload: response
                       }));
              };
            }
        

Complex async code

life without epic

Redux-observable

            Rx.Observable.fromEvent(this.refs.input, 'input')
                .map(event => event.target.value)
                .filter(value => !!value)
                .debounceTime(500)
                .switchMap(searchTerm =>
                    ajax('/api/search', searchTerm)
                    .map(payload => ({ type: 'QUERY_FULLFILLED', payload }))
                    .catch(payload => ({
                         type: 'QUERY_FULLFILLED',
                         error: true,
                         payload
                    }))
        

ngrx/store

ngrx/store

Why should I use redux?

Community

redux
23,982
react-redux
4,854
redux-actions
2,025
react-redux-router
4,550
reselect
4,219
redux-saga
4,471
redux-thunk
3,169
redux-form
3,944
redux-devtools
5,807
redux-devtools-extension
2,251
ng-redux
547
backbone-redux
125
ngrx/store
1,363
redux-immutable
666
normalizr
4,918
flux-standard-action
1,619
multireducer
224
canonical-reducer-composition
138

Companies which are using redux

Thank you!

kitos.github.io/redux-talk

Further reading

Questions

Powered by Shower