loading...

Rate Questions with React and Redux - A baby example 👶

createdd profile image Daniel Deutsch ・5 min read

Rate Questions with React and Redux - A baby example 👶


https://unsplash.com/photos/s0XDLfhyN34

I will build a small application for simply rating questions. This is designed as an exercise project for React and Redux, since I am still not understanding it.

➡️ Github Repo is available here ⬅️

📄 Table of contents


"Everything is practice." - Pele

Motivation for this article

Another small application to understand Redux and React. It feels like it's the 100th app trying to grasp Redux. But 1 month without Redux and you start at basically nothing again. I am like: "Yeah I have heard about that" - and that's it. Action, Action Creators, Reducers, Dispatch, blabla. Too many things to understand :D So once again ↗️

Modularizing the base

Structure the components in order to fit perfectly into a Redux application.

➡️ Codebase on Github ⬅️

  • the stopwatch component has it's own local state is not dependent on other components
  • the stats and counter components are dependent on other components
  • the AddQuestionForm is dependent on other components and also contains logical information
  • the header and question components

Modularizing helps to

  • isolate responsibilities, which means easier testing and debugging
  • better scale the app and easier for the use of Redux
  • better organize between teams

➡️ Modularized Code on Github ⬅️

Adding Redux

Action Types

Decide which components should take part in the Redux store.
-> In this application only the questions have to be made available to all components.

Find what events happen in your application for this specific state. -> In this application it is

  • changing the score
  • adding questions
  • removing questions

Reducers

Reducers are pure functions, that change state according to the action type.

The reducer function provides different switch statements on how change the state. (Make sure to never change the state itself! It should be a pure function! #immutability)

For example:

export default function Player(state = initialState, action) {
  switch (action.type) {
    case QuestionActionTypes.ADD_PLAYER:
      return [
        ...state,
        {
          name: action.name,
          score: 0,
        },
      ];
    case QuestionActionTypes.REMOVE_QUESTION:
      return [...state.slice(0, action.index), ...state.sclice(action.index + 1)];
    case QuestionActionTypes.UPDATE_QUESTION_SCORE:
      return state.map((question, index) => {
        if (index === action.index) {
          return {
            ...question,
            score: question.score + question.score,
          };
        }
        return question;
      });
    default:
      return state;
  }
}

Actions and Action Creators

Submiting an action to Redux

  • action creators generate an action (action = an event that will result in a change in state)
  • action is dispatched to the Redux store
  • a reducer passes the action to a component and returns the new state

For example for adding a question:

export const addQuestion = name => ({
  type: QuestionActionTypes.ADD_QUESTION,
  name,
});

Create the Redux Store

Create a store in your index.js passing it the main reducer and wrap it around your scoreboard component in order to provide the store to the whole application.

Connect the container to the store

  • use mapStateToProps to assign the state to a prop value -> assign the state of the questions as props
  • for automatically dispatching actions, that are created use:
const {dispatch, questions} = this.props;
const addQuestion = bindActionCreators(QuestionActionCreators.addQuestion, dispatch);
const removeQuestion = bindActionCreators(QuestionActionCreators.removeQuestion, dispatch);
const updateQuestionScore = bindActionCreators(QuestionActionCreators.updateQuestionScore, dispatch);
  • update the event handlers on the components accordingly (counter, question and scoreboard components)
  • the header and stopwatch components don't need changes, because they do not participate in the Redux cycle

Add another component in the Redux App

Now we want to display details to each question

  • add a new action type (select a question)
  • extend the reducer with a new switch case and additional state
  • add a new action creator for selecting a question
  • create a new bindActionCreator in the scoreboard component
  • update mapStateToProps with the selected question index
  • create a QuestionDetail component to display details
  • update the event handler on the question component

➡️ See the commit with the implementation of the detail component on Github ⬅️

Implement ducks

For smaller apps the ducks concept can help to develop a Redux application faster. Basically instead of keeping everything modular (actions, reducers, actionCreators), we can also keep them in one file to have a better overview.

This file looks like:

// Actions
const ADD_QUESTION = 'question/ADD_QUESTION';
const REMOVE_QUESTION = 'question/REMOVE_QUESTION';
const UPDATE_QUESTION_SCORE = 'question/UPDATE_QUESTION_SCORE';
const SELECT_QUESTION = 'question/SELECT_QUESTION';

// Reducers
const initialState = {
  questions: [
    {
      name: 'Do you like AI?',
      score: 31,
      created: '00:00',
      updated: '00:00',
    },
    {
      name: 'Do you like Engineering?',
      score: 20,
      created: '00:00',
      updated: '00:00',
    },
    {
      name: 'How many Redux Apps?',
      score: 50,
      created: '00:00',
      updated: '00:00',
    },
  ],
  selectedQuestionIndex: -1,
};

export default function Question(state = initialState, action) {
  const date = `${new Date().getHours()}:00`;
  switch (action.type) {
    case ADD_QUESTION:
      const addQuestionList = [
        ...state.questions,
        {
          name: action.name,
          score: 0,
          created: date,
        },
      ];
      return {
        ...state,
        questions: addQuestionList,
      };
    case REMOVE_QUESTION:
      const removeQuestionList = [
        ...state.questions.slice(0, action.index),
        ...state.questions.slice(action.index + 1),
      ];
      return {
        ...state,
        questions: removeQuestionList,
      };
    case UPDATE_QUESTION_SCORE:
      const updateQuestionList = state.questions.map((question, index) => {
        if (index === action.index) {
          return {
            ...question,
            score: question.score + action.score,
            updated: date,
          };
        }
        return question;
      });
      return {
        ...state,
        questions: updateQuestionList,
      };
    case SELECT_QUESTION:
      return {
        ...state,
        selectedQuestionIndex: action.index,
      };
    default:
      return state;
  }
}

// ActionCreators
export const addQuestion = name => ({
  type: ADD_QUESTION,
  name,
});
export const removeQuestion = index => ({
  type: REMOVE_QUESTION,
  index,
});
export const updateQuestionScore = (index, score) => ({
  type: UPDATE_QUESTION_SCORE,
  index,
  score,
});
export const selectQuestion = index => ({
  type: SELECT_QUESTION,
  index,
});

➡️ See the commit with the implementation of ducks on Github ⬅️

Chrome Redux DevTools

const store = createStore(
    QuestionReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
);

The DevTools help to develop and debug your Redux app. Check out this article for more.


➡️ Result on Github ⬅️


If you gained something from this article let me know with a comment or heart. Make sure to follow for more :)

Discussion

pic
Editor guide