联系方式

您当前位置:首页 >> Java编程Java编程

日期:2024-07-04 11:03

Unlimited Att empts Allowed


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 Management

2024/7/3

20 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

8/8


版权所有:留学生编程辅导网 2020 All Rights Reserved 联系方式:QQ:821613408 微信:horysk8 电子信箱:[email protected]
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。 站长地图

python代写
微信客服:horysk8