Learning Redux

Before starting, things that became clear to you later on: It is the job of the Reducer (more precisely, the different reducer functions, but you can think of them collectively as a reducer) to modify the state [Actually, to return a new copy of the state as the state is read only]. So all actions (and action creators) end up at the Reducer, who then goes and modifies the state.

1-createStore()

Redux uses an object called store to keep track of the state of the whole app. We declare store as a variable (in the create react app setup, we did it in the index.js - the place where the root App component is called), and called the createStore() function to create it. Note that createStore() is a redux method, so it should be called with Redux.something. Also, the createStore method always needs to take a function called 'reducer' as an argument (we will cover reducer later). Here is the code:

//creating a reducer function first:
const reducer = (state = 5) => {
  return state;
}

Redux methods are available from a Redux object For example: Redux.createStore()

//now using the reducer function as an argument:
const store = Redux.createStore(reducer)

2-store.getState()

Another way to write the above code more concisely is to throw the function body directly inside createStore():

const store = Redux.createStore(
  (state = 5) => state
);

//note how we can skip the 'return' in 'return state'

Now, assume we want to create a variable called currentState and assign to it the value of the current state. We can get the value of current state by using store.getState(). The code:

const store = Redux.createStore(
  (state = 5) => state
);

const currentState = store.getState();

currentState will now have the value of 5. We can do this because we now have a universal store object and we can check its state whenever we want.

Note that the Redux store object provides several methods that allow you to interact with it, and getState() is one of them.


3-Updating States

Updating states in one of the core functionalities in Redux. In Redux, state updates are triggered by actions. An action is simply a JS object that contains information about an action event that has occured (like onClick, I guess). The redux store receives these action objects and updates the state accordingly.

Sometimes a Redux action also carries some data. For example, the action carries a username after a user logs in. While the data is optional, actions must carry a type property that specifies the 'type' of action that occurred.

Writing a Redux action is as simple as declaring an object with a type property. Example:

const action ={
  type : 'LOGIN'
}

4-Action Creators

After creating an action, the next step is sending the action to the Redux store so it can update its state. But before that, you need an to define an action creator. An action creator is simply a JavaScript function that returns an action. In other words, action creators create objects that represent action events:

const action = {
  type: 'LOGIN'
}
// Define an action creator here:

function actionCreator(){
  return action;
}
//in the above, const instead of function 
//could have also worked

Both bi darbe wa7de:

const actionCreator = () => {
  return {
    type: 'LOGIN'
  }
};

Note that creating an action does not mean that it has been passed to redux store. We will pass it in the next step.


5-Dispatching an action

To dispatch (pass?) an action to the redux store, you use the store.dispatch function. Based on the previous example, both these ways of dispatching would work:

store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });  
//This works too because type is unique to every action
//note: the dispatch itself is called based on some kind
//of event (like lifecycle event, login, form submission, etc)

Here is a code of the whole thing running together:


//store created, with reducer function inside, 
//state initialized to first value, where login is false
const store = Redux.createStore(
  (state = {login: false}) => state
);

//a login action, which once sent to the reducer, 
//will update the state of login based on how its 'reducer function' works (see next section)
const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};

//the code that triggers and action to be sent to the reducer, 
//can be called on button click or whatever
store.dispatch(loginAction());

6-Responding to a dispatched action

If a certain action is dispatched based on some event, the Redux store needs to know how to respond to that action. This is the job of a reducer function. Reducers in Redux are responsible for the state modifications that take place in response to actions.

A reducer takes state and action as arguments, and it always returns a new state. It is important to see that this is the only role of the reducer.

Another key principle in Redux is that state is read-only. In other words, the reducer function must always return a new copy of state and never modify state directly.

Here is a code:

const defaultState = {
  login: false
};

const reducer = (state = defaultState, action) => {
  // change code below this line
 if(action.type === 'LOGIN'){
   return { login : true};
 }
 else return defaultState;
  // change code above this line
};

const store = Redux.createStore(reducer);

const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};

Note: reducer function doesn't necessarily need to be named reducer. Also you can have more than one reducer function.


7-Using switch in Reducer

Remember: a reducer returns a new state based on an action. To be more precise, it changes a certain 'property' among the many properties that exist within a state, according to a certain action.

In its second argument, a reducer can receive any of the actions in the app, and according to the specific action (which action fits its cases) it delivers a new state.

So I have a bunch of actions that exist within action functions. Whenever any of these actions is triggered, a[/all?] reducer function will be called (with arguments state, and that specific action). According to that specific action, a part (property) of the state will be updated. See example:

const defaultState = {
  authenticated: false
};

const authReducer = (state = defaultState, action) => {
  // change code below this line
  switch(action.type) {
  case 'LOGIN':
    return { authenticated: true};
  case 'LOGOUT':
    return defaultState;
  default:
   return defaultState;
}
  // change code above this line
};

