About the Author:

How to Build a Simple Chat App with React Native and Firebase

November 30th, 2017

Nick is a mobile developer with wide experience in building iOS and Android applications at RubyGarage. He enjoys researching tech topics and sharing his knowledge with other developers.

In mobile development, cross-platform applications are appreciated for their short development cycle, low cost, and quick time to market in comparison with native apps.

One popular framework that enables developers to build hybrid mobile apps is React Native. React Native was created by Facebook developers during a hackathon back in 2013. Since then, the framework has become a core technology for numerous mobile applications including Instagram, Skype, Tesla, Airbnb, and Walmart. (more…)

About the Author:

Build a Camping Weather App with React, Redux and Thunk Middleware

February 7th, 2017

Let’s face it, while it’s fun to write applications from scratch, a lot of software development work involves working with existing code. In this post, we are going to learn how to add a feature to an existing React/Redux app. Following on our previous articles in <list filtering with react/redux> and <component interactivity with react/redux>, we will add a feature to get a weather forecast estimate to help us figure out when is the best time to visit crater lake. In addition, since we are querying an API, we will be learning how to use async actions to retrieve and display the weather.

Here’s an outline of the process we will be following

  1. Adding a date selector component to let the user pick a date for getting the weather.
  2. Getting weather information from the <DarkSky api>
  3. Identifying where and when to update state to incorporate our new weather feature
  4. Handling state changes with async interfaces

Check out the final product on Heroku and get the code on Github.

finished image.png

Adding a date selector

Using the react-datepicker module we’ll create a new WeatherDatePicker component to show the date picker and the forecast results. When the user loads the page they will see “Select a date…”:

date picker.png

After a date is selected, the forecast will be displayed:

date picked.png

Taking a look at the render method in WeatherDatePicker.jsx:

render() {

 return (

 <div>

 Travel Date:

 <DatePicker

 placeholderText="Select a date..."

 selected={this.props.selectedDate}

 dateFormat = "YYYY-MM-DD"

 onChange={(selectedDate) => {

 let newDate = this.checkDate(selectedDate)

 if (newDate) {

 this.props.fetchWeather(this.checkDate(selectedDate), this.props.currentLat, this.props.currentLong)

 }

 } }/>

 Forecast: {this.props.weatherSummary}

 </div>

 );

}

When the DatePicker is opened and a date is selected, firing the onChange event, we first check the date to make sure it is in the future:

checkDate(selectedDate) {

 if (selectedDate.isBefore(this.props.currentDate)) {

 alert("Please pick a date in the future.")

 return undefined

 }

 else {

 return selectedDate.format("YYYY-MM-DD")

 }

}

The DatePicker module uses moment.js for the date object. We can use moment’s isBefore method to compare two moment objects, and display an alert if the user picks a date in the past. If the render method receives a valid result, it calls the delegate fetchWeather, passing along the selected date, to retrieve the weatherSummary.

Now we will add the WeatherDatePicker component and the DarkSky attribution graphic to CampFilterApp.jsx:

render() {

 return (

 <div className="container">

 <div><a href="https://darksky.net/poweredby/"><img src="https://darksky.net/dev/img/attribution/poweredby.png" style={{ width: 100 }}/></a></div>

 <Jumbotron>

 <h1>Crater Lake Camping</h1>

 </Jumbotron>

 <br></br>

 <CampFilterList {...this.props}/>

 <br></br>

 <WeatherDatePicker {...this.props}/>

 <br></br>

 <CampMapContainer {...this.props} />

 </div>

 )
};

While we are working with components, let’s also update the Redux store to include the thunk middleware that we’ll be using for the next steps. We just need to add thunkMiddleware to our createStore call. In index.js:

import thunkMiddleware from 'redux-thunk'

const store = createStore(reducer, applyMiddleware(

 thunkMiddleware

))

Adding state and async actions for the weather feature

Now that we have a component to display the weather, lets look into the associated actions and state needed to do the work.

For the weather feature, the following fields are added to the state in index.js and set as props in CampFilterApp.jsx:

currentDate: today,

weatherSummary: "",

currentLat: 42.9456,

currentLong: -122.2,

selectedDate: undefined

The DarkSky API needs a position and date to retrieve the weather. Since our app is localized to Crater Lake we’ll use those coordinates to retrieve the weather. Don’t forget to convert these new state variables to props to send down to the WeatherDatePicker component in CampFilterApp.jsx:

function mapStateToProps(state) {

 return {

...

 weatherSummary: state.get('weatherSummary'),

 currentDate: state.get('currentDate'),

 currentLat: state.get('currentLat'),

 currentLong: state.get('currentLong'),

 selectedDate: state.get('selectedDate')

 };

}

To query the API and update the selectedDate and weatherSummary variables we will use thunk middleware and fetch as described in this article from the Redux docs.

To handle an async action, we will split the API call into request and receive actions. Our request action, REQ_WEATHER, will update the selectedDate to the value passed from the DatePicker and weatherSummary to “Loading…” to let the user know the weather info is loading. From action_creators.js:

export function reqWeather(weatherDate) {

 return {

 type: 'REQ_WEATHER',

 weatherDate

 }

}

And the corresponding function in reducer.js:

function reqWeather(state, weatherDate) {

 return state.merge(Map({

 'weatherSummary': "Loading...",

 'selectedDate': weatherDate

 }))

}

For the receive action, RECV_WEATHER, we add a response field to our state to hold the JSON returned by the DarkSky API:

export function recvWeather(weatherDate, result) {

return {

type: 'RECV_WEATHER',

weatherDate,

response: result

}

}

The corresponding method in reducer.js for parsing the API response into the weatherSummary field:

