Syncing up with Async Events in React

Big Picture Organization for a React App

It often helps to visualize the bigger picture when learning a new set of tools.

When working with React, it is easy to get bogged down in the complications of managing props and state - the key features for allowing React components to communicate with each other and execute their responsibility in the course of the React lifecycle.

However, as applications grows more featured, implementing each new step becomes more complicated. This is where tools like Redux enter, to neatly handle application state. One challenge for me in learning Redux was how to enable more complex and non-trivial action creators, which are the functions that modify the state. Action creators are the primary home of logic for an app - this is where you place logic for communicating with your backend, calling external APIs, and detail how exactly to update the state that sits in the Redux store.

Tracking the State While Executing Complex Logic

Complex logic of this nature should often be executed asynchronously. There are several organizational approaches in Redux for this (the first link at the bottom of the post has great background on this topic). I used Redux-Thunk. However I quickly discovered that I wanted to to cram more logic than seemed to fit in the flow of a single Redux-Thunk function. So I was pleased to discover that I could make my Redux-Thunk function a Promise. The Promise could encapsulate multiple logical actions I need my app to take (external api calls and multiple separate cals to my backend database). It is great to have all this asynchronous activity neatly handled.

Having a front end that encapsulates complex logic allows me to keep the backend and its API simple and more maintainable. It seems to me better to handle complex activity on the front end service, especially because I envision multiple front end and external services interacting with my backend API.

Where to Put the Application Logic

Here is the code for my Redux-Thunk that encapsulates an asynchronous Promise. In this example, we collect output from 3 endpoints (apiRequest1,apiRequest2,apiRequest3) in our application backend. Objects returned by 2 separate requests need to interact before we can update the Redux state. The Promise (see Promise.all(...)) offers an excellent solution to neatly manage this slighly complex series of asynchronous events before updating the state, see this in the last line of Promise, dispatch(setBaseData(...)).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
export const thunk_action_creator = country => {
store.dispatch(setActiveCountry(country));
return function(dispatch, getState) {
var apiRequest1 = fetch(`http://localhost:8000/country/centroid/?country=${country}`)
.then(data => { return data.json() })
.then(data => {
if (data.message === "Not Found") {
throw new Error("No such user found!");
}
else { return data.coordinates; }
})
var apiRequest2 = fetch(`http://localhost:8000/country/district/?country=${country}`)
.then( data => {return data.json() })
.then( data => {
if (data.message === "Not Found") {
throw new Error("No such user found!");
}
else { return JSON.parse(data) }
})
.catch(err => { null })

var apiRequest3 = fetch(`http://localhost:8000/country/party/?country=${country}`)
.then( data => {return data.json() })
.then( data => {
if (data.message === "Not Found") {
throw new Error("No such user found!");
}
else { return data; }
})
.catch(err => { return null })

var combinedData = {"apiRequest1":{},"apiRequest2":{},"apiRequest3":{}};
return Promise.all([apiRequest1,apiRequest2,apiRequest3]).then(function(values){
combinedData["apiRequest1"] = values[0];
combinedData["apiRequest2"] = values[1];
combinedData["apiRequest3"] = values[2];
dispatch(setMapCenter( combinedData["apiRequest1"] ))
var partyLookup = combinedData["apiRequest3"];
combinedData["apiRequest2"].features.map( x=> Object.assign(x.properties , { party: partyLookup.find( d => d.districts == x.properties.pk ) || "none"} ))
dispatch(setBaseData( combinedData["apiRequest2"]))
return;
});
};
};

Resources for Completing an Implementation

I look forward to learning more about efficiently handing complex application logic in these types of projects.

Here are some great resources I used:

Using React and Redux to Build Mapbox Studio https://medium.com/@davidtheclark/redux-for-state-management-in-large-web-apps-17b115a1b3 Summary of Building Mapbox Studio

Great Intro to Redux and Redux-Thunk https://www.valentinog.com/blog/redux/

Making a DRY Django API with Django Rest Framework https://www.django-rest-framework.org/