const store = Redux.createStore(authReducer);

const loginUser = () => {
  return {
    type: 'LOGIN'
  }
};

const logoutUser = () => {
  return {
    type: 'LOGOUT'
  }
};

Note: I am not sure if all reducer functions are called in an action dispatch, or if we can specify which reducer function to be called? I guess not, I guess all of them are called and all of them search through their switch statement.

In the above example, we have two actions: LOGIN, and LOGOUT. We have a store that is connected to a reducer function that focus on login and logout. We have a default state for our state when it comes to authentication, which makes it false by default.

When Login or Logout actions are triggered, the reducer function will be called (by dispatch) to update the state accordingly. Note that we used the switch syntax. When using the switch syntax here, make sure there are no break statements, and that a default statement is included.


8- Assigning strings to variables

It is better to have LOGIN and LOGOUT as variables assigned to strings instead of using the strings directly. So instead of the above code, it is better to write it this way:

// change code below this line
const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';
// change code above this line

const defaultState = {
  authenticated: false
};

const authReducer = (state = defaultState, action) => {

  switch (action.type) {

    case LOGIN:
      return {
        authenticated: true
      }

    case LOGOUT:
      return {
        authenticated: false
      }

    default:
      return state;

  }

};

const store = Redux.createStore(authReducer);

const loginUser = () => {
  return {
    type: LOGIN
  }
};

const logoutUser = () => {
  return {
    type: LOGOUT
  }
};

Note how the const string declarations were all caps. It is a convention to write them that way.


9-store.Subscribe()

Another redux method related to the store is store.subscribe(). It allows me to subscribe listener functions to the store which are called whenever an action is dispatched against the store.

A simple way of using it is to subscribe a function to your store that simply logs a message every time an action is received and the store is updated.

The following code was confusing to me because no action was defined (but maybe it is not needed anyway) -EDIT: you are already dispatching actions at the end of the code, I guess we can safely assume they are defined.

const ADD = 'ADD';

const reducer = (state = 0, action) => {
  switch(action.type) {
    case ADD:
      return state + 1;
    default:
      return state;
  }
};

const store = Redux.createStore(reducer);

// global count variable:
let count = 0;

// change code below this line
const increment = () => {
  count++;
}
store.subscribe(increment);
// change code above this line

store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);

The subscriber listener function was called on each store dispatch. We will have a console log as follows:
1
2
3


10- Organizing (deviding) states: Reducer Composition

As our state becomes more complex, we might be tempted to devide it into multiple pieces. But remember: Redux works on the principle that all states of the app are put together in one place inside the store. (In a single state object). Therefore, to be able to 'divide' or organize states inside the store, we do something called reducer composition.

We define multiple reducers to handle different pieces of the app state, then combine these reducers together into one ROOT REDUCER. The root reducer will be what will be passed into the redux method: createStore().

So to combine those reducers, we use combineReducers() method. This method takes an object as an argument, and this object has properties as a key:reducer pair. The name given to the keys will be sued by Redux as the name of the associated piece of state.

Example:

const rootReducer = Redux.combineReducers({
  auth: authenticationReducer,
  notes: notesReducer
});

Typically, it is a good practice to create a reducer for each piece of application state when they are distinct or unique in some way. In the above example (a note-taking app with user authentication), one reducer could handle authentication while another handles the text and notes that the user is submitting.

The key notes will contain all of the state associated with our notes and handled by our notesReducer. This is how multiple reducers can be composed to manage more complex application state. In this example, the state held in the Redux store would then be a single object containing auth and notes properties.

Here is a more complete example:

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