function recvWeather(state, weatherDate, result) {

 let content = ""

 try {

 let tempMax = result.daily.data[0].temperatureMax

 let tempMin = result.daily.data[0].temperatureMin

 let summary = result.daily.data[0].summary

 content = summary + " High: " + Math.ceil(tempMax) + " Low: " + Math.ceil(tempMin)

 }

 catch (err) {

 console.log("couldnt get weather summary: " + err)

 }

 return state.merge(Map({

 'weatherSummary': content

 }))

Alright, at this point you may be wondering “This is great and all, but where’s the part where we actually query the API?” Excellent question! That’s next.

Now that we have our request and receive actions setup, lets combine them in the fetchWeather action, which if you recall, is what is getting called from the WeatherDatePicker onChange event:

export function fetchWeather(weatherDate, currentLat, currentLong) {

 let request_url = "https://crossorigin.me/https://api.darksky.net/forecast/8266ff95ef9bbfccf0ea24c325818f31/"

 let weather_str = weatherDate.format("YYYY-MM-DD") + "T00:00:00"

 request_url = request_url + currentLat + "," + currentLong + "," + weather_str

 return function (dispatch) {

 dispatch(reqWeather(weatherDate))

 return fetch(request_url)

 .then(response => response.json())

 .catch(error => {

 console.log("unable to get weather " + error)

 })

 .then(respData => {

 dispatch(recvWeather(weatherDate, respData))

 })

 .catch(error => {

 console.log("unable to parse weather result " + error)

 })

 }

}

Stepping through this piece by piece – first we are constructing the URL for the DarkSky request. As explained in the Redux article, this is where the thunk middleware comes into play; this is what allows the fetchWeather action to return a function instead of an object.

The first thing the returned function from fetchWeather does is dispatch the reqWeather action to update the UI showing that we are requesting the weather. It then fetches the response from the DarkSky API, dispatching the recvWeather action if it receives a valid result.

Now, we have all the plumbing hooked up to retrieve the weather based on the date chosen in the date picker. Give it a try and download the code. If you want to make updates and deploy your own Heroku app make sure to link the create-react-app buildpack in Settings.

Some next steps to try:

  • Allow the user to get customized forecasts for each campground by modifying the CampListItem component to fetch the weather, as opposed to retrieving a general area forecast as we did in this post.
  • When querying the DarkSky API instead of just getting a daily forecast, look for a week before and a week after and get an average, high, and low temp to give a better idea of seasonal weather patterns

About the Author:

Google Maps in React / Redux: Interactivity Across Different Components

January 20th, 2017

Rich UIs provide users with a variety of ways to interact. For example, HipCamp, a platform for discovering camping opportunities, uses a combination of a map-based UI and an informative list of campgrounds:

hipcamp.png

This split UI gives users an idea of location through identifying the campground on a map, while keeping the map tidy by using the marker info windows as basic identifier, keeping the more verbose content within the campground list.

Explore React Courses

In this post we will build on our previous learnings in list filtering through state management in React/Redux by adding a map to the UI. We will see how to use state to link the list and map views together, enabling the user to leverage the information of both UI elements when interacting with one or the other. We will also extend the filtering functionality to the markers by connecting the map components to the same state elements as the filter components. The resulting app will filter both the markers and the campground list, and provide users with visual cues when interacting with one or the other. Clicking on a marker will pop up an info window showing the campground name and also highlight the corresponding list item by changing the border. Likewise, clicking a list item will open the info window for the corresponding marker and change the item border:

map.png

You can get the code for this app on GitHub and interact with it live at this Heroku demo app. We will continue using the google-maps-react npm module and some of the techniques outlined on Full Stack React for working with this package. All of our state management will be done via Redux, as opposed to at the component level in the Full Stack React article.

Adding the Map

Picking up where we left off in the previous post, we will add a google maps container to CampFilterApp.jsx:

render() {

 return (

 <div className="container">

 <Jumbotron>

 <h1>
 Crater Lake Camping
 </h1>

 </Jumbotron>

 <br></br>

 <CampFilterList {...this.props }/>

 <br></br>

 <CampMapContainer {...this.props }/>

 <br></br>

 <CampList {...this.props }/>

 </div>

 )
};

The CampMapContainer component will serve as a wrapper for the google maps components, and a point of entry for the GoogleApiComponent provided by google-maps-react. First lets look at the render method for CampMapContainer.jsx:

 

render() {

 return (

 <div>

 <CampMap google={this.props.google}>

 {this.props.markers.map(marker =>

 <Marker

 key={marker.get('title') }

 title={marker.get('title') }

 description={marker.get('description') }

 properties={marker.get('properties') }

 position={marker.get('position') }

 mapOn={marker.get('mapOn') }

 addMarker={this.props.addMarker}

 onMarkerClick={this.props.onMarkerClick}/>

 ) }

 <InfoWindow {...this.props}

 marker={this.props.activeMarker}

 visible={this.props.showingInfoWindow}>

 <div>

 <h4>{this.props.selectedTitle}</h4>

 </div>

 </InfoWindow>

 <CampList {...this.props}/>

</div>

 ) }

We have four components inside the container – CampMap is a container for the map itself, which contains a marker component for each marker in our markers property and an InfoWindow component to be set based on the activeMarker property. We will discuss these components more in detail in a bit.

After the class definition in CampMapContainer.jsx, we will add an export for the google maps component:

let key = config.getGoogleKey()

export default GoogleApiComponent({

apiKey: key

})(CampMapContainer)

You can use the API key included in the config supplied with the config.js file for demonstration purposes, but please don’t abuse it! You can use the google-maps-react package without an API key, but by providing one we receive a google property which can be passed to the components to create markers, info windows, and maps as we will see shortly. Notice that the <CampMap> component in the render method above makes use of the google property.

Lets dive into the Google Maps components.

The CampMap Component

This component sets up our map and renders the Marker components. In the render method, we set the min size for the map and call the renderChildren function. You can check out that code in CampMap.jsx. Essentially, it will render any children of the map object, in our case the Marker components, passing on the google and map properties.

render() {

 const style = {

 minWidth: '400px',

 minHeight: '400px'

 }

 return (

 <div className="row">

 <div style={style} ref='map'>

 {this.renderChildren() }

 Loading map...

 </div>

 </div>

 )

}

Recall above that we passed the google property to this component. The API connection is asynchronous, meaning that initially this property will be undefined. When the API returns, the google property will be updated, resulting in a ComponentDidUpdate event on the CampMap component. This has two impacts:

  1. When the render method is initially called, and the Markers are rendered as a result, the map property passed to the Markers in renderChildren will be undefined. This means the markers will not be attached to the map since it does not yet exist. In order to attach the markers to the map we will need to renderChildren again after the API returns.
  2. We need to wait until the API returns a valid google property to load the map.

To account for this, we will handle the ComponentDidUpdate event in the CampMap component, calling both the loadMap function that draws the google map and forceUpdate, which will force the component to re-render including the call to renderChildren. We will only do this if the google property has been updated, to avoid re-rendering for other cases where componentDidUpdate is fired. The loadMap function sets the map parameters and attaches it to the DOM, see CampMap.jsx.

componentDidUpdate(prevProps, prevState) {

 if (prevProps.google !== this.props.google) {

 this.loadMap();

 this.forceUpdate()

 }

}

The Marker Component

Now that we have our map created, let’s take a look at the Marker component, which receives the google and map properties from its parent component, CampMap, in addition to these properties defined in CampMapContainer:

<Marker

key={marker.get('title')}

title={marker.get('title')}

description={marker.get('description')}

properties={marker.get('properties')}

position={marker.get('position')}

mapOn={marker.get('mapOn')}

addMarker={this.props.addMarker}

onMarkerClick={this.props.onMarkerClick}/>

Because google maps renders components directly to the DOM, the render method of this component simply returns null. Instead, we render the marker when there is a change in the map property or in the marker properties or mapOn properties. We are only looking at changes in these specific marker properties because the others are static in our application. See the ComponentDidUpdate method in Marker.jsx

You’ll notice that we are passing two actions down to the Marker component; addMarker and onMarkerClick. The addMarker action is used to create a list of google marker objects as part of the Redux state. This marker array will be used to allow interactivity between the markers and the campground list items. The onMarkerClick action will be used to set the info window content. We will discuss these actions more in detail later on. The InfoWindow component implementation is from the Full Stack React Google Maps article, which you can reference to understand the lifecycle of this component.

The CampListItem Component

We need to make a few changes to our CampListItem component from the last post to change the border of the selected element and enable opening the corresponding marker’s info window. Let’s take a look at the image in the CampListItem component render method:

<img src={img_url} alt="campground" ref="cg_image" style={{width:200, height:100}} onClick={() =>this.props.onMarkerClick(this.getMarker(this.props.title))}></img>

We’ve added a ref property to the image which we will use to set the border style when the item is selected. We’ve also added a click handler to call onMarkerClick with the current marker. This is the same onMarkerClick called by the Marker component, which will set the info window content and open/close it as required. Notice that we are calling a local function getMarker to retrieve the current marker for the onMarkerClick parameter. First let’s take a look at setting the style with the ref property in CampListItem.jsx:

componentDidUpdate(prevProps, prevState) {

if (this.props.activeMarker !== prevProps.activeMarker) {

let img_ref = this.refs.cg_image

if (this.props.showingInfoWindow && (this.props.selectedTitle === this.props.title)) {

img_ref.style.border = "3px solid black"

}

else {

img_ref.style.border = null

}

}

}

On ComponentDidUpdate we check to see if the activeMarker field has changed, and if so we proceed with changing the image border. Next, we will either add the border if this item is selected, or remove it if it is no longer selected. If it turns out that the info window should be shown and the selectedTitle property matches the title of the current marker we will update the border to “3px solid black”, otherwise we set the border to null.

Recall that we have been using addMarker to create an array of google maps markers as part of our Redux state whenever a new marker is rendered. We now have two arrays of markers as part of our state, from index.js:

function set_state(campgrounds) {

 store.dispatch({

 type: 'SET_STATE',

 state: {

...

 markers: campgrounds,

 gmapMarkers: [],

 ...

}

})

}

The markers array is our geoJSON-based markers used for filtering and displaying data. The gmapMarkers array is for the google marker objects we need for the map markers. Both sets of markers have a title field that can be used to link the two. In the CampListItem component, the marker property is from the markers array. The onMarkerClick action needs a marker from the gmapMarkers array in order to attach the info window to the correct marker, so we have a helper function getMarker that retreives the matching gmapMarkers element to provide to onMarkerClick:

getMarker(title_match) {

 let match_list = this.props.gmapMarkers.filter(item =>

 item.get('title') === title_match

 )

 if (match_list) {

 return match_list.first()

 }

 else {

 return null;

 }

}

Where this.props.title is provided as the title_match parameter.

Now that we’ve discussed the component hierarchies, let’s take a look at how to hook up these actions in Redux.

Redux Implementation

Remember we first need to add our new actions to the action_creators.js file, which you can review here. Next, we add the following to our reducer.js file to handle these new actions:

function onMarkerClick(state, marker) {

 return state.merge(Map({

 'activeMarker': marker,

 'selectedTitle': marker.get('title'),

 'showingInfoWindow': true

 }))

}

function addMarker(state, marker) {

 let markers = state.get('gmapMarkers')

 let newMarkers = markers.push(marker)

 return state.update('gmapMarkers', oldmarkers => newMarkers)

}

Whenever the markerOnClick event is fired, we want to show the info window, so showInfoWindow is set to true. activemarker and selectedTitle are set based on the marker passed up from a CampListItem or Marker component. For addMarker, we simply add the new marker to the existing gmapMarkers array. Don’t forget to add the new actions to the reducer:

export default function (state = Map(), action) {

 switch (action.type) {

 case 'SET_STATE':

 return setState(state, action.state);

 case 'CHANGE_FILTER':

 return changeFilter(state, action.filter);

 case 'MARKER_CLICK':

 return onMarkerClick(state, action.marker)

 case 'ADD_MARKER':

 return addMarker(state, action.marker)

 default:

 return state

 }

}

One last change to make in the reducer, and this is for the changeFilter action. Recall that we used a mapOn property in the Marker component:

if (!this.props.mapOn) {

 this.marker.setMap(null);

}

else {

 this.marker.setMap(map)

}

The mapOn property was added to the geoJSON-based markers array to set marker visibility based on the filter settings. Adding the following to changeFilter in reducer.js will set mapOn to true of the marker should be shown:

let markers = state.get('markers')

let updatedMarkers = markers

markers.forEach(marker => {

 let markerIndex = getMarkerIndex(state, marker.get('title'))

 let mapOn = true

 active_filters.forEach(item => {

 if (marker.get('properties').get(item.get('id')) !== true) {

 mapOn = false

 }

 })

Based on the mapOn property we can “remove” the markers not to be displayed based on the current filter settings by marker.setMap(null) if mapOn is false.

And that’s a wrap! Using the gmapMarkers property and invoking the onMarkerClick action from both the Marker and CampListItem components we have enabled opening of the InfoWindow from clicking on the CampListItem image and highlighting the CampListItem image as a result of clicking on the Marker. The additional field mapOn enables us to filter both the campground list and the markers.

About the Author:

Mapping Colorado’s 14er Mountains: React and Redux Mega Tutorial

October 20th, 2016

appendTo is based out of Colorado, a US state famous for the quantity and size of its mountains. 53 mountains over 14,000 feet (4267.2 meters) are located in Colorado’s borders. During the summer, many Colorado residents make it a mission to climb as many of these ’14ers’ as they can.

In this React and Redux tutorial, we will be creating an interactive map application of Colorado’s 14ers using React and Redux. No previous knowledge of Redux is required, but a basic understanding of React concepts such as the state and components will be very helpful.

Here’s a demo of the app that we’re going to build (for the sake of not making this React tutorial longer, we did not make this responsive yet)

Explore React Courses

react tutorial demo app final

Here’s an outline of what we will be doing in this React tutorial:

  1. Redux Concepts
  2. Setting Up the Project
    1. Installing Third-party Packages
  3. Accessing the App
  4. Building the App
    1. Actions
    2. Reducers
      1. currentSelectionsReducer
      2. searchReducer
      3. index
    3. Components
      1. Main Component
      2. VisibleMarkers Container
      3. VisibleCards Container
      4. MarkerList Component
      5. CardList Component
      6. SearchFilters Component
    4. Helpers

The final code is available on this Github repository

Redux Concepts

Many developers have found Redux harder to grasp than React. Before we build out the React components, we’re going to cover how Redux will be used in this application. Here are the 4 primary Redux concepts that this demo app will use:

  • State – The state is data that needs to be updated during the life cycle of a component. It contains information on the current state of the component. For example, if you have a switch that is toggled on, you might store something like switch_value in the state and then assign it a value of true.
  • Store – The store is a big JavaScript object that represents the state of the entire app. It is where you save, update, and retrieve the data that needs to be accessible throughout the whole app.
  • Actions – The specific actions in your app which allow you to update the store. For example, you can have a SWITCH_ON action which turns on a <Lightbulb> component.
  • Reducers – Reducers specify how the state may change, given a specific action. These accept the data provided by the action and use it to update the store.

To bring all these concepts together, let’s explain using a metaphor with real-world objects. This will be a simplified explanation of Redux.

Imagine you have a <Switch> component and a <Lightbulb> component. They’re independent of each other but they’re both used in your app. The <Lightbulb> component will not be aware of the changes in the state of the <Switch> component. They are separate and their state won’t affect each other. So even if you change the switch_value to true within your <Switch> component, the <Lightbulb> component won’t actually turn on. We need a way for these separate components to be able to draw from the same state. This is where Redux and ‘state management’ come in. Redux gives us a ‘store’ that can be accessed throughout the whole app. The Redux store is the single source of truth of the current switch_value.

We now have a store to serve our single source of state/data/truth. We need a way to update the store and update it in such a way that all the relevant components will be notified of the change. Those components can then update their own state and re-render. This is where Actions and Reducers come in. Actions are used to create a payload of information to update the store. Reducers are the part of Redux who receive this data. Reducers specify how the store will be updated based on the action type (switch_value) and the payload.

Redux provides the functionality to create the store, dispatch actions and subscribe to changes in the store. This is how the different components in the app get updated.

To better understand how Redux works, check out the following diagram:

redux diagram

In our demo app, a user can hover over a mountain card. Everything starts from a specific component. In this case, it’s a card component which displays the details of a mountain. When the user hovers over a card, we use React’s event system to listen for the mouseEnter event. When this event is triggered, an action (plain-old JavaScript object) containing the type and details of the action is dispatched. In this case, the only detail that we need to submit is the id of the card being hovered over.

component to action

This action goes to the store and the store executes the corresponding reducer based on the type that was specified in the action. The reducer’s job is to determine and return the new state of the store based on the details of the action. Here we can see that the reducer is fetching additional details based on the id and passes this new data back to the store. The store is then updated with this new data under the currently_hovered_card object.

action to store

Next, the store provides the default state and methods that allows the components to subscribe to changes in the store and dispatch actions that can be used to update it. This is made possible by the Provider component that comes with Redux. When the store is updated, we can execute a code that will allow us to update the state of the component involved. As you already know, React automatically re-renders the whole component if the state changes so updating the state updates the UI as well.

store to provider

Later on, you’ll also see that there are two kinds of components: containers (smart components) and plain components (dumb components). The provider component passes down the state to the containers, and the components handle the rendering of the UI.

provider to components

That’s the bird’s eye view of how Redux works, as we go through this tutorial, you’ll learn all about the details of how each of the piece is implemented.

Setting Up the Project

To quickly get started with setting up the project, we’ll be using Create React App. This allows us to install and setup all the tools required in creating a React project.

The first thing that you need to do is to install the package globally:

npm install -g create-react-app

Once that’s installed, you can now create a new project using the create-react-app command:

create-react-app 14ers-redux

Enter the newly created folder and start the development server:

cd 14ers-redux/
npm start

The root directory of the project contains the following files and folders:

  • node_modules – contains the project dependencies. Most of the time you won’t really need to touch this directory.
  • public – contains the files that are served by the server.
  • src – contains the source files of the project. The different components, reducers, and other React files are stored here. This is where we will be primarily working on, so anytime you see a file path, assume that this is the root directory.
  • package.json – contains information regarding the installed packages and the scripts that you can execute inside the project. Here are the one’s included by create-react-app:
"react": "^15.3.2",
"react-dom": "^15.3.2",

react is the main react library, while react-dom is the one that allows you to attach react components into the DOM.

Installing Third-party Packages

Aside from the one’s installed by create-react-app, we’re also going to need the following:

"picnic": "^6.2.4",
"react-google-maps": "^4.11.0",
"react-input-range": "^0.9.2",
"react-redux": "^4.4.5",
"redux": "^3.6.0"

picnic is used to beautify things. This is pretty much like bootstrap but more lightweight.

react-google-maps is used for rendering a Google map.

react-input-range is used for creating slider components that will be used for the filter controls.

redux is used for storing and managing state used by the whole app.

react-redux is used for easily working with Redux within a React app. Redux is a framework agnostic library for working with state. This means that it’s not something that’s exclusive to React. That’s why we need react-redux to provide the functions necessary for us to work with Redux inside the React environment.

Accessing the App

You can access the app at the following url: https://localhost:3000. By default it should show the default page:

create-react-app default page

Every time you make a change to any of the files inside the src directory, it will trigger a recompilation of the source files and a page refresh. This is nice because you can easily see your output whenever you make a change. Create-react-app also comes with ESlint and an error reporter so you can easily see the errors and warnings in your code.

Adding the Project Assets

So that you’ll have some content to work with, first create a photos folder inside the public directory, then download the Github repo of this project. Copy the contents of the public/photos directory of your download to the public/photos directory of the project.

While still inside the public directory, create a css folder and inside create a style.css file then add the following code:

body, html {
  height: 100%;
  width: 100%;
}

#root, .App {
    width: 100%;
    height: 100%;
}

.hidden {
    display: none;
}

#filters-container {
    width: 60%;
    margin: 0;
    padding: 0;
}

#search-results {
    height: 85%;
    overflow-y: scroll;
    padding: 20px;
    margin-top: 100px;
}

