Fly High with React and Redux: Building a Flight Booking App — Chapter 2
Welcome to chapter — 2 of the Redux and React journey. In the first chapter we wrote an application in vanilla js to dispatch an action to reducer and update our state. In this article, we’ll explore how to use React and Redux together to build a flight booking application.
Prerequisites: Before we start, make sure you have the following tools installed on your computer:
- Node.js
- NPM or Yarn
- Vite
- Redux
Step 1: Setting Up the React App with Vite We’ll start by creating a new React app using Vite. Open your terminal and type the following command: npm init @vitejs/app flight-booking-app --template react
Once the installation is complete, navigate into the new project directory by running cd flight-booking-app
Note that Vite is a build tool that offers a faster development experience compared to Create React App, as it uses an optimized development server that provides hot module replacement (HMR) out of the box. It also supports a wide range of modern web technologies and allows for easy customization of the project configuration.
Step 2: Installing Redux To use Redux in our application, we need to install two packages: redux
and react-redux
. Open your terminal and run the following command: npm install redux react-redux
Step 3: In the src
directory, create a new folder called redux
. Inside this folder, create another folder called booking
. This folder will contain the Redux code for managing the booking feature of the application.
Inside the booking
folder, create three new files: actions.js
, actionTypes.js
, and reducer.js
.
The actionTypes.js
file will contain the constants that define the action types for booking-related actions. For example:
// This is actionTypes.js file
export const ADD_BOOKING = 'ADD_BOOKING';
export const DELETE_BOOKING = 'DELETE_BOOKING';
The actions.js
file will contain action creators for booking-related actions. These action creators will create and dispatch actions to the Redux store. For example:
// This is actions.js file
import { ADD_BOOKING, DELETE_BOOKING } from './actionTypes';
//bringing the constants from action types.js
export const addBooking = (booking) => {
return {
type: ADD_BOOKING,
payload: booking
};
};
export const deleteBooking = (bookingId) => {
return {
type: DELETE_BOOKING,
payload: bookingId
};
};
The bookingReducer.js
file will contain the reducer function for managing the state of bookings. For example:
//Our bookingReducer.js file
import { ADD_BOOKING, DELETE_BOOKING } from './actionTypes';
const initialState = [];
const bookingReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_BOOKING:
let booking = action.payload;
return [...state, booking];
case DELETE_BOOKING:
let bookingId = action.payload;
return state.filter((el) => el.id !== bookingId);
default:
return state;
}
};
export default bookingReducer;
In this code block, we have defined a Redux reducer function called bookingReducer
that takes two arguments: state
and action
.
The reducer function is responsible for updating the application state based on the action dispatched by the components. It checks the action.type
to determine what type of action was dispatched, and then returns a new state object that reflects the change caused by the action.
In this case, we have defined two action types: ADD_BOOKING
and DELETE_BOOKING
. If the action type is ADD_BOOKING
, the reducer adds a new booking object to the state array by creating a new array with the spread operator and appending the new booking object to the end.
If the action type is DELETE_BOOKING
, the reducer filters out the booking object with the matching id
from the state array using the filter
method, which returns a new array that excludes the deleted booking object.
The use of the spread operator and the filter
method ensures that the state is updated immutably, meaning that the original state is not mutated or modified directly. Instead, a new state object is created and returned by the reducer function.
Here’s a simplified diagram of how the initial state is updated:
+---------------+
| Component |
+---------------+
|
v
+----------------+
| dispatch() |
+----------------+
|
v
+-----------------+ +-----------------+
| Initial state | | New state after |
| (empty array) | <--------+ | ADD_BOOKING |
+-----------------+ +-----------------+
|
v
+----------------+
| Reducer |
+----------------+
|
v
+-----------------+ +-----------------+
| Initial state | | New state after |
| (empty array) | <--------+ | DELETE_BOOKING |
+-----------------+ +-----------------+
In the diagram, the component dispatches an action to the store, which is handled by the reducer function. The reducer function returns a new state object based on the action type, which is then used to update the application state. The updated state is then passed back to the component via the useSelector
hook, which allows the component to re-render with the updated state.
Note that the action types, action creators, and reducer function are all organized together in the booking
folder for easy maintenance and scalability.
Step 4: Inside the redux
folder, create a new file called store.js
. This file will contain the code for creating the Redux store. In this file, we'll import the createStore
function from the redux
package and the booking reducer from the bookingReducer.js
file.
javascriptCopy codeimport { legacy_createStore as createStore } from 'redux'
import bookingReducer from "./Booking/bookingReducer";
const store = createStore(bookingReducer);
export default store;
In the code above, we created a new Redux store using the createStore
function and passed in our booking reducer. We then exported the store object so that we can use it in other parts of the application.
Note that in a larger application, you might have multiple reducers that are combined together using the
combineReducers
function from Redux. However, for the purposes of this tutorial, we only have one reducer for the booking feature. we will see this this in detail in following chapters.
Step 5: Coonect the whole Application with Redux Store. This makes the state values inside the store available in the whole application.
import { Provider, useSelector } from "react-redux";
import store from "./redux/store";
import BookingInput from "./components/BookingInput";
function App() {
return (
<Provider store={store}>
<BookingInput/>
</Provider>
);
}
export default App;
In the code above, we imported a function from the react-redux
package: Provider
. We also imported the store
object that we created in the store.js
file located in the redux
folder. Finally, we imported the BookingInput
component, which will be used to display a form for adding a new booking.
The Provider
component is a special component provided by react-redux
that allows us to "provide" the Redux store to all components in the application. We pass the store
object as a prop to the Provider
component, and all child components can then access the store using the useSelector
hook.
The useSelector
hook is a function provided by react-redux
that allows us to select data from the Redux store. We can use this hook to retrieve data from the store and use it in our components.
In the App
component, we wrap the BookingInput
component in the Provider
component, passing the store
object as a prop. This allows the BookingInput
component to access the Redux store using the useSelector
hook.
Step 5: Testing the Application Finally, we’ll test the application by running the development server with npm start
and making sure we can book a flight and delete it successfully.
Conclusion: In this article, we explored how to use React and Redux together to build a flight booking application. We created a Redux store, a booking reducer, and actions to add and delete bookings. We also connected React components to the Redux store using the connect
function from react-redux
. By following these steps, you can create a functional flight booking application using React and Redux.