DEV Community

loading...
Cover image for Demystifying 15 lesser-known React Redux terms and features (With examples) ๐Ÿ”ฎ

Demystifying 15 lesser-known React Redux terms and features (With examples) ๐Ÿ”ฎ

vaibhavkhulbe profile image Vaibhav Khulbe ใƒปUpdated on ใƒป9 min read
NOTE: Most of the content is regarding React-Redux.

Previously, we talked about some of the major terms or features of React which may go unnoticed as we build or work on more and more project with the awesome UI library.

As we make more and more big or complex applications with React, we come to know that handling the overall state of the entire app isn't possible with just React.Component class, using a constructor() with setState() call. We need a state container like the famous Redux to run it in different environments, centralizing the app state/logic and is easily debuggable.

So just like before, here are some more terms or features of Redux which you might have used but didn't quite know what or how exactly they do...and yes, with examples!


Mystery GIF

Exaclty that!


1. What are the core principles of Redux? (Doc ๐Ÿ“ƒ) X๏นX

Redux works on three core principles:

1๏ธโƒฃ Single source of truth

This means that the state of your entire application is stored in a store as an object tree.

It has some advantages like:

  • A single state tree makes it easier to debug or inspect an application.
  • It also enables you to persist your app's state in development, for a faster development cycle.
  • This makes it easy to create universal or large-scale apps.

Example:

// If you execute the following:
console.log(store.getState())

// You'll get this as a result:
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}

2๏ธโƒฃ State is read-only

The only way you change the state is by releasing an action.

Because all changes are centralized and happen one by one, there are no subtle race conditions to watch out for.

Example:

// This is how you dispatch actions:
store.dispatch({
  type: 'COMPLETE_TODO',
  index: 1
})

store.dispatch({
  type: 'SET_VISIBILITY_FILTER',
  filter: 'SHOW_COMPLETED'
})

3๏ธโƒฃ Changes are made with pure functions

You write reducers to specify how the state is transformed by _action_s.

Because reducers are just (pure) functions, you can control the order in which they are called, pass additional data, or even make reusable reducers.

Example:

// Here's how to create pure functions/reducers
function visibilityFilter(state = 'SHOW_ALL', action) {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}

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

2. How's Flux better when compared to Redux? (Flux Doc ๐Ÿ“ƒ) (ใ€‚๏นใ€‚*)

Flux is a pattern and Redux is a library. But there are few compromises you need to make when using Redux:

  1. You will need to avoid mutations: You can enforce this with packages like redux-immutable-state-invariant, Immutable.js etc.

  2. You're going to have to carefully pick your packages: Redux has extension points such as middleware and store enhancers, having a vast ecosystem of packages.

  3. There is no nice Flow integration yet.

Here's a great article for those who want to know more about Flux and Redux:

3. What is the difference between mapStateToProps() and mapDispatchToProps()? โ”—( T๏นT )โ”›

> mapStateToProps() (Doc ๐Ÿ“ƒ)

It is a utility which helps your component to get updated state (which is updated by some other components). This function should be passed as the first argument to connect and will be called every time when the Redux store state changes.

Example:

// TodoList.js
function mapStateToProps(state) {
  const { todos } = state
  return { todoList: todos.allIds }
}

export default connect(mapStateToProps)(TodoList)

> mapDispatchToProps() (Doc ๐Ÿ“ƒ)

It is a utility which will help your component to fire an action event (dispatching action which may cause a change of application state). The component gets the [dispatch](https://react-redux.js.org/api/connect#dispatch) by default.

Example:

import { addTodo, deleteTodo, toggleTodo } from './actionCreators'

const mapDispatchToProps = {
  addTodo,
  deleteTodo,
  toggleTodo
}

export default connect(
  null,
  mapDispatchToProps
)(TodoApp)

4. What if you dispatch an action in the reducer? >_<

This comes back from a popular Stackoverflow question embedded below. Always remember that:

Dispatching an action within a reducer is an anti-pattern.

Can I dispatch an action in reducer?

199

is it possible to dispatch an action in a reducer itself? I have a progressbar and an audio element. The goal is to update the progressbar when the time gets updated in the audio element. But I don't know where to place the ontimeupdate eventhandler, or how to dispatch anโ€ฆ

5. What about dispatching and action on load? <( _ _ )>

Yes yes, you can dispatch an action in on load with componentDidMount() method verifying the data in the render() method.

Example:

class App extends Component {
  componentDidMount() {
    this.props.fetchData()
  }

  render() {
    return this.props.isLoaded
      ? <div>{'Loaded'}</div>
      : <div>{'Not Loaded'}</div>
  }
}

const mapStateToProps = (state) => ({
  isLoaded: state.isLoaded
})

const mapDispatchToProps = { fetchData }

export default connect(mapStateToProps, mapDispatchToProps)(App)

6. How to reset state in Redux? (โ‰ง๏น โ‰ฆ)

The best way is to use a root reducer and handle the action to reducer generated by combineReducers(). If you want to know deeply how exactly it's done check out this article.

Example:

const appReducer = combineReducers({
  // Top-level reducers here
})

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    state = undefined
  }

  return appReducer(state, action)
}

7. Why we use the at (@) symbol in the Redux connect decorator? (โ‰ง๏น โ‰ฆ)

In JavaScript, the @ symbol is used to signify decorators. Learn more about them here. Basically you annotate or modify classes and properties with them.