const counterReducer = (state = 0, action) => {
  switch(action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
};

const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';

const authReducer = (state = {authenticated:
false}, action) => {
  switch(action.type) {
    case LOGIN:
      return {
        authenticated: true
      }
    case LOGOUT:
      return {
        authenticated: false
      }
    default:
      return state;
  }
};

const rootReducer = Redux.combineReducers({
  auth: authReducer,
  count: counterReducer
});


const store = Redux.createStore(rootReducer);

Note how when we created the store, it was given only one reducer - the combiner reducer. Note how our state has been devided into two main 'sections': one handling authentication, and one handling a counter. Each of these state pieces has its own reducer method, and each of these state pieces has several 'state' possiblities for actions.


11- Sending data inside actions

So far we've learned how to dispatch actions to the Redux store, but they haven't included any useful information except the type. We can also send data inside actions: actually, that's mainly what actions are used for.

const ADD_NOTE = 'ADD_NOTE';

const notesReducer = (state = 'Initial State', 
action) => {
  switch(action.type) {
    // change code below this line
    case ADD_NOTE:
      return action.text;
    // change code above this line
    default:
      return state;
  }
};

const addNoteText = (note) => {
  // change code below this line
  return {
    type: ADD_NOTE,
    text: note
  }
  // change code above this line
};

const store = Redux.createStore(notesReducer);

console.log(store.getState());
store.dispatch(addNoteText('Hello!'));
console.log(store.getState());

Note: remeber that the dispatcher is what triggers the action: kind of calls the action handler.

The reducer function is taking the state and action, checking whether action is of a certain type, and returning a new state according to that type.

So here is how the sytem works together: you have a reducer, a store creator, an action handler, and a dispatcher.

The store creator is always present, it creates a store inside redux to hold a state, and always takes a reducer method as a parameter, which is 'binded' to that state. Remeber that you can have more than one state, each with its own reducer, but then you have to combine reducers into one root reducer and have the store creator call that one root reducer (because we can only create the store once).

The store creator method took a reducer as a parameter. The reducer is a method that takes both state and action as a parameters.

Now, what is the use of the action parameter taken by the reducer function? The action parameter is not 'useful' unless the action itself triggers the reducer. So a reducer method is triggered to work when a certain action is performed. That action talks to the state (which mean it triggers the state reducer), and the reducer will check if it covers that 'type' of action. So I might have 10 actions, and it is expected that all these actions have cases inside the reducer, and whenever any of these actions is triggered (by dispatch), it goes to the reducer to check for a case and a new state is generated.

I am not sure dispatch is the only thing that can trigger an action, but so far, it is the way we've been triggering actions.

SO IN SUMMARY: We create a redux store to hold the state through createStore. createStore() calls a reducer function that initializes the state to a certain value [actually I am not sure of this, it could be that reducers are only called when actions are dispatched]. Reducers are always ready to cover action dispatches that might arise in the future. Whenever an action (that has been intialized by an action handler) is triggered by a dispatcher, that action will go through all reducers, tells them: "look, here is my type, do you have any case covering me?" - if any of those reducers do, then that case is executed and a new state is generated.

We can pass information through actions.


12- Using Middleware to Handle Asynchronous Actions

Note: asynchronous actions actually mean that they are synchronized (I guess?) -- Well, actually this means I am requesting data from somewhere outside my application and I need to wait for it.

Redux Thunk middleware is used to handle asynchronous actions. To include it: you pass it as an argument to Redux.applyMiddleware().

This statement is then provided as a second optional parameter to the createStore() method.

Example:

const store = Redux.createStore(
  asyncDataReducer,
  Redux.applyMiddleware(ReduxThunk.default)
);

Then, to create an asynchronous action, you return a function in the action creator that takes dispatch as an argument. Within this function, you can dispatch actions and perform asynchronous requests.

Example:

const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'

const requestingData = () => { 
    return {type: REQUESTING_DATA} 
}
const receivedData = (data) => { 
    return {type: RECEIVED_DATA, users: data.users} }


//the below is a special action creator to handle
//asynchronous actions
const handleAsync = () => {
  return function(dispatch) {
    // dispatch request action here
    dispatch(requestingData());
    //setTimeout here is for simulation: 
     //as if we're waiting on a server
    setTimeout(function() {
      let data = {
        users: ['Jeff', 'William', 'Alice']
      }
      // dispatch received data action here
    }, 2500);
    dispatch(receivedData(data));
  }
};

const defaultState = {
  fetching: false,
  users: []
};

const asyncDataReducer = (state = defaultState, action) => {
  switch(action.type) {
    case REQUESTING_DATA:
      return {
        fetching: true,
        users: []
      }
    case RECEIVED_DATA:
      return {
        fetching: false,
        users: action.users
      }(
    default:
      return state;
  }
};

const store = Redux.createStore(
  asyncDataReducer,
  Redux.applyMiddleware(ReduxThunk.default)
);

Note that we have two normal actions, a store creator that takes the Redux Thunk as a second parameter, a reducer, and then we also have the special action handler function called handleAsync. Inside it, whenever an action needs to be called asynchronously, we call it using dispatch(actionHandler). That's it.

EDIT: Actually there is more to it. I finally understood everything. So normally in JS, when we have code, it doesn't execute chronologically. Meaning that, when a function is called, we don't wait for it to finish before executing the code below it.

That is what Redux Thunk is trying to solve: I want to be able to execute an action exactly when I finish fetching data from a database. That is why I have to use the special action handler (I am still slightly confused by the code in it though).


13-Review

Review excercise: creating a simple counter:

const INCREMENT = 'INCREMENT'; 
const DECREMENT = 'DECREMENT'; 

const counterReducer = (state = 0, action) => {
  switch(action.type){
    case INCREMENT:
    return state + 1;

    case DECREMENT:
    return state -1;

    default:
    return state;
  }
}; 

const incAction = () =>{
 return {
   type: INCREMENT
 }
}; 

const decAction = () =>{
 return {
   type: DECREMENT
 }
};

const store = Redux.createStore(
  counterReducer); 

14- Immutable State

Immutable state means that you never modify state directly, instead, you return a new copy of state.

Redux does not actively enforce state immutability in its store or reducers, that responsibility falls on the programmer. Fortunately, JavaScript (especially ES6) provides several useful tools you can use to enforce the immutability of your state, whether it is a string, number, array, or object.

Note that strings and numbers are primitive values and are immutable by nature. In other words, 3 is always 3. You cannot change the value of the number 3. An array or object, however, is mutable.

Here is an example where we had to be careful not to modify state (note, we weren't paying attention to this earlier):

const ADD_TO_DO = 'ADD_TO_DO';

// A list of strings representing tasks to do:
const todos = [
  'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code',
];

const immutableReducer = (state = todos, action) => {
  switch(action.type) {
    case ADD_TO_DO:
      // don't mutate state here or the tests will fail
      return todos.concat(action.todo)
    default:
      return state;
  }
};

// an example todo argument would be 'Learn React',
//note that todo is passed as a parameter to this 
//function, it is like a user input
const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo
  }
}

const store = Redux.createStore(immutableReducer);

Note: instead of the return todos.concat(action.todo), we could have selected anyway of adding the item to the array provided it remains immutable:

Hint 1

const means: it cannot change through re-assignment, and it cannot be re-declared. Since objects and arrays are mutable, you can add to it by index (array[3] = 3), by property (object.name=“sam”), by extending (with various array methods)

Hint 2

.push() and .splice() directly modify the array

Hint 3

.concat() doesn’t modify array but just returns a new array

Hint 4

.slice() doesn’t modify array but just returns a new array

Hint 5

spread operator […array] doesn’t modify array but just returns a new array


15- The Spread Operator [...]

The spread operator has a variety of applications, one of which is well-suited to the previous challenge of producing a new array from an existing array.

For example, if you have an array myArray and write:

let newArray = [...myArray];

newArray is now a clone of myArray. Both arrays still exist separately in memory. If you perform a mutation like newArray.push(5), myArray doesn't change. The ... effectively spreads out the values in myArray into a new array. To clone an array but add additional values in the new array, you could write [...myArray, 'new value']

It's important to note that the spread operator only makes a shallow copy of the array. That is to say, it only provides immutable array operations for one-dimensional arrays.

Here is a small challenge where we used the spread operator:

const immutableReducer = (state = ['Do not mutate state!'], action) => {
  switch(action.type) {
    case 'ADD_TO_DO':
      // don't mutate state here or the tests will fail
      return [...state, action.todo]
    default:
      return state;
  }
};

const addToDo = (todo) => {
  return {
    type: 'ADD_TO_DO',
    todo
  }
}

const store = Redux.createStore(immutableReducer);

16-Removing item from array while keeping it immutable

const immutableReducer = (state = [0,1,2,3,4,5], action) => {
  switch(action.type) {
    case 'REMOVE_ITEM':
      // don't mutate state here or the tests will fail
      return state.slice(0, action.index).concat(state.slice(action.index + 1, state.length))
    default:
      return state;
  }
};

const removeItem = (index) => {
  return {
    type: 'REMOVE_ITEM',
    index
  }
}

const store = Redux.createStore(immutableReducer);

note how we didn't use the [...state] syntax here, because both slice() and concat() do not alter original array (they return a new one).

However, we could have also done it this way:

return [
        ...state.slice(0, action.index),
        ...state.slice(action.index + 1, state.length)
      ];


17 - Forcing state immutability when the state is an object

A useful tool for handling objects is the Object.assign() utility. Object.assign() takes a target object and source objects and maps properties from the source objects to the target object. Any matching properties are overwritten by properties in the source objects. This behavior is commonly used to make shallow copies of objects by passing an empty object as the first argument followed by the object(s) you want to copy. Here's an example:

const newObject = Object.assign({}, obj1, obj2);

This creates newObject as a new object, which contains the properties that currently exist in obj1 and obj2.

In the following example, we use Object.assing() to create a new object, make it the same as the previous state, except that we also pass a second object as source object in order to modify the 'status' property inside the state:

const defaultState = {
  user: 'CamperBot',
  status: 'offline',
  friends: '732,982',
  community: 'freeCodeCamp'
};

const immutableReducer = (state = defaultState, action) => {
  switch(action.type) {
    case 'ONLINE':
      // don't mutate state here or the tests will fail
      return Object.assign({}, state, {status: 'online'})
    default:
      return state;
  }
};

const wakeUp = () => {
  return {
    type: 'ONLINE'
  }
};

const store = Redux.createStore(immutableReducer);

GOOD LUCK!