BookstoreReactState State Management

d
Unlimited Att empts Allowed
 to 
Details
Points: 20 points
Overview
In this project, we will provide a centralized global store for our bookstore project. In projects 3 and
5, we used props to share data among components down the components hierarchy. However,
using props to share data is not ideal for large projects (remember prob drilling). In Project 6, we will
use Context in order to share data in our bookstore. We will also start working on a new component
called Cart for our bookstore project (will be fully implemented in Project 7). When fully
implemented, using this component, users can add any books that they want to buy to their cart,
view, modify (add quanti ty, remove, etc), and clear their cart.
Setup Your Server Project
Duplicate your server project [Name]BookstoreFetch on your machine, and name the new
project [Name]BookstoreState.
Open your new project in IntelliJ and find the setti ngs.gradle file. Update the project name to end
with State instead of Fetch. Then click the elephant to reload Gradle. Open your Tomcat run
configurati on, and go to the Deployment tab. Delete the war file asset that no longer works. Press
the Fix butt on, and change the applicati on context to /[Name]BookstoreState. Run the Tomcat
configurati on. The REST API should work as in Project 4. Test some complex APIs. For
example: htt p://localhost:8080/[Name]BookstoreFetch/api/categories/name/Classics/suggested?books?limit=2.
Project 6 - State Management20 Points Possible
In Progress
NEXT UP: Submit Assignment
Att empt 1 Add Comment
?
 Project 6 - State Management
 1/8
