Angular to React - Data (Part 3 of 6)

Written by Cristian Marquez

Welcome back to the third part of my six-part guide for Angular developers looking to learn React. Over the course of the series, we will rebuild Angular’s starter app example in React, covering each section of the Angular Starter docs in a separate article.


In part two, we learned how to leverage the React Router Dom library to implement navigation in React. This article will cover the use of Redux, one of the most popular state management libraries for React.


Let's start by reviewing the features we have implemented so far. At this point, the user can see the product catalog, click on the product name, and navigate to the product details page. In this tutorial, we will expand the app by implementing the following:

  • Include a buy button in the details view to add the current product to include the item into the cart service.
  • Create a new view to display the items available into the cart.

The starter kit for the app can be found here.


Comparing data management in Angular and React


Data management refers to the process of storing and propagating data throughout the application. In React, state can be managed at the component level and passed down to sub components. In Angular, this can be done with a simple variable and the use of the @input and @output system to propagate the data. For small applications, this internal and simple state management might be enough, but most apps require a more robust data management system.


Modern React apps use hooks to manage the state. Take a look at the “useReducer” and “useState” hooks here.


Angular provides such a system with the use of services, which is a flexible way to share and store information. Services allow information to be injected in every component using the Angular injection system. To replicate this in React, we will need something more powerful than the internal state system provided. Although there are numerous data management solutions in React, most apps use Redux.


To get started we will need to install two libraries:

redux

and

react-redux

.


Organizing the App


Before we implement Redux, let’s improve the organization of our app:

  1. Create the following folders: 1) components, 2) containers, and 3) store.
  2. Move all the components to the component folders.
  3. Go to index.js and update the import path:
    
import TopBar from './components/TopBar';
import ProductList from './components/ProductList';
import ProductDetails from './components/ProductDetails';
    
  

Update the product.js path in ProductAlerts.js, ProductDetails.js, and ProductList.js

    
import {products} from '../products';
    
  

Refresh the page, and after the update, the scaffolding of the app should look like:



Define the Cart Service


The concepts of Angular services correlate nicely with concepts within Redux. To get a better idea of why, let’s start by creating the initial state of the application that will store our items:

  1. Create a new folder “reducers” inside the “store” folder
  2. Create a new file called “cart.js” inside the new folder
  3. Add the following code:
    
const initialState = {
 items: [],
};
    
  

As you can see, the initial state is a regular Javascript object that will store our items.


Next we need to define the actions a user can take on the items in the cart. For now we will let them add an item to the cart and clear the cart completely. The first step is creating a name for these actions in a file that we can reference elsewhere.

  1. Create a new folder “action-types” inside the “store” folder.
  2. Create a new file called “cart.js” inside the new folder.
  3. Add the following code:
    
const CartTypes = {
 ADD_TO_CART: 'ADD_TO_CART',
 CLEAR_CART: 'CLEAR_CART',
};
 
export default CartTypes;
    
  

These names are self-explanatory:

CartTypes.ADD_TO_CART

will allow us to add a new item and

CartTypes.CLEAR_CART

will clear the array.


Next, we need to define the actions themselves. In Redux, an action is a function that returns an object that includes a type and a payload. Let’s create the action:

  1. Create a new folder “actions” inside the “store” folder.
  2. Create a new file called “cart.js” inside the new folder.
  3. Add the following code:
            
    import types from '../action-types/cart';
    const axios = require('axios');
     
    export const addToCart = product => {
     return {
       type: types.ADD_TO_CART,
       product
     };
    };
     
    export const clearCart = () => {
     return {
       type: types.CLEAR_CART,
     }
    };
          
        

    Next up we need to create the reducer. Reducers are functions that return a new state of the application when an action is dispatched. How to modify the store is determined by which

    action-type

    was dispatched. You could see this as the new state generator.

  4. Create a new folder "reducers" inside the "store" folder.
  5. Create a new file called “cart.js” inside the new folder.
  6. Add the following code:
            
    import types from '../action-types/cart';
     
    const initialState = {
     items: [],
     shippingPrices: [],
     shippingPricesLoading: false
    };
     
    export default function(state = initialState, action) {
     switch (action.type) {
       case types.ADD_TO_CART: {
         const { product } = action;
         const { items } = state;
     
         window.alert('Your product has been added to the cart!');
         return {
           ...state,
           items: [...items, product]
         };
       }
     
       case types.CLEAR_CART: {
         return {
           ...state,
           items: []
         };
       }
     
       default:
         return state;
     }
    };
            
          

If multiple files use the same strings, creating a map is a good practice.


Allow React to Use the Store


Now that we’ve setup a redux store, we need to integrate it into the app:

  1. Go to index.js
  2. Add the following code:
            
    import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
    import { Provider } from 'react-redux';
    import ReduxThunk from 'redux-thunk';
     
     
    const rootReducer = combineReducers({
     cart: cartReducer,
    });
     
    const logger = store => {
     return next => {
       return action => {
         console.log('[MiddleWare] Dispatching', action);
         const result = next(action);
         console.log('[MiddleWare] next state', store.getState());
         return result;
       };
     };
    };
     
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
     
    // You can apply multiple middleware separated by ,
    const store = createStore(rootReducer, composeEnhancers(applyMiddleware(logger, ReduxThunk)));
            
          