.query-container label {
    font-size: 15px;
}

ul.ranges li {
    list-style: none;
    padding: 0 10px;
}

ul.ranges {
    padding: 0;
    margin: 0;
}

.search-filters {
    position: absolute;
    z-index: 10;
    background-color: #f5f4f4;
    width: 60%;
    padding-top: 20px;
    border: 1px solid #dadada;
    margin: 0 2px;
}

.search-filters > div {
    padding: 0 20px;
}

.InputRange {
    margin-bottom: 50px;
}

.InputRange-labelContainer {
    font-size: 15px;
    color: #6b6b6b;
}

.label-text {
    font-size: 15px;
    margin-bottom: 12px;
    font-weight: bold;
}

.card-container {
    padding: 10px;
}

.photo {
    width: 100%;
    height: 150px;
}

.photo-container {
    text-align: center;
}

.name {
    font-weight: bold;
    font-size: 20px;
    line-height: 20px;
}

.range {
    font-size: 15px;
    color: #2d2d2d;
}

.details {
    font-size: 13px;
    color: #656262;
}

.details ul li {
    margin: 0;
    padding: 0;
}

.details-container {
    padding: 10px;
}

.details ul {
    padding-left: 20px;
}

.range-container {
    padding: 0 50px;
}

.is_hidden {
    display: none;
}