I am learning Redux with React and stumbled upon this code. I am not sure if it is Redux specific or not, but I have seen the following code snippet in one of the examples.

@connect((state) => {
  return {
    key: state.a.b
  };
})

While the functionality of connect isโ€ฆ

8. What is the difference between React context and React-Redux? (T_T)

> React context (Doc ๐Ÿ“ƒ)

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

Consider using it for a small-scale application whereas Redux provides this and much more deep and powerful state control internally.

> React-Redux (Doc ๐Ÿ“ƒ)

React-Redux is the official React binding for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update data.

9. How to make an AJAX request in Redux? (T_T)

AJAX allows you to send asynchronous HTTP requests to submit or retrieve data from the server.

To do an AJAX call in Redux you can use the redux-thunk middleware which allows you to define async actions.

Example:

export function fetchAccount(id) {
  return dispatch => {
    dispatch(setLoadingAccountState()) // Show a loading spinner
    fetch(`/account/${id}`, (response) => {
      dispatch(doneFetchingAccount()) // Hide loading spinner
      if (response.status === 200) {
        dispatch(setAccount(response.json)) // Use a normal function to set the received state
      } else {
        dispatch(someError)
      }
    })
  }
}

function setAccount(data) {
 return { type: 'SET_Account', data: data }
}

10. What is the recommended way of accessing Redux store? ใ€’โ–ฝใ€’

The best way is to use the connect() function using Higher Order Functions (HOCs) pattern. This allows to map state and action creators to the component, and have them passed in automatically as the store updates.

Example:

// You can pass the context as an option to connect
export default connect(
  mapState,
  mapDispatch,
  null,
  { context: MyContext }
)(MyComponent)

// or, call connect as normal to start
const ConnectedComponent = connect(
  mapState,
  mapDispatch
)(MyComponent)

// Later, pass the custom context as a prop to the connected component
<ConnectedComponent context={MyContext} />

11. Why we use constants in Redux? โ‰ง ๏น โ‰ฆ

This is because they allow us to easily find all usages of a specific functionality across the project. Also, you won't get much ReferenceError when you use constants.

Example:

// In constants.js
export const ADD_TODO = 'ADD_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const EDIT_TODO = 'EDIT_TODO'

// In actions.js
import { ADD_TODO } from './actionTypes';

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

// In reducer.js
import { ADD_TODO } from './actionTypes'

export default (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ];
    default:
      return state
  }
}

12. Why we use of ownProps (Doc ๐Ÿ“ƒ) in mapStateToProps() and mapDispatchToProps()? (๏ผ›โ€ฒโŒ’')

The ownProps is an optional parameter we add as the second argument in both mapStateToProps() or mapDispatchToProps(). You use this only if your component needs the data from its own props to retrieve data from the store.

Example:

// Todo.js
function mapStateToProps(state, ownProps) {
  const { visibilityFilter } = state
  const { id } = ownProps
  const todo = getTodoById(state, id)

  // component receives additionally:
  return { todo, visibilityFilter }
}

// Later, in your application, a parent component renders:
<ConnectedTodo id={123} />
// and your component receives props.id, props.todo, and props.visibilityFilter

As the doc says:

You do not need to include values from ownProps in the object returned from mapStateToProps. connect will automatically merge those different prop sources into a final set of props.

13. What are the differences between call() and put() in redux-saga? (โ”ฌโ”ฌ๏นโ”ฌโ”ฌ)

redux-saga is a library that aims to make application side effects easier to manage, more efficient to execute, easy to test, and better at handling failures.

> call() (Doc ๐Ÿ“ƒ)

In simple words, you use call() to create effect description, which instructs middleware to call the promise. After which the middleware invokes the function and examines its result.

> put() (Doc ๐Ÿ“ƒ)

Whereas the put() function creates an effect, which instructs middleware to dispatch an action to the store.

function* fetchUserSaga(action) {
  // `call` function accepts rest arguments, which will be passed to `api.fetchUser` function.
  // Instructing middleware to call promise, it resolved value will be assigned to `userData` variable
  const userData = yield call(api.fetchUser, action.userId)

  // Instructing middleware to dispatch corresponding action.
  yield put({
    type: 'FETCH_USER_SUCCESS',
    userData
  })
}

Hence, both call() and put() are effect creator functions.

14. What are the differences between redux-saga and redux-thunk? โ•ฏ๏ธฟโ•ฐ

The Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met.

  • Thunk uses Promises to deal with them, whereas Saga uses Generators.
  • Thunk is simple to use and Promises are familiar, Sagas/Generators are more powerful but you will need to learn them.

Here is a good post on how you can implement both of them:

15. How to set initial state in Redux? ::>_<::

There are two ways:

Example:

const rootReducer = combineReducers({
  todos: todos,
  visibilityFilter: visibilityFilter
})

const initialState = {
  todos: [{ id: 123, name: 'example', completed: false }]
}

const store = createStore(
  rootReducer,
  initialState
)
  • Or by using explicit check inside the reducer.

Example:

function myReducer(state = someDefaultValue, action)

I hope I've explained these 15 points in the right way. Did you know about them? Honestly, I never knew about @ symbol in Redux!


๐Ÿ“ซ Subscribe to my weekly developer newsletter ๐Ÿ“ซ

PS: From this year, I've decided to write here on DEV Community. Previously, I wrote on Medium. If anyone wants to take a look at my articles, here's my Medium profile.

Discussion

pic
Editor guide