FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Upgrade

Angular to React.js - Data (Part 3 of 6)

Written by 
Cristian Marquez
,
Senior Software Engineer
Angular to React.js - Data (Part 3 of 6)
blog post background
Debugging in React Native: Flipper vs React Native Debugger vs Reactotron
Agile Test Management - Zephyr for JIRA
The Difference Between MongoDB and Firestore, and When You Should Use Each

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:

-- CODE language-jsx keep-markup --
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

-- CODE language-jsx keep-markup --
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:

-- CODE language-jsx keep-markup --
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.

  • Create a new folder “action-types” inside the “store” folder.
  • Create a new file called “cart.js” inside the new folder.
  • Add the following code:

-- CODE language-jsx keep-markup --
const CartTypes = {
  ADD_TO_CART: 'ADD_TO_CART',
  CLEAR_CART: 'CLEAR_CART',
};

exportdefault 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:

-- CODE language-jsx keep-markup --
import types from '../action-types/cart';
const axios = require('axios');

exportconst addToCart = product => {
  return {  
    type: types.ADD_TO_CART,  
    product
  };
};

exportconst 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.

  • Create a new folder "reducers" inside the "store" folder.
  • Create a new file called “cart.js” inside the new folder.
  • Add the following code:

-- CODE language-jsx keep-markup --
import types from '../action-types/cart';

const initialState = {
  items: [],
  shippingPrices: [],
  shippingPricesLoading: false
};

exportdefaultfunction(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:

-- CODE language-jsx keep-markup --
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:

-- CODE language-jsx keep-markup --
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:

-- CODE language-jsx keep-markup --
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:

-- CODE language-jsx keep-markup --
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:

-- CODE language-jsx keep-markup --
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.

-- CODE language-jsx keep-markup --
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:

-- CODE language-jsx keep-markup --
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>
   );
};

exportdefault ProductDetails;

The action is extracted on:

-- CODE language-jsx keep-markup --
const ProductDetails = ({ match, addToCart }) => {

The HTML event that triggers the action is:

-- CODE language-jsx keep-markup --
<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.

Cristian Marquez
Written by
Cristian Marquez
Cristian Marquez

As a Senior Front-End developer at FullStack Labs I focus on building single page web applications using Angular and React.js. I pride myself on my attention to detail, and my ability to build pixel-perfect user interfaces. Prior to FullStack I held a variety of roles at leading Colombian companies, including Globant and Prodigious. I have a Masters of Sciences Degree in Engineering from the Universidad Nacional de Colombia.

FullStack Labs Icon

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 $50,000.

company name
name
email
phone
Type of project
Reason for contact
How did you hear about us?
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.