.card:hover {
    border: 2px solid orange;
}

Next, inside the project root, create a data folder and inside create a filters.js and mountains.js file. The contents for these files are available on the Github repo. Use the following links to view the contents of the files:

  • filters.js – The default data for the query and filters.
  • mountains.js – The mountains data which we will be working on.

Open the index.html file and right before the closing <head> tag, link to the CSS file that you’ve just created. While you’re at it, also change the value of the <title> tag and link to the Google Maps Script. Don’t forget to replace GOOGLE_API_KEY with your actual Google Maps API key. If you don’t have one, you can get it from Google Console.

    14ers Redux Clone%MINIFYHTML06efd1d957d4364b977c11e0f877469515%

Building the App

Navigate inside the src directory and open the index.js file. This file is responsible for rendering what you saw on the browser earlier. Delete all of its contents and add the following:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'; //import main component

import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from './reducers/index';
let store = createStore(reducer);

import './index.css'; //add some css

ReactDOM.render(
  (
      
          
      
  ),
  document.getElementById('root')

Lines 1 and 2 should already make sense to you, but in case not, you need to import react every time you make use of jsx on the current file and react-dom to render it on the DOM.

On line 5, we’re importing the Provider component from Redux. This is a root-level component which allows us to attach the store on the component. This is the main thing that allows us to have access to the store throughout the entire app. This works by having it passed down to children components by means of the context. Later on, you’ll see how to access the store from a child component, and how to use it to dispatch and subscribe to actions that allows us to update the UI for certain parts of the app.

On lines 6 to 8, we’re importing the createStore method from Redux, and the reducers, to create a store.

On lines 12 to 18, we’re rendering the main component inside the div with the id of root.

Actions

The actions file (actions/index.js) serves as the repository for all the actions that can be performed throughout the app. Actions are plain JavaScript objects which describes what happened in the app. For example, in a grocery list app, you might have the following action when the user clicks on the “Add Item” button:

{
    type: 'ADD_ITEM',
    name: '2 dozen eggs'
}

When the user has put the item into their basket, we can have another action:

{
    type: 'ADD_TO_BASKET',
    id: 100 //auto-generated ID before the item was added
}

Basically anything that can happen inside your app can have its corresponding action. As you have noticed, actions always have a type property. This is a required property as it describes what a specific action is doing. This is how the Reducers know what to do when it receives a specific action.

The functions in this file will be called from the different components later on to perform updates to the store. It returns an object containing the data passed in as arguments, together with the type of action. The reducers that we will create later are the one’s who will utilize the data returned by the actions. This is why the number of functions in the actions file should be equal to the number of reducers that we will create later on.

We can easily omit this file and simply supply the type every time we dispatch an action, but we would be violating the programming principle that there should only be a single source of truth. Having all the actions in a single file would give a general idea to new project members on how the state is updated and what parameters are required to update it.

export const updateSelection = (key, position, showInfoWindow) => {
  return {
    type: 'UPDATE_SELECTION',
    key, 
    position,
    showInfoWindow
  }
}

export const updateQuery = (query, show_filters, filters) => {
  return {
    type: 'UPDATE_QUERY',
    query,
    show_filters,
    filters
  }
}

Putting all the actions in a single file would work for a small app like this, but it could easily become convoluted once you work on a fairly complex app. Remember to split up related actions into different files if your actions file is already trying to do too much.

Reducers

In Redux, Reducers are responsible for specifying how the store changes when a specific action is received. They’re the ones who utilize the data passed by the actions in order to update the store. They represent a separate state that is utilized in the entire app. All reducers are invoked with two parameters: the state and the action. Every time an action is dispatched, the object returned by the action is passed as the value for the action parameter. The value passed to the state parameter is the currently stored state. If you supply a default value to the state argument, it will only be used as the default value of the state the first time the app runs.

Every time an action is dispatched, all reducers are invoked. That is why we need to check for the action type then return the new state based on the action payload. Otherwise, it just returns the current state. Even though all the reducers are invoked, it’s important to note that only the reducer which handles that specific action will do the work.

currentSelectionsReducer

currentSelectionsReducer returns the state which stores the data for the currently hovered mountain.

let default_state = {
    key: null,
    position: null,
    showInfoWindow: false
};

export default function currentSelectionsReducer (state = default_state, action) {
    if (action.type === 'UPDATE_SELECTION') {
        return {
            key: action.key,
            position: action.position,
            showInfoWindow: action.showInfoWindow
        };
    }
    return state;
}

searchReducer

searchReducer returns the state which stores the data for the user’s query and filters. This uses the data from the data/filters.js file as its initial data.

import filters from '../data/filters';

export default function searchReducer (state = filters, action) {
    if (action.type === 'UPDATE_QUERY') {
        return {
            query: action.query,
            show_filters: action.show_filters,
            filters: action.filters
        };
    }
    return state;
}

index

To bring all the reducers together, we create an index.js file inside the reducers folder. Here we use the combineReducers() function provided by Redux to bring all the reducers together. We need to do this because we want to be able to access the current store value by using only a single object. Once we do this, the current selections data can be accessed by calling this.context.store.getState().currentSelections and the search results data can be accessed by calling this.context.store.getState().search. This can be done in any component which has access to the context. Later on, you’ll see how to specify that a component should use the context.

import { combineReducers } from 'redux';
import search from './searchReducer';
import currentSelections from './currentSelectionsReducer';

export default combineReducers({
  search,
  currentSelections
});

When creating reducers, you should always remember two things:

  1. Reducers should be pure functions. This means that they shouldn’t modify the state directly. As you have seen from the two reducers that we’ve created, they either return an entirely new object which represents the current state or returns the default state. If the state is an object, you can either use Object.assign() or return a new object altogether. If the state is an array, you want to use Array.concat() instead of Array.push() if you want to push a new item into the array. This is because Array.concat() creates a new array with the new item that you want to push, while Array.push() modifies the array that you supplied. If you want to know more about pure functions, check out this article from Eric Elliott: Master the JavaScript Interview: What is a Pure Function.
  2. Filtering and sorting items in the state is not the job of the reducer. For example, on a todo list app, you don’t want to create a reducer named filterItemsReducer which will modify the state based on the item status (active, done, or deleted) selected by the user. Because if the user selects done, the state will only have the items that are already crossed off the list. So if the user selects active after that, it’s no longer all of the items that will be filtered, it will only be the items that have a status of done, so you basically end up with an empty array. Later on, we’ll have a helper function that will do that job for us right before we render the items in the state.

Components

We now proceed with the different components of the app. In this section, you’ll learn how the data flows between the components of the app through the use of dispatching and subscribing on the store.

Main Component

Let’s start by creating the main component. You can do that by creating an App.js file and adding the following:

import React, { Component } from 'react';

import '../node_modules/picnic/releases/picnic.min.css'; //use picnic.css styles

import VisibleCards from './containers/VisibleCards';
import VisibleMarkers from './containers/VisibleMarkers';
import SearchFilters from './components/SearchFilters';

class App extends Component {

  render() {
    return (

); } } export default App;

The code above is pretty self-explanatory but I’d like to address one thing, and that is the difference between components and containers. As you can see above, we’re both rendering <VisibleMarkers> and <SearchFilters> but why is <VisibleMarkers> inside the containers directory while <SearchFilters> is inside the components directory? This is because it allows us to have a distinction between components that have their data handed out to them (components) and those that are providing data (containers). If you scour the internet, you will see people often call them Smart or Dumb components. Later on, you’ll see the difference between the two. But if you want to learn more about this, you can read this article on Presentational and Container components.

VisibleMarkers Container

Create the containers/VisibleMarkers.js file and add the following:

import { connect } from 'react-redux';
import MarkerList from '../components/MarkerList'; //component for rendering markers
import getVisibleMountains from '../helpers/getVisibleMountains';

import mountains from '../data/mountains'; //hard-coded mountains data

const mapStateToProps = (state) => {
  return {
    mountains: getVisibleMountains(
      mountains,
      state.search.query, //query entered by the user
      state.search.filters //filters selected by the user
    )
  }
}

const VisibleMarkers = connect(
  mapStateToProps
)(MarkerList);

export default VisibleMarkers;

In the code above, we’re creating a container called VisibleMarkers. As mentioned earlier, containers are components which fetch their own data. That data is supplied from the store. We are able to access the store from this file by means of the mapStateToProps() function. This function is where we return an object containing the data that we want to pass as props to the MarkerList component. In the above code, we’re returning an object containing mountains as the property. Its value is an array containing the filtered mountain list which is provided to us by the getVisibleMountains() function. This means that in the MarkerList component we will have a props called mountains. If it were a class-based component, you could access it by using this.props.mountains. If it were a functional component, you could access it using props.mountains.

But you may ask how is the state being passed to the mapStateToProps() function? Well, that’s where the connect() function provided by react-redux comes into play. Its the one responsible for passing the current state to mapStateToProps() function. Behind the scenes, the connect() function generates a wrapper component which subscribes to the store. So every time the store is updated with the relevant data, the new state is passed as an argument to the mapStateToProps() function.

VisibleCards Container

VisibleCards container (containers/VisibleCards.js) is pretty much the same as VisibleMarkers container, but this time it uses the CardList component to render the content.

import { connect } from 'react-redux';
import CardList from '../components/CardList';

import getVisibleMountains from '../helpers/getVisibleMountains';
import mountains from '../data/mountains';

const mapStateToProps = (state) => {
  return {
    mountains: getVisibleMountains(
      mountains,
      state.search.query,
      state.search.filters
    )
  }
}

const VisibleCards = connect(
  mapStateToProps
)(CardList);

export default VisibleCards;

Explore React Courses

MarkerList Component

Next is the MarkerList Component. This component is responsible for rendering the map as well as the markers for the currently displayed results.

MarkerList

Create a components/MarkerList.js file and import React and the components made available by react-google-maps:

import React, { Component } from 'react';
import { GoogleMapLoader, GoogleMap, Marker, InfoWindow } from "react-google-maps";

Create the MarkerList component and declare the default state inside the constructor:

export default class MarkerList extends Component {

  constructor(props, context) {
    super(props, context);
    this.state = {
      //center of the map
      defaultCenter: {
        lat: 39.113014,
        lng: -105.358887
      },
      windowPosition: null, //position of the info window
      showInfoWindow: false, //controls the info window is visible or not
      current_name: '' //the content of the info window (name of mountain or peak that's currently selected.)
    }
  }
}

Before the component is mounted, subscribe for changes in the store. The current state can be fetched by calling the getState() function in the store. This returns an object containing the global app state. Here’s what the object looks like by default:

{  
   "search":{  
      "query":"",
      "show_filters":false,
      "filters":{  
         "elevation_height":15000,
         "elevation_distance":5000,
         "prominence_height":5000,
         "prominence_distance":3000,
         "isolation_height":700,
         "isolation_distance":2000
      }
   },
   "currentSelections":{  
      "key":null,
      "position":null,
      "showInfoWindow":false
   }
}

currentSelections is the only one we need, so we extract it from the result of the getState() call and then use it to update the state of the component.

componentWillMount() {
  this.context.store.subscribe(() => {
    let state = this.context.store.getState().currentSelections;
    this.setState({
      windowPosition: state.position, //position of the currently selected mountain in the map
      showInfoWindow: state.showInfoWindow, //whether to show the info window or not
      current_name: state.key //name of the currently selected mountain
    });
  });
}

toggleInfoWindow controls the visibility of the InfoWindow. For those not aware, this is the little pop-over box which becomes visible whenever you click on a marker inside Google maps. This function gets executed when a marker on the map or the close button on the InfoWindow gets clicked. If the loc parameter is null, it means that the user has closed the InfoWindow. Otherwise, it means that the user clicked on the marker so we supply the necessary information by updating the state.

toggleInfoWindow(name, loc) {
  if (loc == null) {
    this.setState({ windowPosition: null });
    return;
  }
  let markerLoc = {
    lat: loc.latLng.lat(),
    lng: loc.latLng.lng()
  };
  this.setState({
    current_name: name,
    windowPosition: markerLoc,
    showInfoWindow: true
  });
}

In the render() function, use the <GoogleMapLoader> component to initialize Google maps. This requires a couple of props to be passed in: containerElement and googleMapElement. containerElement, just like the name suggests, requires you to pass the HTML that will serve as the container for the Google Map.

render() {
  let mountains = this.props.mountains;
  return (

} />

); }

