React,Redux:导航失败后加载数据
React, Redux: Loading data after navigate fails
现在我是 react/redux 的新手,我想弄清楚为什么我不能遍历一堆数据。该应用程序非常简单:
我正在通过 Link 切换到 table 条目的页面 -> 数据已加载并显示...目前一切正常。但是当创建一个新条目并使用 useNavigate 切换回上一页时,我收到以下错误:
Uncaught TypeError: books.books.map is not a function
booksSlice.js:
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from 'axios';
const BASE_URL = 'http://127.0.0.1:8000/api/book/';
const initialState = {
books: [],
status: 'idle', //'idle' | 'loading' | 'succeeded' | 'failed'
error: null
};
export const getBooks = createAsyncThunk('api/book', async (id=null, { rejectWithValue }) => {
try {
const response = await axios.get(BASE_URL);
return response.data.results;
} catch (err) {
console.log(err);
return rejectWithValue(err.response.data);
}
});
export const postBook = createAsyncThunk('api/book', async(book, { rejectWithValue }) => {
try {
const response = await axios.post(BASE_URL, book);
return response.data;
} catch (err) {
console.log(err);
return rejectWithValue(err.response.data);
}
});
const booksSlice = createSlice({
name: 'books',
initialState,
extraReducers(builder) {
builder
.addCase(getBooks.pending, (state, action) => {
state.status = 'loading';
})
.addCase(getBooks.fulfilled, (state, action) => {
state.books = action.payload;
state.status = 'succeeded';
})
.addCase(getBooks.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
})
}
});
export const selectAllBooks = (state) => state.books;
export default booksSlice.reducer;
booksList.js:
import * as React from 'react';
import { Link } from 'react-router-dom';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getBooks, selectAllBooks } from './booksSlice';
const BooksList = () => {
const dispatch = useDispatch();
const books = useSelector(selectAllBooks);
useEffect(() => {
dispatch(getBooks());
}, [dispatch]);
return(
<>
<Stack sx={{ maxWidth: "80%", mx: "auto", mt: "2rem" }} spacing={2} direction="row">
<Button variant="contained">
<Link className="link-create-book" to="create">Create Book</Link>
</Button>
</Stack>
<TableContainer sx={{ maxWidth: "80%", mx: "auto", mt: "2rem" }} component={Paper}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell align="right">Description</TableCell>
<TableCell align="right">Author</TableCell>
</TableRow>
</TableHead>
<TableBody>
{ books.books && books.books.map(item => (
<TableRow
key={item.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">{item.title}</TableCell>
<TableCell align="right">{item.description.slice(0, 30)}...</TableCell>
<TableCell align="right">{item.full_author_name}</TableCell>
</TableRow>
)) }
</TableBody>
</Table>
</TableContainer>
</>
)
};
export default BooksList;
booksCreate.js:
import { useState } from "react";
import { useSelector, useDispatch } from 'react-redux';
import { useNavigate } from "react-router-dom";
import { postBook } from './booksSlice';
import { Link } from 'react-router-dom';
const BooksCreate = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [author, setAuthor] = useState('');
const onTitleChanged = (e) => setTitle(e.target.value);
const onContentChanged = (e) => setContent(e.target.value);
const onAuthorChanged = (e) => setAuthor(e.target.value);
const onSavePostClicked = () => {
try {
dispatch(postBook({
title,
description: content,
author,
})).unwrap();
setTitle('');
setContent('');
setAuthor('');
navigate('/book');
} catch (err) {
console.log('Failed to post new book!', err);
}
};
return(
<section className="create-book-section">
<h2>Add a new book</h2>
<form>
<label htmlFor="postTitle">Title:</label>
<input type="text" id="postTitle" name="postTitle" value={title} onChange={onTitleChanged} />
<label htmlFor="postDescription">Description:</label>
<textarea id="postDescription" name="postDescription" value={content} onChange={onContentChanged} />
<label htmlFor="postAuthor">Author:</label>
<input type="text" id="postAuthor" name="postAuthor" value={author} onChange={onAuthorChanged} />
<Stack sx={{ my: "1rem" }} spacing={2} direction="row">
<Button variant="contained" onClick={onSavePostClicked} disabled={!canSave} >Create Book</Button>
<Button variant="contained">
<Link className="link-create-book" to="/book">Back to books</Link>
</Button>
</Stack>
</form>
</section>
)
};
export default BooksCreate;
除此之外,我还尝试通过控制台输出 response.data,在显示错误消息后,我可以在控制台中看到新数据,尽管 none的数据显示在我的window里面。
如果您需要查看更多数据,请现在告诉我。
感谢您的帮助,祝您今天愉快!
在您的 booksSlice.js
中的 createSlice 中,添加一个将您的状态重置为空的操作 []
reducers: {
clearBooks(state){
state.books= [];
}
}
export const {
clearBooks
} = booksSlice.actions;
然后在您的 booksList.js
调用此阳离子以在组件卸载时清除状态,您可以添加额外的 useEffect
或编辑现有的
import { getBooks, selectAllBooks, clearBooks } from './booksSlice';
useEffect(() => {
dispatch(getBooks());
return () => {
dispatch(clearBooks())
}
}, [dispatch]);
好吧...所以回过头来看,解决方案很明显:我在一个 extraReducer 中犯了一个错误 - 当 get-method 失败时 - 忘记包含 post-method 的减速器!
.addCase(getBooks.rejected, (state, action) => {
state.books = [];
state.status = 'failed';
state.error = action.error.message;
})
.addCase(postBook.fulfilled, (state, action) => {
state.books.unshift(action.payload);
state.status = 'succeed';
})
现在我是 react/redux 的新手,我想弄清楚为什么我不能遍历一堆数据。该应用程序非常简单:
我正在通过 Link 切换到 table 条目的页面 -> 数据已加载并显示...目前一切正常。但是当创建一个新条目并使用 useNavigate 切换回上一页时,我收到以下错误:
Uncaught TypeError: books.books.map is not a function
booksSlice.js:
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from 'axios';
const BASE_URL = 'http://127.0.0.1:8000/api/book/';
const initialState = {
books: [],
status: 'idle', //'idle' | 'loading' | 'succeeded' | 'failed'
error: null
};
export const getBooks = createAsyncThunk('api/book', async (id=null, { rejectWithValue }) => {
try {
const response = await axios.get(BASE_URL);
return response.data.results;
} catch (err) {
console.log(err);
return rejectWithValue(err.response.data);
}
});
export const postBook = createAsyncThunk('api/book', async(book, { rejectWithValue }) => {
try {
const response = await axios.post(BASE_URL, book);
return response.data;
} catch (err) {
console.log(err);
return rejectWithValue(err.response.data);
}
});
const booksSlice = createSlice({
name: 'books',
initialState,
extraReducers(builder) {
builder
.addCase(getBooks.pending, (state, action) => {
state.status = 'loading';
})
.addCase(getBooks.fulfilled, (state, action) => {
state.books = action.payload;
state.status = 'succeeded';
})
.addCase(getBooks.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
})
}
});
export const selectAllBooks = (state) => state.books;
export default booksSlice.reducer;
booksList.js:
import * as React from 'react';
import { Link } from 'react-router-dom';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getBooks, selectAllBooks } from './booksSlice';
const BooksList = () => {
const dispatch = useDispatch();
const books = useSelector(selectAllBooks);
useEffect(() => {
dispatch(getBooks());
}, [dispatch]);
return(
<>
<Stack sx={{ maxWidth: "80%", mx: "auto", mt: "2rem" }} spacing={2} direction="row">
<Button variant="contained">
<Link className="link-create-book" to="create">Create Book</Link>
</Button>
</Stack>
<TableContainer sx={{ maxWidth: "80%", mx: "auto", mt: "2rem" }} component={Paper}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell align="right">Description</TableCell>
<TableCell align="right">Author</TableCell>
</TableRow>
</TableHead>
<TableBody>
{ books.books && books.books.map(item => (
<TableRow
key={item.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">{item.title}</TableCell>
<TableCell align="right">{item.description.slice(0, 30)}...</TableCell>
<TableCell align="right">{item.full_author_name}</TableCell>
</TableRow>
)) }
</TableBody>
</Table>
</TableContainer>
</>
)
};
export default BooksList;
booksCreate.js:
import { useState } from "react";
import { useSelector, useDispatch } from 'react-redux';
import { useNavigate } from "react-router-dom";
import { postBook } from './booksSlice';
import { Link } from 'react-router-dom';
const BooksCreate = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [author, setAuthor] = useState('');
const onTitleChanged = (e) => setTitle(e.target.value);
const onContentChanged = (e) => setContent(e.target.value);
const onAuthorChanged = (e) => setAuthor(e.target.value);
const onSavePostClicked = () => {
try {
dispatch(postBook({
title,
description: content,
author,
})).unwrap();
setTitle('');
setContent('');
setAuthor('');
navigate('/book');
} catch (err) {
console.log('Failed to post new book!', err);
}
};
return(
<section className="create-book-section">
<h2>Add a new book</h2>
<form>
<label htmlFor="postTitle">Title:</label>
<input type="text" id="postTitle" name="postTitle" value={title} onChange={onTitleChanged} />
<label htmlFor="postDescription">Description:</label>
<textarea id="postDescription" name="postDescription" value={content} onChange={onContentChanged} />
<label htmlFor="postAuthor">Author:</label>
<input type="text" id="postAuthor" name="postAuthor" value={author} onChange={onAuthorChanged} />
<Stack sx={{ my: "1rem" }} spacing={2} direction="row">
<Button variant="contained" onClick={onSavePostClicked} disabled={!canSave} >Create Book</Button>
<Button variant="contained">
<Link className="link-create-book" to="/book">Back to books</Link>
</Button>
</Stack>
</form>
</section>
)
};
export default BooksCreate;
除此之外,我还尝试通过控制台输出 response.data,在显示错误消息后,我可以在控制台中看到新数据,尽管 none的数据显示在我的window里面。 如果您需要查看更多数据,请现在告诉我。
感谢您的帮助,祝您今天愉快!
在您的 booksSlice.js
中的 createSlice 中,添加一个将您的状态重置为空的操作 []
reducers: {
clearBooks(state){
state.books= [];
}
}
export const {
clearBooks
} = booksSlice.actions;
然后在您的 booksList.js
调用此阳离子以在组件卸载时清除状态,您可以添加额外的 useEffect
或编辑现有的
import { getBooks, selectAllBooks, clearBooks } from './booksSlice';
useEffect(() => {
dispatch(getBooks());
return () => {
dispatch(clearBooks())
}
}, [dispatch]);
好吧...所以回过头来看,解决方案很明显:我在一个 extraReducer 中犯了一个错误 - 当 get-method 失败时 - 忘记包含 post-method 的减速器!
.addCase(getBooks.rejected, (state, action) => {
state.books = [];
state.status = 'failed';
state.error = action.error.message;
})
.addCase(postBook.fulfilled, (state, action) => {
state.books.unshift(action.payload);
state.status = 'succeed';
})