Using Redux to Build a Recipe App

For the past approximately one and half years of my adventure in exploring front-end development, I was already aware that there is this thing called state management, but I just started to learn about it very recently. As far as I know, there is Redux, MobX, Recoil, etc., but I guess Redux is the most used by the community and also one of the in-demand technology in the job market in my area.

In light of that knowledge, I decided to learn Redux straight away. This time I will create the project for a mobile application with React Native, and Expo. To get the context of how this application will work, the below diagram will explain it.

Mainly Redux will take the role to create the functionality of the application. Based on the diagram, I will have three reducers which are cookReducer, likeReducer, stepReducer. cookReducer to add a recipe to the list, likeReducer to add a recipe to a liked list, and stepReducer to keep track of the step that has been done while cook a recipe.

By the way the API that I’m gonna use in this project is based on indonesian language, you can check out the documentation through this link.

Now we already got the schema of the application, let’s get into the code!

1. Install the project

First make sure that expo is already installed within the machine, if yours not done yet, refer to react native docs here. Another thing is to download expo go from play store/app store to preview the app. Once it done, run this command.

expo init recipe-app
cd recipe-app

Then to start the project run this command.

expo start

After its running, you can see the QR code within the output in terminal, open the expo go that we already download previously, and scan the QR code with it.

2. Install the Redux

To install the redux, run this command in the terminal. We need to install redux and react redux.

npm install redux react-redux

3. Configure the Redux

After install redux is done, let’s create redux folder within the root directory. After that, create file reducer.js within the redux folder. Inside this file we will have our reducers. Based on my explanation before, we will have 3 reducers within this application, and all of them will be placed within this file. Here’s what it look like.

export const reducerFavorite = (state = [], action) => {
if (action.type == 'ADDFAV') {
return [...state, action.payload]
}
else if (action.type == 'REMOVEFAV') {
return state.filter(i => i !== action.payload)
}
return state
}
export const reducerCook = (state = [], action) => {
if (action.type == 'ADDCOOK') {
return [...state, action.payload]
}
else if (action.type == 'FINISHCOOK') {
return state.filter(i => i !== action.payload)
}
return state
}
export const reducerCookSteps = (state = [], action) => {
if (action.type == 'ADDSTEP') {
return [...state, action.payload]
}
else if (action.type == 'REMOVESTEP') {
return state.filter(i => i !== action.payload)
}
return state
}

In our App.js we need to import those 3 reducers and will combine them into one using combineReducers from redux, then create a store from it. Here’s what it look like.

import { combineReducers, createStore } from 'redux'
import { reducerFavorite, reducerCook, reducerCookSteps } from './redux/reducer';
import { Provider } from 'react-redux';
const rootReducer = combineReducers({ reducerFavorite, reducerCook, reducerCookSteps})
let store = createStore(rootReducer)
return (
<Provider store={store}>
//our application
</Provider>
)

There we assign combineReducers to rootReducer variable, then from it, we create a store then assign it to the store variable. Notice that we also import provider from react-redux to wrap our application so every component that need the state can access it.

Now we are done configuring the redux, let’s start changing some data from our component.

3. Dispatch data

For example here in my recipe detail page, there will be option to start cook the recipe, or in other words is adding this recipe to the list of cooking progress using cookReducer.

import { useDispatch } from "react-redux";const Detail = ({ navigation, route }) => {
const [recipe, setRecipe] = useState({})
let dispatch = useDispatch()
const cookFood = (second) => { dispatch({ type: 'ADDCOOK', payload: recipe })
navigation.navigate('cookDetail', { data: recipe })
} return(
//...
<View style={styles.startWrap}>
<TouchableOpacity onPress={() => cookFood()} activeOpacity={.8} style={styles.startBtn}> <Text style={styles.btnText}>Start Cook! <Icon name="arrow-right" size={20} /></Text> </TouchableOpacity> </View>
)
}

Notice that when we press the start cook button, we will dispatch an action to the reducer with payload the whole recipe detail. After dispatching data, we will navigate to the cook detail page which consist the step to cook the recipe.

4. Accessing the state

In cook detail page, we will access the state of step that is already done so we can make different between the step that is checked and still not done yet. This can be done by using useSelector from react-redux, and when mapping the step of the recipe, we can check whether the state from redux includes the particular step or not, to give difference style between them.

For each step we are mapping, we can give onPress event to it so when we press it, the checkStep function will fire. First we will check whether the particular step already include within the step from reducer, if yes its includes, then we will remove it from the redux state, and if its not includes we can add it to the redux state by dispatching an action.

Here is how its done.

import { useSelector } from 'react-redux';const CookDetail = ({ navigation, route }) => {
let steps = useSelector(i => i.reducerCookSteps)
let dispatch = useDispatch()
const checkStep = (params) => {
if (steps.includes(params)) {
dispatch({ type: 'REMOVESTEP', payload: params })
}
else {
dispatch({ type: 'ADDSTEP', payload: params })
}
}
return(
<View style={styles.listWrap}>
{ route.params.data.step.map((i, idx) => ( <TouchableOpacity activeOpacity={.8} key={idx} onPress={() => checkStep(i)} style={{ backgroundColor: steps.includes(i) ? '#4fc26c' : 'transparent', borderWidth: steps.includes(i) ? 0 : 1, borderColor: '#56BF6D' }}> <Text style={[styles.listText, { color: steps.includes(i) ? 'white' : 'black' }]}>{idx + 1}. {i.substr(2)}</Text> <IconAnt name='checkcircleo' color={steps.includes(i) ? '#00ff16' : 'black'} size={16} style={{ paddingTop: 5 }} /> </TouchableOpacity> )) }</View>)}

When using useSelector you can try to only write useSelector(i => i) and log it to the console to see all the value it has, if based on our case, when we log the resultuseSelector(i => i) it will gave us 3 value because we have 3 reducers, but because here we want to access the cook steps only, then we can directly access its value based on its reducer name which is reducerCookSteps.

In our tab navigation, we can gave it a badge in Tab.screen options prop so when a user added some recipe to their list it will automatically added a badge based on how many item in the list. Access the value of the cook list using useSelector in our tabNav component like below, then get its length in tabBarBadge property.

const TabNav = () => {
const Tab = createBottomTabNavigator();
let cooks = useSelector(i => i.reducerCook)

return (
<Tab.Navigator screenOptions={{ tabBarShowLabel: false, headerShown: false }} >
//...rest of the tabs
<Tab.Screen name="cook" component={Cook} options={{ tabBarIcon: () => <IconI name='local-fire-department' size={24} />, tabBarBadge: cooks.length ? cooks.length : null }} /> </Tab.Navigator>);}

That’s it! Hope this article helpful for you to get started with redux. The full code can be found through this repositories. Thank you!

Support me if you like this kind of article on:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tulusibrahim

Tulusibrahim

Frontend engineer, usually post some thought once in a while.