On the other hand, googleMapElement requires the <GoogleMap> component to be passed in. It also has a couple of props: defaultZoom, which is the default zoom of the map and defaultCenter, which is the position of the center of the map.

googleMapElement={
  
  ...
  
}

Inside the <GoogleMap> component, render the markers by looping through the array of mountains. The <Marker> component accepts the position and the key as its props. There’s also an optional onClick props which is the function to call when the marker is clicked.

{mountains.map((row, key) => (
  

  

))}

Right after the loop, render an InfoWindow based on the current state. In the code below, the InfoWindow will only be rendered if showInfoWindow is true. It also accepts a position as its props. This is the same position as the markers, that is why we need to pass in an options props to adjust it accordingly. This is through the use of pixelOffset. Calling new window.google.maps.Size(0,-30) returns that position which is 30 pixels less on the Y axis. This makes it appear at the top of the marker. Note that we used window to access the Google Maps library because google is not available in the current scope.

{
  this.state.showInfoWindow &&
   { this.setState({ showInfoWindow: false }) }}
    options={{pixelOffset: new window.google.maps.Size(0,-30)}}
    >
    {this.state.current_name}
  
}

Lastly, specify the contextTypes. This tells React that we’re expecting a store object in the context. In React, the context is a set of attributes implicitly passed down from its parents to its children. In this case, the store is basically the store that we created earlier on the index.js file and was passed as a props to the <Provider> component. So when we called this.context.store.subscribe() earlier, we’re basically referring to that same store.

