I want to display all the books in the file db.json located in the server folder when the browser is run the first time. I'm using json-server to do that. However, I caught the error Uncaught TypeError: Cannot read properties of undefined (reading 'books') in the HomePage.js. I tried to look at the bookListSlice.js and its fetchBookData function, but I couldn't figure out what I missed. How can I overcome that problem?
Here is the full codebase: https://github.com/PhucDong/Redux-Bookstore
Here are the codes in HomePage.js
import { useState, useEffect } from "react";
import { ClipLoader } from "react-spinners";
import { useNavigate } from "react-router-dom";
import PaginationBar from "../components/PaginationBar";
import SearchForm from "../components/SearchForm";
import { FormProvider } from "../form";
import { useForm } from "react-hook-form";
import {
Container,
Alert,
Box,
Card,
Stack,
CardMedia,
CardActionArea,
Typography,
CardContent,
} from "@mui/material";
import { BACKEND_API } from "../config";
import { useDispatch, useSelector } from "react-redux";
import {
fetchBookData,
} from "../service/bookList/bookListSlice";
const HomePage = () => {
const [pageNum, setPageNum] = useState(1);
const totalPage = 10;
const limit = 10;
const [loading, setLoading] = useState(false);
const [query, setQuery] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const handleClickBook = (bookId) => {
navigate(`/books/${bookId}`);
};
const bookData = useSelector((state) => state.bookList.books);
const dispatch = useDispatch();
useEffect(() => {
setLoading(true);
let url = `/books?_page=${pageNum}&_limit=${limit}`;
if (query) {
url += `&q=${query}`;
}
dispatch(fetchBookData(url));
setLoading(false);
}, [dispatch, pageNum, query]);
//------React hook form
const defaultValues = {
searchQuery: "",
};
const methods = useForm({
defaultValues,
});
const { handleSubmit } = methods;
const onSubmit = (data) => {
setQuery(data.searchQuery);
};
//------
return (
<Container>
<Stack sx={{ display: "flex", alignItems: "center", m: "2rem" }}>
<Typography variant="h3" sx={{ textAlign: "center" }}>
Book Store
</Typography>
{errorMessage && <Alert severity="danger">{errorMessage}</Alert>}
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack
spacing={2}
direction={{ xs: "column", sm: "row" }}
alignItems={{ sm: "center" }}
justifyContent="space-between"
mb={2}
>
<SearchForm />
</Stack>
</FormProvider>
<PaginationBar
pageNum={pageNum}
setPageNum={setPageNum}
totalPageNum={totalPage}
/>
</Stack>
<div>
{loading ? (
<Box sx={{ textAlign: "center", color: "primary.main" }}>
<ClipLoader color="inherit" size={150} loading={true} />
</Box>
) : (
<Stack
direction="row"
spacing={2}
justifyContent="space-around"
flexWrap="wrap"
>
{bookData.map((book) => (
<Card
key={book.id}
onClick={() => handleClickBook(book.id)}
sx={{
width: "12rem",
height: "27rem",
marginBottom: "2rem",
}}
>
<CardActionArea>
<CardMedia
component="img"
image={`${BACKEND_API}/${book.imageLink}`}
alt={`${book.title}`}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{`${book.title}`}
</Typography>
</CardContent>
</CardActionArea>
</Card>
))}
</Stack>
)}
</div>
</Container>
);
};
export default HomePage;
Here are the codes in bookListSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import apiService from "../../apiService";
const initialState = {
books: [],
error: "",
};
export const fetchBookData = createAsyncThunk(
"books/fetchBooks",
async (url) => {
const response = await apiService.get(url);
return response.data;
}
);
export const bookListSlice = createSlice({
name: "bookList",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchBookData.pending, (state, action) => {
state.status = "loading";
})
.addCase(fetchBookData.fulfilled, (state, action) => {
state.status = "succeeded";
state.books = state.books.concat(action.payload);
})
.addCase(fetchBookData.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export default bookListSlice.reducer;
I checked. The issue you facing its in store.js
You have to call the reducer like that.
import bookListSlice from "./bookList/bookListSlice";
In better way you can call like that for not confusing
import { configureStore } from "@reduxjs/toolkit";
import bookListSliceReducer from "./bookList/bookListSlice";
const store = configureStore({
reducer: {
bookList: bookListSliceReducer,
},
});
export default store;
Because you importing this
Not this
And this is how it's looking now.
So let me know if this is the problem you asking about.