In one of our previous blogs, we mentioned how recompose improves both the readability and the maintainability of the code.
We also saw how branch and renderComponent functions from recompose help us in deciding which component to render based on a condition.
We can use the code from renderComponent documentation to render a spinner component when the data is being fetched in a ReactJS application.
Initial Code
1// PatientsList.js 2 3import React, { Component } from 'react'; 4import LoadingIndicator from './LoadingIndicator'; 5 6export default class PatientsList extends Component { 7 8 state = { 9 isLoading: true, 10 patientsList: [], 11 } 12 13 componentDidMount() { 14 api.getPatientsList().then(responseData => { 15 this.setState({ 16 patientsList: responseData, 17 isLoading: false, 18 }) 19 }) 20 } 21 22 render() { 23 const { isLoading } = this.state; 24 if (isLoading) { 25 return <LoadingIndicator isLoading={isLoading} /> 26 } else { 27 return ( 28 <ScrollView> 29 // Some header component 30 // View rendering the patients 31 </ScrollView> 32 ) 33 } 34 }
In the above code, when the PatientsList component mounts, it fetches the list of patients from the API. During this time, the isLoading state is true, so we render the LoadingIndicator component.
Once the API call returns with the response, we set the isLoading state to false. This renders ScrollView component, with our list of patients.
The above code works fine, but if our app has multiple screens, which show the loading indicator and fetch data, the above way of handling it becomes repetitive and hard to maintain.
Building a higher order component
Here's where Higher Order Components(HOC) are very useful. We can extract the logic for the ability to show the loading indicator in a HOC.
1// withSpinner.js 2 3import React from "react"; 4import { ScrollView } from "react-native"; 5import LoadingIndicator from "./LoadingIndicator"; 6 7const withSpinner = (Comp) => ({ isLoading, children, ...props }) => { 8 if (isLoading) { 9 return <LoadingIndicator isLoading={isLoading} />; 10 } else { 11 return <Comp {...props}>{children}</Comp>; 12 } 13}; 14 15export default withSpinner;
Here, we created a HOC component which accepts a component and the isLoading prop.
If isLoading is true, we show the LoadingIndicator. If isLoading is false, we show the supplied component with its children, and pass in the props.
Now, we can use the above HOC in our PatientsList.js file. The supplied component can be any React Native component based on the use case. Here in our case, its a ScrollView.
1// PatientsList.js 2 3import { ScrollView } from 'react-native'; 4import withSpinner from './withSpinner'; 5const ScrollViewWithSpinner = withSpinner(ScrollView); 6 7export default class PatientsList extends Component { 8 9 state = { 10 isLoading: true, 11 patientsList: [], 12 } 13 14 componentDidMount() { 15 api.getPatientsList().then(responseData => { 16 this.setState({ 17 patientsList: responseData, 18 isLoading: false, 19 }) 20 }) 21 } 22 23 render() { 24 const { isLoading } = this.state; 25 return( 26 <ScrollViewWithSpinner 27 isLoading={isLoading} 28 // other props 29 > 30 // Some header component 31 // View rendering the patients 32 </ScrollViewWithSpinner> 33 ) 34 }
Conclusion
Because of the above extraction of logic to a HOC, we can now use the same HOC in all our components which render a loading indicator while the data is being fetched.
The logic to show a loading indicator now resides in a HOC. This makes the code easier to maintain and less repetitive.