MarkerList.contextTypes = { store: React.PropTypes.object };

CardList Component

Next is the CardList component (components/CardList.js).

CardList Component

import React from 'react';
import Card from './Card';

const CardList = ({mountains}) => (
{mountains.map((row, key) => )}

) export default CardList;

The CardList component uses the Card component (components/Card.js) to render the mountain details. It’s also responsible for adding the event handler for when the cards are hovered over. Start by importing the files that we need. Here we’re extracting the updateSelection() method from the actions/index.js file. We didn’t need to specify /index after the folder name because it’s already assumed that we’re referring to an index file if it’s omitted. As you have seen earlier, this file houses all the actions that can be performed throughout the app.

Explore React Courses

import React, { Component } from 'react';
import { updateSelection } from '../actions';

Create the Card component and add a showInfoWindow() function. This function is executed every time the user hovers over a card. It’s responsible for dispatching the action which updates the state for user selection. This state stores the data for the currently hovered card. That’s why the function expects the general mountain details stored in the properties parameter, as well as the position (coordinates) of the marker in the map. We then use this data to dispatch the updateSelection action. In Redux, the way we dispatch an action is by calling the dispatch() method from the store. The store then invokes the reducer functions. Earlier in the reducers/index.js file, we used the combineReducers() functions to create one big object that houses all the state that we need to keep track of. This means that all reducers that we supplied to that function will get invoked. That’s why we were checking the action type on each reducer so that only the reducer which knows what to do with the action will be executed. The store passes in two arguments to the reducer: the current state and the actual action object. You can just imagine that the store executes a code similar to the following every time an action gets dispatched:

var currentState = { 
  currentSelections:{  
    key: null,
    position: null,
    showInfoWindow: false
  }
}

var action = {
  type: 'UPDATE_SELECTION',
  key: "Mount Massive",
   position":{  
      lat: 39.1875,
      lng: -106.4757
   },
   showInfoWindow: true
}

let newState = currentSelectionsReducer(currentState, action); //the new value for the corresponding state

Once the store is updated, it will notify all the subscribers that the store has been updated, which then allows them to fetch the updated data using the getState() function. We did this earlier inside the componentWillMount() function of the MarkerList component. This allowed the component to show the InfoWindow on top of the target marker.

export default class Card extends Component {
  showInfoWindow(properties, coordinates, event) {
    let position = {
      lat: coordinates[1],
      lng: coordinates[0]
    };
    let showInfoWindow = true;
    this.context.store.dispatch(
      updateSelection(properties.name, position, showInfoWindow)
    );
  }  
}

We can’t really leave the InfoWindow hanging around when the user is no longer hovering over a card, that’s why we need the hideInfoWindow() function to dispatch an action which will tell the MarkerList component to hide the InfoWindow.

hideInfoWindow() {
  let key = null;
  let position = null;
  let showInfoWindow = false;
  this.context.store.dispatch(updateSelection(key, position, showInfoWindow));
}

The render() method displays all the relevant details from the data source as well as attach the functions for showing and hiding the InfoWindow on MouseEnter and MouseLeave events.

render() {
  let photo = "/photos/" + this.props.data.photo;
  let properties = this.props.data.properties;
  let coordinates = this.props.data.geometry.coordinates;

  return (

 

 

{properties.name}

 

 

{ properties.name }

 

{ properties.range }

 

 

    • Elevation { properties.elevation[0].toLocaleString() } ft, { properties.elevation[1].toLocaleString() } m

 

    • Prominence { properties.prominence[0].toLocaleString() } ft, { properties.prominence[1].toLocaleString() } m

 

    • Isolation { properties.isolation[0].toLocaleString() } mi, { properties.isolation[1].toLocaleString() } km

 

 

 

 

 

); }

SearchFilters Component

We now need to implement the SearchFilters component (components/SearchFilters.js).

SearchFilters Component

This is responsible for displaying the search field and all the filters used in trimming down the search results. Inside the file, first, import the slider and its default styling. We’ll be using the react-input-range package to easily create sliders that the user can use to select the elevation, prominence and isolation of the mountain they’re looking for.