The store will be available in the variable “store”. One way to think about the store is as Angular Services. This means:

  1. You could define some variables to store the information (initial state)
  2. You could create methods to edit the state (Reducers + actions).

Finally, use the

<Provider>

; component from the “react-redux” library to “provide” the store to the application. Let's see this code in action:

  1. Go to index.js
  2. Wrap returned JSX with the Provider.
  3. Your code should look like:
            
    import { Provider } from 'react-redux';
     
    ....
     
    render() {
      return (
        <Provider store={store}>
          <Router>
            <TopBar />
            <div className="container">
              <Route exact path="/" component={ProductList} />
              <Route path="/products/:productId" component={ProductDetails} />
            </div>
          </Router>
        </Provider>
      );
    }
            
          

Injecting the service into a Component


To inject our service into each component, we need to use the connect function available in the react-redux library. Components that interact with redux are usually called Container components, while other components are called Presentational components. This separates concerns between interacting with the store and defining the UI. You can read more about this distinction here.

Let’s separate Cart and ProductDetails into containers and presentational components:

  1. Go to the containers folder.
  2. Create Cart.js and ProductDetails.js containers.
  3. Add the following code inside the Cart.js file:
            
    import React from 'react';
    import { connect } from 'react-redux';
     
    import Cart from '../components/Cart';
     
    import * as actions from '../store/actions/cart';
     
    const Cart = props => {
     const { products } = props;
     
     return <Cart products={products} {...props} />
    }
    const mapStateToProps = state => {
     return {
       products: state.cart.items,
     };
    };
     
    export default connect(
     mapStateToProps
    )(Cart);
            
          

Above we are explicitly extracting the products from the props, but most of the times the containers component will propagate the props with the following approach:


   
return <Cart {...props} />
    
  

The

mapStateToProps

function merges the props of the component with the data present in the store. All this setup will allow the view component to access the values of the store and react to any changes.


Interacting with the store


To interact with the store, we need to provide a second parameter to the connect function. Let’s use the DetailsComponent container as an example:

  1. Go to the container folder
  2. Go to the ProductDetails.js file.
  3. Add the following code:
            
    import React from 'react';
    import { connect } from 'react-redux';
     
    import ProductDetails from '../components/ProductDetails';
     
    import * as actions from '../store/actions/cart';
     
    const ProductDetails = props => {
     const { productId, addToCart } = props;
     
     return <ProductDetails productId={productId} addToCart={addToCart} {...props} />
    }
     
    const mapStateToProps = state => {
     return {
       items: state.cart.items,
     };
    };
     
    const mapDispatchToProps = dispatch => {
     return {
       addToCart: product => dispatch(actions.addToCart(product)),
     };
    };
     
    export default connect(
     mapStateToProps,
     mapDispatchToProps
    )(ProductDetails);
            
          

The

mapStateToProps

function returns an object of methods that interact directly with the store. These actions are available directly in the props of the view component using the connection created in the container component. This means that when executing the

addToCart

function into the ProductDetails component, redux will generate the new state and inform all the components.


Injecting the service into a Component


Now let’s update the components associated with each Route to point to the new Container components.

  1. Go to the index.js
  2. Include the containers into the Route.
    
import ProductDetails from './containers/ProductDetails';
import Cart from './containers/Cart';
 
  render() {
   return (
     <Provider store={store}>
       <Router>
         <TopBar />
         <div className="container">
           <Route exact path="/" component={ProductList} />
           <Route path="/products/:productId" component={ProductDetails} />
           <Route exact path="/checkout" component={Cart} />
         </div>
       </Router>
     </Provider>
   );
 }
    
  

Updating Items with Redux


With all this setup, actions are now available directly in the props of our view component. To add a new item in the cart, we should extract the action from the props and create a button that will execute that action

onClick

. The

ProductDetails.js

component should be updated as follows:

    
import React from 'react';
 
import { products } from '../products';
import { currency } from '../utils';
 
const ProductDetails = ({ match, addToCart }) => {
 const {
   params: { productId }
 } = match;
 const product = products[productId];
 
 return (
   <div className="product-deatils">
     <h2>Product Details</h2>
     <div>
       <h3>{product.name}</h3>
       <h4>{currency(product.price)}</h4>
       <p>{product.description}</p>
       <button onClick={() => addToCart(product)}>Buy</button>
     </div>
   </div>
 );
};
 
export default ProductDetails;
    
  

The action is extracted on:

    
const ProductDetails = ({ match, addToCart }) => {
    
  

The HTML event that triggers the action is:

    
<button onClick={() => addToCart(product)}>Buy</button>
    
  

Solutions

  1. The Angular solution to this tutorial can be found here.
  2. The React solution to this tutorial can be found here.

What’s next?

Although we’ve implemented a good amount of the Angular app in React, we’re still missing the shippings view. In the next article we will cover async actions and HTTP libraries. This might seem like a lot of boilerplate to get the functionality required in React but once you get familiar with the concepts and ideas of Redux and React, you will end up adding redux libraries to your Angular apps or creating your custom implementation of a store like service.


---
At FullStack Labs, we pride ourselves on our ability to push the capabilities of cutting-edge frameworks like React. Interested in learning more about speeding up development time on your next project? Contact us.

Let’s Talk!

We’d love to learn more about your project. Contact us below for a free consultation with our CEO.
Projects Start at $25,000.

FullStack Labs
This field is required
This field is required
Type of project
Reason for contact:
How did you hear about us? This field is required