Ensure that React Developer tools for Chrome are installed (check here
(htt ps://react.dev/learn/react-developer-tools) for installati on)
Run the server project. Ensure that your Project 6 ([Name]BookstoreReactState) is working correctly
and running as expected.
Setup Your Client Project
Duplicate your client project [name]-bookostore-fetch-client on your machine, and name the new
project [name]-bookstore-state-client. Open the new project in VS Code or IntelliJ. Edit the
package.json file so that the name property is [name]-bookstore-state-client. Edit the vite.config.ts
file so that the base property is /[Name]BookstoreState. Finally, run the client project in the
terminal with "npm ci".
Run the client project. Ensure that it works like Project 5 running at
htt p://localhost:5173/[Name]BookstoreState.
Create Category Context
In P5, we fetched the category informati on from the backend in our App component and shared this
informati on with other components by using props. Below is the component hierarchy of our
bookstore. (Specifically, this is the component hierarchy of the AnotherBookstore starter code from
Project 3, so yours may differ slightly if you made modificati ons.)
?
 Project 6 - State Management
 2/8
The categories data, fetched in the App component, is used in components such as HeaderDropdown
and CategoryNav. Since these components are not direct children of the App component, the
categories data is first passed from App to AppHeader, and then to HeaderDropdown (similarly to
CategoryBookList to CategoryNav). Note that both AppHeader and CategoryBookList do not need
the category data directly themselves. This un-necessary passing through of properti es via
components is called "prop drilling". Prop drilling is a real problem if the component hierarchy is
deep.
React has a scheme that simplifies the sharing of data among components by creati ng a "global
store" called Context. Context hooks work by using Provider and Consumer components. Let's show
how we USE a context provider for categories first. We will then progressively create a "global store"
for category data.
In main.tsx, add the following bold import and code. Here we are showing intuiti vely that we can
make category informati on available to all child components inside the <App> without the need for
prop drilling.
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { CategoryProvider } from './contexts/CategoryContext';
?
 Project 6 - State Management
 3/8
import App from "./App.tsx";
import "./assets/global.css";
// import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
 <React.StrictMode>
 <BrowserRouter basename={import.meta.env.BASE_URL}>
 <CategoryProvider>
 <App />
 </CategoryProvider>
 </BrowserRouter>
 </React.StrictMode>
);
Next we will show how to set up such a context and provider component. In your "client" module,
create a directory named contexts under the src directory. Create a file named CategoryContext.tsx
in that contexts folder. Create a context named Category by adding the following code. The code
uses createContext.
import React, { createContext, useState, useEffect, useContext } from 'react';
import { CategoryItem } from '../types';
import { fetchCategories } from '../services'; // This will be created later
// Define the type for the context value
interface CategoryContextType {
 categories: CategoryItem[];
}
// Create the context with default values
const CategoryContext = createContext<CategoryContextType | undefined>(undefined);
Now it is ti me to add to CategoryContext.tsx to create a CategoryProvider component that fetches
the data from the backend using the REST API, updates the state, and makes it available for the
other components. Use the following skeleton code. Note that you have to REMOVE the code that
downloads and updates the categories state from the App component, since we will be fetching the
categories form the server here now.
// Create a provider component
export const CategoryProvider = ({ children }) => {
 const [categories, setCategories] = useState<CategoryItem[]>([]);
 useEffect(() => {
 fetchCategories()
 .then((data) => setCategories(data))
 .catch(console.error);
 }, []); // Empty dependency array to run only once on page load
 return (
 <CategoryContext.Provider value={{ categories }}>
 {children} {/* This renders the nested components, e.g., <App /> */}
 </CategoryContext.Provider>
 );
};
// Create a custom hook for easy access to the context
export const useCategoryContext = () => {
 const context = useContext(CategoryContext);
 if (!context) {
 throw new Error('useCategoryContext must be used within a CategoryProvider');
 }
?
 Project 6 - State Management
 4/8
 return context;
};
This creates a Provider called CategoryProvider that provides categories data to all its "children". The
CategoryProvider component receives a children prop. This prop is a special prop that React
automati cally passes to every component, representi ng the child components nested inside this
component. In JavaScript, ({ children }) is object destructuring syntax. It means we're extracti ng
the children property from the props object.
Now we have the categories data available to all components in our bookstore (descendants of the
App component). In order to use the context, we use the useContext() hook via the custom
useCategoryContext hook method above. Add the following statements in all the components that
need to use categories data.
import { useCategoryContext } from '../contexts/CategoryContext'; // Import the custom hook
...
 const { categories } = useCategoryContext(); // Use the custom hook to get categories
 return (
 ...
You can now delete all the occurrences of CategoryProps from the bookstore. Make sure category
data is not passed as props in any component. Replace the types.ts file with the one given at the
beginning of this page.
Finally, it is oft en a good idea to separate concerns. We are going to be making service calls to
retrieve data from our server, so let's create a services.ts file in the src folder as follows so we can
fetch categories and books. Move apiUrl from uti ls.ts to this file - it makes more sense here and no
longer needs to be exported.
import axios from 'axios'; 
import { CategoryItem } from './types'; 
const apiUrl = 
 `${location.protocol}//${location.hostname}:` + 
 `${location.port === '5173' ? '8080' : location.port}` + 
 `${import.meta.env.BASE_URL}/api`
export const fetchCategories = async (): Promise<CategoryItem[]> => { 
 const response = await axios.get(`${apiUrl}/categories`); 
 return response.data as CategoryItem[]; 
};
// add a method fetchBooksByCategoryName that takes a categoryName and requests book items
Run your client and make sure everything works exactly like P5.
Create Book Context?
The book data is passed from CategoryBookList to CategoryBookListItem as props in our bookstore
(AnotherBookstore) and that is the only place it is going to be used. There is no prop drilling problem
here as the two components are parent and child. Using props, instead of defining a Context in this
case is acceptable. However, if you prefer to use Context instead of props, you are free to do so as
this is a design decision.
?
 Project 6 - State Management
 5/8
Create the Cart
The last thing we are going to do for this project is to create a CartStore global store for a Shopping
Cart. Before doing this, we need to create a TypeScript class to represent items in our shopping cart
called ShoppingCartItem. Take a look at the given new types.ts file (above).
Our shopping cart (cart in short) will be a state just like the categories or the books states discussed
above. To manage the categories and the books states, we used the useState hook. These two states
are simple to manage as the only operati on they support is mutati ng the data. However, a cart
should support several different operati ons, such as adding and removing books from the cart,
incrementi ng/decrementi ng the quanti ty of a specific book in the cart, etc.
React has a hook, useReducer(), to manage such complex states. We are going to use the useReducer
hook to manage our cart state. The cart state will be used by multi ple components, such as in the
AppHeader to display the number of items in our cart, and in the CategoryBookListItem for "Add to
Cart" butt on to take effect. This warrants the creati on of a Context to make the cart a global stored
object.
Cart Step A: Create CartContext.tsx
1. Define Acti on Types:
Define acti on types for the reducer to handle different acti ons.
Define the acti ons ADD_BOOK , UPDATE_QUANTITY , and CLEAR_CART for the reducer.
import React, { createContext, useReducer, useContext, ReactNode } from 'react';
import { BookItem } from '../types';
import { ShoppingCart } from '../models/ShoppingCart';
// Define action types
type Action =
 | { type: 'ADD_BOOK'; book: BookItem }
 | { type: 'UPDATE_QUANTITY'; book: BookItem; quantity: number }
 | { type: 'CLEAR_CART' };
// Define the initial state
const initialState: ShoppingCart = new ShoppingCart();
2. Create the Reducer:
Create a reducer functi on to manage the shopping cart state.
The reducer functi on, now named cartReducer , handles state transiti ons based on the acti on
type.
// Create the reducer function
const cartReducer = (state: ShoppingCart, action: Action): ShoppingCart => {
 switch (action.type) {
 case 'ADD_BOOK':
 state.addBook(action.book);
 return Object.assign(new ShoppingCart(), { ...state });
 case 'UPDATE_QUANTITY':
 // Pending code in next project...
 case 'CLEAR_CART':
 // Pending code in next project...
?
 Project 6 - State Management
 6/8
default:
 return state;
 }
};
3. Set Up Context and Provider:
Create the context and provider components with a properly initi alized state.
CartProvider uses useReducer to manage the shopping cart state and provides the cart and
dispatch to the context value.
// Create context
interface CartContextType {
 cart: ShoppingCart;
 dispatch: React.Dispatch<Action>;
}
const CartContext = createContext<CartContextType>({ cart: initialState, dispatch: () => null
});
// Create provider component
export const CartProvider = ({ children }: { children: ReactNode }) => {
 const [cart, dispatch] = useReducer(cartReducer, initialState);
 return (
 <CartContext.Provider value={{ cart, dispatch }}>
 {children}
 </CartContext.Provider>
 );
};
4. Create a Custom Hook:
Create a custom hook to easily access the shopping cart context.
useCart custom hook simplifies accessing the shopping cart context, ensuring it's used within
the provider.
// Create custom hook to use shopping cart context
export const useCart = () => {
 const context = useContext(CartContext);
 if (!context) {
 throw new Error('useCart must be used within a CartProvider');
 }
 return context;
};
Cart Step B: Update the Project Component Structure
Note that the above context needs to provide two values, cart and dispatch, to consumer
components. Let us make it accessible to other components by putti ng it up in the components
hierarchy (refer to how we made the CategoryContext available by modifying the main.tsx file).
Cart Step C: Adding Books to the Cart
?
 Project 6 - State Management
 7/8
Now we have the cart and dispatch available to be used throughout the bookstore. Let's first use
dispatch in CategoryBookListItem component. Add the following code to the component.
import { useCart } from '../context/CartContext';
. . . 
 const { dispatch } = useCart();
 const handleAddToCart = () => {
 dispatch({ type: 'ADD_BOOK', book: props.book });
 };
. . . 
modify the "Add to Cart " butt on in the same component with the following onClick property:
. . .
<button className="button" onClick={handleAddToCart}>Add to Cart</button>
. . . 
The above modificati ons allow the applicati on to respond to the click event on the "Add to Cart"
butt on by dispatching it to the CartReducer's reducer functi on and adding the book to the cart. Note
the content of the acti on object of the dispatch.
In your AppHeader component, use the cart to keep the cart count up to date. In order to get the
number of items in the cart you have to use the Cart context as shown above( useCart ). However,
we are interested in the cart value this ti me (not the dispatch). Once you have the cart data, you
have to get the number of items in the cart. Once you have the number of items, you update the
cart icon with the number of items. For AnotherBookstore it is like this (you should replace
expression with the correct javascript expression once you have the number of items:
<button className="button">{ expression }</button>
Once you have done this, you should be able to click on any Add To Cart butt on and see the change
reflected in your cart count.
Submit the URL to Canvas and the war file to the server.
Enter Web URL
htt ps://
Submit Assignment ?
 Project 6 - State Management

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值