Thursday, September 13, 2018

[ Part 2 ] Nutracker, ReactJS application - Adding Redux

In the first part we covered how to create components and
how to interact with a mock api to get data for our application.

all of that can be enough to build a react app but it won't scale well
for the following reasons:
1- state will be all over the place  in different container components which means we have to add more layers of components to share common parts of states.
2- the propagation of events can be hard to deal with when the components tree gets deeper and other components (siblings and their children) will probably need to be notified of state changes.


Redux data flow

so there is a library called redux, and redux-react.

this library in simple terms facilitates dispatching application wide events to specific handlers (reducers) that modify the state based on the actions they receive, it then store the whole state after those handlers change it and pass the new state to react framework to rerender our components.
In short: it's an event publish/subscriber tailored to manage a state object.

it worth mentioning that redux itself can be used to manage any state, not only react (state is just a JS object at the end of the day) but redux-react adds helper functions and saves you from writing some boilerplate code that we will see soon.

Part 2 code 

https://github.com/blabadi/react-nutracker/tree/01f265a10363a73f359e45db917306be0e1d6efc



Directories added (to organize but not needed):
- actions
- reducers
Renamed:
components => presentational (not related nor needed to redux)

To think the redux way, we have to look at what our component raise for events
currently we have searchBox on term change, this can be translated to a redux 'action' called SEARCH_FOOD.
Actions have to be objects (by default) and to distinguish each action, we give it a type property which is unique string across the app, they also hold any other properties as needed.
So we want our searchBox to send (dispatch in redux terms) a SEARCH_FOOD action to trigger a call to the mock api and fetch foods that match the user query.

Note that our presentation component (SearchBox) won't change in anyway because it was designed not to need so (see part 1 for details).
 Only our container components will change 
from:
 - managing state locally 
 - being responsible to fetch the data by calling the external api (handle events)
to: 
dispatch actions and receive new state

for the first action it will look like this:

the action is just an object, the function that creates that object is called an action creator.
in our case, it takes term as an argument and returns a simple object .
the SearchBox dispatches that action by using the method: dispatch passed to it from redux store
A lot has changed in this component it no longer has any of the react related code, and the reason is:
that it is now created for us by redux using the connect() helper method from redux-react library.
 
The moment you start using redux our container components become unnecessary to be created manually (repetitive boilerplate code) as they will just have one variant in the code , which is to tell redux what to dispatch on each event (mapDispatchToProps), and how to read the state and pass it to child presentation components (mapStateToProps).

without using the connect utility method we will need something like this

class FoodSearchBox extends Component {
  onTermChange = (term) => {
    store.dispatch(searchFood(term))
  }
  render = () => {
     return <SearchBox  ...
            onTermChange = {this.onTermChange}
            results={toResults(store.getState().searchFoodResults)} />

  }
}

The next question if you look at the code above is where did state.searchFoodResults was created ?
the answer is: in the reducer.

A Reducer is a function that receives (state & action) and returns a new state calculated from the action.

for our case here this is our reducer:



it took the state and if the action is SEARCH_FOOD we filled the state with the mock data.
and returned the new state.

few notes:
- Reducers has to be pure functions (no api calls, no database calls nothing that is not a pure js objects manipulation), in this example I did call foodRepo in the reducer but that's just because it's mock data call, not actual network call, but we will change this next part.
- combineReducers is a method from redux that you pass it all the reducers you have and it will build the state tree out of these. (it's good practice to split reducers based on parts of the state each handles, and not have one gigantic function). if we say want to store user information in the state, we would do so by having a reducer like this :
const user = (state = {}, action) => { ... }
and our state will look like this:

  user: {}, 
  searchFoodResults: [] 
}
and so on, Redux will use the keys (in combineReducers) to build the state and it will also pass that part of the state to that reducer to let it focus on that part of the state so our user reducer will only get a reference to: state.user and not a reference to the whole state tree.

So by now our app hasn't changed functionality wise, but internally it changed a lot, and this is a needed foundation to scale it for a bigger state and more interactions across components.

In the next part I'll explain how we can get rid of the mock data and do an actual async ajax call to an api that will return foods and what will need to change to handle the asynchronous  call.

for more details:
https://redux.js.org/basics/

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Istio —simple fast way to start

istio archeticture (source istio.io) I would like to share with you a sample repo to start and help you continue your jou...