import React, { Component } from 'react';
import InputRange from 'react-input-range'; 
import '../../node_modules/react-input-range/dist/react-input-range.css'; 

Import the initial filters. This is called filters but it includes the query as well.

import filters from '../data/filters';

Extract the updateQuery() method from the actions file. This allows us to update the current user query.

import { updateQuery } from '../actions';

Create the SearchFilters component. Inside the constructor() set the initial state to that of the data contained within the data/filters.js file.

class SearchFilters extends Component {

  constructor(props, context) {
    super(props, context);
    this.state = filters;
  }
}

The updateStore() method gets executed every time the user types in something in the search field or when the sliders are adjusted. This is where we dispatch the updateQuery action which we created earlier on the actions/index.js file. As you have seen earlier, the action accepts the current value of the query text field, the checkbox for showing or hiding the filters, and the selected filters.

updateStore() {
  this.context.store.dispatch(
    updateQuery(this.state.query, this.state.show_filters, this.state.filters)
  );
}

The onQueryChange() function updates the component state with the current value inputted by the user in the search field, then calls the updateStore() function to update the store. This will trigger the mapStateToProps() function in the VisibleMarkers and VisibleCards component to be called, which in turn calls the getVisibleMountains() function to return an array containing only the mountains which match the user’s query.

onQueryChange(event) {
  this.setState({
    query: event.target.value.toLowerCase()
  }, () => {
    this.updateStore();
  });
}

toggleFilters() simply flips the current value for the show_filters in the state. This controls whether the filters are hidden or shown.

toggleFilters(event) {
  var show_filters = !this.state.show_filters;
  this.setState({
    show_filters
  });
}

updateFilter() is basically doing the same thing as the onQueryChange() function. But this time, it updates the value for the filters instead of the query.

updateFilter(id, component, value) {
  let current_filters = this.state.filters;
  current_filters[id] = value;

  this.setState({
    filters: current_filters
  }, () => {
    this.updateStore();
  });
}

Render the text field for entering the query as well as the different filters. The sliders requires the following props to be passed in:

  • maxValue – the maximum value allowed by the slider.
  • minValue – the minimum value allowed by the slider.
  • value – the current slider value.
  • labelSuffix – the text to display right after the max, min and current value.
  • onChange – the function to execute when the slider value changes.
render() {
  return (

 

 

 

); }

Since we’re using the store passed through the context, we need to specify the contextTypes again:

SearchFilters.contextTypes = { store: React.PropTypes.object };
export default SearchFilters;

Helpers

Helpers are functions that are used in multiple files throughout the app. These functions are usually used to filter data based on specific parameters. In our case, we’re using it to filter the mountains data based on the query and filters selected by the user. You would normally have this function inside a container component. But since we have two containers which needs this function, we took it out to a separate file and just have it imported. The file path is helpers/getVisibleMountains.js.

const getVisibleMountains = (mountains, query, filters) => {

  let filtered_mountains = mountains.filter((mt) => {
    let name = mt.properties.name.toLowerCase();
    let range = mt.properties.range.toLowerCase();
    if(((query !== '' && name.indexOf(query) !== -1) || (range.indexOf(query) !== -1))
      && (filters.elevation_height >= mt.properties.elevation[0]
        && filters.elevation_distance >= mt.properties.elevation[1]
        && filters.prominence_height >= mt.properties.prominence[0]
        && filters.prominence_distance >= mt.properties.prominence[1]
        && filters.isolation_height >= mt.properties.isolation[0]
        && filters.isolation_distance >= mt.properties.isolation[1]
        )) {
      return mt;
    }
    return false;
  });

  return filtered_mountains;

}

export default getVisibleMountains;

Explore React Courses

Review

To really drive home the point of using Redux, let’s run through how the app works one more time.

The app is made up of four main components:

  • Card
  • CardList
  • MarkerList
  • SearchFilter

One of the main principles of React is to have a number of components that can be reused throughout the whole app. Which is why each of the components should be able to work independently, except for parent-child components such as the CardList and Card. But this also means that we cannot have the components talk to each other, because if we do so, we will be creating a dependency between each of them. For example, the MarkerList and CardList components are essentially presenting the same data, only in a different form. So the original problem was how to create a link between these two components. This is where we used Redux to solve the problem. With Redux we were able to create a store that can be used by the whole app. We then updated this store by using actions and reducers. Actions describe what specific arguments are required to update the store while reducers are the one’s that describes how the store will change based on the data passed by the actions. Whatever value the reducers return will become the new value of that specific state in the store. By using these two, we were able to dispatch and subscribe to actions from any component in the app with the help of react-redux. We used the Provider component provided by react-redux as the root component for our app. This component allowed us to link the store to our React app. From there we just used React’s context to get a reference to the dispatch() and subscribe() functions from the store. For example, we used the dispatch() function on the SearchFilter component every time a change is made to the query or any of the filters. We then used the connect() function to automatically subscribe to the changes made to the store from the VisibleCards and VisibleMarkers container. This allowed us to re-render the CardList and MarkerList components every time the store is updated. This is made possible by the mapStateToProps() function in which the new store value is getting passed every time it’s updated. From there, we then returned only the mountains that matches the user’s query. The data that we returned are then passed as props to the CardList and MarkerList components which is finally presented to the user.

Conclusion

In this React and Redux tutorial, you’ve created an interactive 14ers clone. By doing so, you learned how to use Redux for managing application state within a React environment. You also learned concepts like the store, actions, reducers, dispatch and subscribe for making data flow throughout the whole application.

Further Reading

About the Author:

Handling the Challenge of Shared State With Ngrx/Store in Angular 2

August 25th, 2016

The state we’re in:

Angular2 has gone a long way in making UI apps more modular. The app has become a tree of potentially independent components that can be tested in isolation. Http operations are encapsulated in their own class and use Observables for asynchronous operation. The Forms have been overhauled with built in controls and validators. All in all things are much more modular and reactive. The problem of shared state remains, however.

As client-side apps become more complex, the need to handle more and more state on within the browser increases. As components need to share data between them we end up with bits and pieces of the state stored and managed all over the app, often with dependencies on the state managed by other components and our nice, componentized, highly testable, modular app is suddenly tightly coupled again. It’s also difficult to get a picture of the total state of the app.

Explore Angular Courses

Enter Redux

An increasingly popular pattern for dealing with this problem is Redux. The basic principles of Redux are as follows:

  • All the state in the app in encapsulated in a single JavaScript object called state, which is held in a store.
  • The store is immutable and never directly changed.
  • User interaction, events etc. fire actions which describe what has happened and encapsulate the data.
  • A function called a reducer combines the old state and the action to create the new version of the state, which is stored in the store.

Using this style makes the complicated job of state management more predictable, it separates the functional code from the presentational (separation of concerns) and the app code easier to maintain.

It’s possible to use Redux directly in Angular2, however that is more work than using a library implementation. There are a couple of implementations available and this post will use one of them, ngrx-store

Example app

The Angular2 seed project can be used to generate starter projects based on Angular2. Out of the box it presents a list of scientists along with an input box to allow you to add your own:

The initial list of names comes from doing an http call to get a list from a local file, data.json, which is in the assets folder. The service code that initiates this is the NameListService which is found in the shared/name-list folder:

In this tutorial we will update the app to use redux via ngrx-store and hook it into the service.

Pre-requisites, etc.

The examples below assume you are following the standard set-up and conventions of using Node & NPM, typescript for development and Angular 2 as per the seed project.  Details of how to get up and running are in the seed project ReadMe file.

