Using re-select effectively

  • Strict equals
  • How re-renders work
  • How redux works
  • How to use selectors to minimize

Topics

Strict equals

const obj = {}

obj === obj // true
obj === {} // false
const arr = []

arr === arr // true
arr === [] // false
const str = 'string'

str === str // true
str === 'string' // true
const num = 1

num === num // true
num === 1 // true
const bool = true

bool === bool // true
bool === true // true

React

update

reconcile

setState()

<RContainer />

<Modal />

<Button />

<Root />

<Link />

Redux store

export default function createStore(reducer) {
  let currentState

  const getState = () => currentState

  const dispatch = action => {
    currentState = reducer(currentState, action)
  }

  // ..

  return {
    dispatch,
    getState
  }
}
import { createStore } from 'redux'
import reducer from './reducer'

const store = createStore(reducer)

Redux store

const initialState = {
  items: []
};

export default function todosReducer(
  state = initialState,
  action
) {
  if (action.type === "ADD_TODO") {
    return {
      ...state,
      items: [...state.items, action.payload]
    };
  }
  return state;
}

Redux store

export default function createStore(reducer) {
  let currentState

  const dispatch = action => {
    currentState = reducer(currentState, action)
  }

  dispatch({ type: '@@redux/INIT$' })

  // ..
}
const initialState = {
  items: []
}

export default function todosReducer(state = initialState, action) {
  // ..
  return state
}

Connect

import { connect } from 'react-redux'

const ModalContainer = connect(mapStateToProps)(Modal)
import store from './store/store'

const App = () => (
  <Provider store={store}>
    <ModalContainer />
  </Provider>
)

export default App
import React, { Component } from 'React'
import Context from './Context'

export default class Provider extends Component {
  constructor(props) {
    super(props)
    this.state = {
      storeState: props.store.getState()
    }
  }

  // ..

  render() {
    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    )
  }
}
const ModalStatus = () => (
  <Context.Consumer>
    {({ storeState }) => (
        <p>Modal is: {storeState.modal.visible}</p>
    )}
  </Context.Consumer>
)

Redux

function shallowEqual(objA, objB) {
  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  for (let i = 0; i < keysA.length; i++) {
    if (objA[keysA[i]] !== objB[keysA[i]]) {
      return false
    }
  }

  return true
}
const newProps = mapStateToProps(state)

const newProps = mapStateToProps(state)

const propsChanged = shallowEqual(
    prevProps, 
    newProps
)
const newProps = mapStateToProps(state)

const propsChanged = shallowEqual(
    prevProps, 
    newProps
)

if(propsChanged) {
  renderComponent(newProps)
} 

Redux checks that each new prop strict equals (===) the previous prop

Redux re-renders the component if any prop does not strict equal the previous prop

const mapStateToProps = state => ({
  subtitles: {
    text: state.subtitle.long,
    color: state.theme.color
  }
})

This is a problem if mapStateToProps creates an object

mapStateToProps(state).subtitles !== 
mapStateToProps(state).subtitles 

Redux

const mapStateToProps = state => ({
  subtitles: {
    text: state.subtitle.long,
    color: state.theme.color
  }
})

This is a problem if mapStateToProps creates an object

mapStateToProps(state).subtitles !== 
mapStateToProps(state).subtitles 

Reselect

subtitlesSelector(state) === subtitlesSelector(state)

The solution is to memoize objects using a selector library like Reselect

Reselect

Reselect works by memoizing (caching) the last call of a function

Reselect

const memoizeFn = fn => {
  let prevResult
  let prevArg

   
      
    




  
}
const memoizeFn = fn => {
  let prevResult
  let prevArg
  return arg => {







  }
}
const memoizeFn = fn => {
  let prevResult
  let prevArg
  return arg => {
    if (prevArg === arg) {
      return prevResult
    }
    const result = fn(arg)
    
    
    
  }
}
const memoizeFn = fn => {
  let prevResult
  let prevArg
  return arg => {
    if (prevArg === arg) {
      return prevResult
    }
    
    
   
    
  }
}
const memoizeFn = fn => {
  let prevResult
  let prevArg
  return arg => {
    if (prevArg === arg) {
      return prevResult
    }
    const result = fn(arg)
    prevArg = arg
    prevResult = result

  }
}
const memoizeFn = fn => {
  let prevResult
  let prevArg
  return arg => {
    if (prevArg === arg) {
      return prevResult
    }
    const result = fn(arg)
    prevArg = arg
    prevResult = result
    return result
  }
}

Effective reselectors

  1. Return a primitive value
  2. Ensure new object isn't created if dependencies don't change

Reselect

const memoizeFn = fn => {
  let prevResult
  let prevArg
  return arg => {
    if (prevArg === arg) {
      return prevResult
    }
    const result = fn(arg)
    prevArg = arg
    prevResult = result
    return result
  }
}

Return a primitive value

export const selectUpNextType = createSelector(
  [getUpNext],
  (upNext) => {
    if (!upNext) {
      return null;
    }

    return upNext.source;
  }
);

Using reselect effectively

By Edd Yerburgh

Using reselect effectively

Using reselect-effectively

  • 138
Loading comments...

More from Edd Yerburgh