With the mutuality of JavaScript, several frameworks have emerged. React is one of those frameworks that have come up with an effective way to manipulate the virtual DOM. Using React, every user interface section is viewed and composed as a single independent component that has a state. Any changes that occur in the section are determined by the state which the component is always listening to. Changes in the state results to react updating the virtual DOM tree.
The uniqueness in react is seen when the changes occur in the virtual DOM. Immediately, react takes that current DOM and compares it with the virtual DOM after which it will only update only those objects that have changed on the virtual DOM. This brings about a huge performance increase to the table compared to using vanilla JavaScript.
The center of all this is the “State”. So if each component has a state, say we need to access one component state in another component? Or manipulate data in one state and still see the changes on another component? These are the questions that Redux comes to answer with the idea of having a store that has a state or states which operates on a global level.
These states are accessible by all components on the application in that changes or manipulation reflects throughout the application. See, simple. So do I use a React Redux throughout my entire application or I can mix it up with React state? In this article, we will demonstrate how to use both React Redux and React state in the same application.
Using React state
To set up a react state, we initializing local state by assigning an object to
this.state
on the constructor inside a class.class ItemCocktails extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
itemCocktail: [],
id: null,
};
}
Once that is done, we deploy the
function of react. The function is involved immediately when the component is mounted which is good given that we want the asynchronous operation to be triggered on mount followed by a dom tree manipulation. componentDidMount()
componentDidMount() {
this.setState({ isLoading: true });
// eslint-disable-next-line react/destructuring-assignment
const { id } = this.props.match.params;
axios({
method: 'GET',
url: 'https://the-cocktail-db.p.rapidapi.com/lookup.php',
headers: {
'content-type': 'application/octet-stream',
'x-rapidapi-host': 'the-cocktail-db.p.rapidapi.com',
'x-rapidapi-key': 'add key here',
useQueryString: true,
},
params: {
i: id,
},
})
.then(response => {
this.setState({
isLoading: false,
itemCocktail: response.data.drinks,
id,
});
})
.catch(error => {
throw error;
});
}
Each and every time a
setState()
is called to modify the state, it as well triggers a render which will happen before the browser updates the screen. This way, the user cant see the intermediate state. In our case, we employ a condition isLoading
which acts as a safeguard in a case where the asynchronous operation has not finished executing after which it is set to false. Once the promise has been executed the response is captured by the . then
and used to set a new state with the needed data.To improve the user experience, we use condition rendering to display awaiting asynchronous operation.
render() {
const { isLoading, itemCocktail, id } = this.state;
let loadData;
if (isLoading === true) {
loadData = (
<div>
<div className="alert alert-info" role="alert">
Loading data in a moment!
</div>
<Loading />
</div>
);
} else if (
isLoading === false && itemCocktail !== null
&& itemCocktail.length > 0
) {
const data = itemCocktail.map(item => item);
loadData = <ItemCocktailCard itemData={data[0]} key={id} />;
} else {
loadData = (
<div>
<div className="alert alert-danger" role="alert">
Something went wrong! Navigate back to home page
</div>
<Loading />
</div>
);
}
return <div className="container">{loadData}</div>;
}
}
Using React Redux State
Asynchronous operation using react-redux requires a middleware extension that allows simple asynchronous updates by dispatching an action thus interacting with the store. One of the most popular middleware extensions is Redux Thunk which is now part of the react toolkit. In this article, we will not dive deep into redux-thunk(see the documentation to get started).
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met.
The first step in configuring redux is to have actions. Actions have to have a type which will be the condition which has to be met for the reducer to execute.
const fetchCocktailByCategory = cocktails => ({
type: FETCH_COCKTAILS,
cocktails,
});
The above function is an action that sets the type and adds the cocktails payload. With that set, next, we need to have the asynchronous function which will provide the needed data payload.
export const fetchAllCocktailByCategory = () => dispatch => {
const allCategories = [
'Ordinary Drink',
'Cocktail',
'Milk / Float / Shake',
'Other/Unknown',
'Cocoa',
'Shot',
'Coffee / Tea',
'Homemade Liqueur',
'Punch / Party Drink',
'Beer',
'Soft Drink / Soda',
];
return allCategories.map(category => axios({
method: 'GET',
url: 'https://the-cocktail-db.p.rapidapi.com/filter.php',
headers: {
'content-type': 'application/octet-stream',
'x-rapidapi-host': 'the-cocktail-db.p.rapidapi.com',
'x-rapidapi-key':
'add key here',
useQueryString: true,
},
params: {
c: category,
},
})
.then(response => dispatch(
fetchCocktailByCategory([category, response.data.drinks]),
))
.catch(error => {
throw error;
}));
};
The
fetchAllCocktailByCategory()
above is a high order function as it returns another function in our case dispatch
. Once the execution has completed and the promises resolved, the response is passed and dispatched invoking the fetchCocktailByCategory()
action function and passing the payload. The reducer function is always listening for the type to be provided after which it set the payload as a new state.
import { FETCH_COCKTAILS } from '../actions/types';
const cocktailReducer = (state = [], action) => {
switch (action.type) {
case FETCH_COCKTAILS:
return [...state, action.cocktails];
default:
return state;
}
};
The only thing remaining is at this point is to call the reducer at the top level. In most cases an application has more than one reducer, thus it is important to combine them using
combineReducers
from redux and call it as one root reducer. import { combineReducers } from 'redux';
import cocktailReducer from './Cocktails';
import categoryReducer from './Category';
import FilterByCategoryReducer from './FilterByCategory';
const rootReducer = combineReducers({
cocktails: cocktailReducer,
categories: categoryReducer,
filter: FilterByCategoryReducer,
});
The
rootReducer
is invoked at the index and passed as a parameter at the createstore
redux function that creates the store.
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
import { fetchAllCocktailByCategory } from './utils/utils';
const store = createStore(rootReducer, applyMiddleware(thunk));
store.dispatch(fetchAllCocktailByCategory());
Using a reducer is the recommended approach when dealing with a state in a large application. In a large application, the reducer can even be nested where one reducer calls another to provide the next state.
The critical issue is for the developer to analyze their system requirements and operation so as to decide to use the react state or use redux.
To get the complete code at github
image by Academind, Sep 19, 2016