Step 1: Add ngrx-store as a dependency

We need to add ngrx-store to the project package.json file. These are highlighted below:

"dependencies": {

 "@angular/common": "2.0.0-rc.5",

 "@angular/compiler": "2.0.0-rc.5",

 "@angular/core": "2.0.0-rc.5",

 "@angular/forms": "0.3.0",

 "@angular/http": "2.0.0-rc.5",

 "@angular/platform-browser": "2.0.0-rc.5",

 "@angular/platform-browser-dynamic": "2.0.0-rc.5",

 "@angular/router": "3.0.0-rc.1",

 "es6-module-loader": "^0.17.8",

 "core-js": "^2.4.0",

 "@ngrx/core": "^1.0.0",

 "@ngrx/store": "^2.0.0",

 "rxjs": "5.0.0-beta.6",

 "systemjs": "0.19.27",

 "zone.js": "^0.6.12"

}

 

After this we need to run NPM install to bring in the requisite node modules.

Step 2: Introducing the key concepts of Redux

Any ngrx-store (and Redux) app relies on the following key components

  • Store
  • Reducers
  • Actions

Store

The Store is the Javascript object that holds the application state. The common analogy is that it is like a database. The state maintained by the application is an array of strings which hold the names of the famous scientists. This is therefore what we will maintain in our store.

With ngrx-store, the convention is to create the store at bootstrap time. Until the recent (at time of writing) addition of modules to Angular2 this was achieved by importing the provideStore function and using it on bootstrap of the app. Now, we need to import the StoreModule from ngrx-store to the root module of the app and call provideStore on that. In the case of the seed application, the root module is defined in app.module.ts.

First we add an import to import StoreModule from @ngrx/store. Then in the imports serction of the NgModule declaration, we add the code which instantiates the Store:

StoreModule.provideStore( {names})

import {StoreModule} from '@ngrx/store';

import { names } from './shared/store/name-reducer';

@NgModule({

 imports: [BrowserModule, HttpModule, RouterModule.forRoot(routes), AboutModule, HomeModule, SharedModule.forRoot(),

   StoreModule.provideStore( {names})],

 declarations: [AppComponent],

 providers: [{

provide: APP_BASE_HREF,

useValue: '<%= APP_BASE %>'

 }],

 bootstrap: [AppComponent]

Wait! What is that parameter, “names”? That is a reducer, and they are covered in the next section. A more complex example will contain many reducers but as this in an introduction we’ll keep it simple.

Now we have set up the store, we need to make it available to all the sub-components that make up the app. The next change we do is to the AppComponent, defined in app.component.ts

This time we add the import statement for the Store:

import {Store} from '@ngrx/store';

And then change the constructor from this:

constructor() {

 console.log('Environment config', Config);

}

To this:

constructor(private _store: Store<any>) {

 console.log('Environment config', Config);

}

You can see where the store will be injected into the new private attribute, _store, and then be available to the rest of the components in the app.

Reducers

The usual analogy is that if the Store is the database, the reducers are like the tables in the database. They represent the “slices” of state that we want to keep track of. What makes this harder to grasp is that they may not represent a single recognisable business object. They could just be the state of a UI component, but for now we’ll keep things simple.

When dealing with Reducers, some theory needs to come in. Reducers need to be pure functions. A pure JavaScript function:

  1.       Always evaluates to the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices (or HTTP calls).
  2.       Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices or HTTP calls.

In our example app, we’re going to add a reducer function, names. In a new file, src/client/app/shared/store/name-reducer.ts, we add the following:

export const names = (state = [], action) => {

let index: number;

switch (action.type) {

    case 'ADD_NAME_LIST':

        return action.payload;

    case 'ADD_NAME':

        return [...state, action.payload];

    default:

        return state;

}

};

So what’s going on here? Well we’re declaring a function that takes two parameters: the state and and action. In this example state is initialised to be an empty array. The Store will use this function to determine how the state will be changed. If the type of the action is ‘ADD_NAME_LIST’ then the function will return the payload associated with the action (in this case it will be the list of names returned by the NameListService). If the action type is ‘ADD_NAME’, then the payload (in this case a new name string) will be appended to the existing state. Lets take a look at actions.

Explore Angular Courses

Actions

While the Store stores the state, and Reducers output how the state changes, Actions are what communicate to the Reducers when the state needs updated. In an application that uses ngrx-store, all user interaction that would cause a state update has to be expressed in the form of actions. Actions are simple JavaScript objects that implement this interface:

export interface Action {

 type: string;

 payload?: any;

}

The type is a string, denoting what change in state has occurred. The payload is a JavaScript object containing any data that is associated with that state change.

When a triggering event occurs, the relevant action is dispatched to the store which then triggers the relevant reducer function, resulting in a new version of the state. Which we will see in a moment.

Changing the Service

The next changes are to the NameListService. Like the app component, we add a _store attribute to the constructor so that the Store can be injected into the service. Thus this:

constructor(private http: Http) {}

Becomes this:

constructor(private http: Http , private _store: Store<string[]>) {

The original code had a “get” method that loads the JSON data file, returning an Observable array of strings. I have added a new function that instead will load the JSON data file, then dispatch the ‘ADD_NAME_LIST’ action to the store so the loaded list of names (which goes into the payload of the action) go into the store instead. This is the new function:

getnamesNgrx() {

 this.http.get('/assets/data.json')

  .map((res: Response) => res.json())

  .map(payload => ({ type: 'ADD_NAME_LIST', payload: payload }))

  .subscribe(action => this._store.dispatch(action));

}

As you can see, the payload in the response from the http call is mapped to the payload of an action object of type’ADD_NAME_LIST’.

The action is then dispatched to the Store, which will call the Reducer function outlined above, and move the payload (the list of names) into the store.)

Changing the home Component

Finally, we change the HomeComponent, which is defined in home.compent.ts. Yet again we add a Store attribute to the constructor so it can be injected. This:

constructor(public nameListService: NameListService) {}

becomes this:

constructor(public nameListService: NameListService, private _store: Store<any>) {

In the original version, the HomeComponent calls the get() method on the NameListService on the NgInit method. We now replace that with a call in the constructor to the new getnameNgrx()method on the NameListService instead.

this.nameListService.getnamesNgrx();

this._store.select('names')

.subscribe(names=>{

this.names = names;

 });

Next, we call select on the store and bind the local variable, names to the list of names in the store. Because the store is an Observable, this means that every time the list of names in the store is updated, our local variable which is subscribed to it (which is bound to the display on the UI page) will change too.

The last change is to the code that adds the name to the list. The original version pushed the new name onto the array of names. We change that to – you guessed it – dispatch an action of type ‘ADD_NAME’  to the store instead (Note: The TODO is from the original code!):

addName(): boolean {

 // TODO: implement nameListService.post

 // this.names.push(this.newName);

 this._store.dispatch(<Action>{type: 'ADD_NAME', payload: this.newName});

 this.newName = '';

 return false;

}

The Store fires the reducer function outlined above and the new name is added to the list in the store. Because the local variable, names, in the HomeComponent is subscribed to the names in the Store, it will be updated as well.

And that’s it. We’ve now converted the seed application to use Redux with ngrx-store instead.

Summary

Yeah but that’s really simple, I hear you say. Indeed it is and a real world application will be a lot more complex. There are a lot of other topics to get into, like Reducer Composition, Rehydration of the Store and others, however hopefully this has served and a useful introduction to Redux and Ngrx-store.

Further reading/watching:

Explore Angular Courses