首页 > 解决方案 > 从子组件返回时,反应状态被覆盖

问题描述

我有一个从 GET 调用(使用 axios)加载书籍卡片(带有书籍图像、标题等)的主页,我正在使用redux动作调用程序来触发 API 调用“getAllBooks” componentDidMount()。我还使用该react-router Link组件将书与“id”链接,一旦用户点击书卡,他将被带到获取 id 的 BookView 页面this.props.match.params。BookView 组件加载 API 调用以获取“bookById”,使用另一个动作调用者(它在前一个动作调用者的同一个文件中,并且都共享同一个 reducer)。

当我从 BookView 页面(使用浏览器的后退按钮或使用this.props.history.goBack())返回主页时,会出现问题。返回 HomeView 时,状态被“BookByID”操作覆盖,我无法恢复旧状态(因此我undefined在主页上收到错误消息)。我尝试将动作放在不同的文件中(当然这是没用的,因为我对这两个动作都使用了相同的减速器)。我尝试componentDidUpdate在主页上设置以在道具不匹配时触发动作。尝试将操作分派到(该减速器的)redux 状态以重置它,但没有任何效果。我不确定我犯了什么错误,请善意地指导我解决方案。为此,我正在使用 React 16.8 和 Redux 7.1(以及帮助异步调用的 Thunk)。

主页.js


import { getBooks, resetGetBooks } from "../../redux/actions/bookDbAction";
class Home extends Component {
  constructor(props) {
    super(props);

    this.signal = true;
    this.titleInput = React.createRef();
    this.descriptionInput = React.createRef();
    this.isbnInput = React.createRef();
    this.isbn13Input = React.createRef();
    this.grIdInput = React.createRef();
    this.imgLinkLargeInput = React.createRef();
    this.imgLinkMediumInput = React.createRef();
    this.imgLinkSmallInput = React.createRef();
  }

  componentDidMount() {
    this.props.getBooks();
  }

  // when component re-renders
  // componentDidUpdate(prevProps, prevState) {
  //   if(prevProps.books !== this.props.books) {
  //     this.props.getBooks();
  //   }
  // }

  renderBooks() {
    const { classes, books, loading, error } = this.props;
    if (loading) {
      return (
        <div className={classes.progressWrapper}>
          <CircularProgress />
        </div>
      );
    }

    if (books.length === 0) {
      return (
        <Typography className={classes.noResults} variant="h4">
          There are no books available
        </Typography>
      );
    }

    return (
      <Grid container spacing={3}>
        {books.map(book => (
          <Grid item key={book.id} lg={2} md={4} xs={6}>
            <Link className={classes.link} to={`/book/${book.id}`}>
              <BookCardGrid book={book} />
            </Link>
          </Grid>
        ))}
      </Grid>
    );
  }

  render() {
    const { classes } = this.props;

    return (
      <CoreLayout title="Home">
        <div className={classes.root}>
          <BookToolbar />
          <div className={classes.content}>{this.renderBooks()}</div>
          <div className={classes.pagination}>
            <Typography variant="caption">1-6 of 20</Typography>
            <IconButton>
              <ChevronLeftIcon />
            </IconButton>
            <IconButton>
              <ChevronRightIcon />
            </IconButton>
          </div>
        </div>
      </CoreLayout>
    );
  }
}

const mapStateToProps = state => {
  return {
    books: state.book.data,
    loading: state.book.dataLoading,
    error: state.book.error
  };
};

const mapDispatchToProps = {
  getBooks,
  resetGetBooks
};

Home.defaultProps = {
  books: [],
  loading: true,
  error: ""
};

Home.propTypes = {
  classes: PropTypes.object.isRequired,
  books: PropTypes.array.isRequired,
  loading: PropTypes.bool.isRequired,
  error: PropTypes.string.isRequired
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(withStyles(styles)(Home));

Book.js <--- 这是 BookView 页面

import { getBookById, resetGetBookById } from "../../redux/actions/bookDbAction";


class Book extends Component {
  constructor(props) {
    super(props);

    this.signal = true;

    this.state = {
      isLoading: false,
      book: {
        bookTitle: "",
        description: "",
        isbn: "",
        isbn13: "",
        grId: "",
        imgLinkLarge: "",
        imgLinkMedium: "",
        imgLinkSmall: ""
      },
      error: null
    };
  }

  componentDidMount() {
    const { id } = this.props.match.params;
    this.props.getBookById(id);
  }

  componentWillUnmount() {
    this.props.resetGetBookById();
  }

  goBack = () => {
    this.props.history.goBack();
  };

  render() {
    const { classes, book, loading, error } = this.props;

    return (
      <CoreLayout title={book.title}>
        <div className={classes.root}>
          <IconButton
            className={classes.iconButton}
            onClick={this.goBack}
            size="medium"
          >
            <BackIcon fontSize="medium" />
          </IconButton>
          {loading ? (
            <div className={classes.progressWrapper}>
              <CircularProgress />
            </div>
          ) : (
            <div className={classes.content}>
              <div className={classes.imageWrapper + " image-wrap"}>
                <img
                  alt={book.title}
                  className={classes.image}
                  src={book.img_m}
                />
              </div>
            </div>
          )}
        </div>
      </CoreLayout>
    );
  }
}

Book.propTypes = {
  classes: PropTypes.object.isRequired
};

Book.defaultProps = {
  books: [],
  loading: true,
  error: ""
};

const mapStateToProps = state => {
  return {
    book: state.book.data,
    loading: state.book.dataLoading,
    error: state.book.error
  };
};

const mapDispatchToProps = {
  getBookById,
  resetGetBookById
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(withStyles(styles)(Book));

BookDbAction.js <--- 这是包含所有动作创建者的 Book 动作文件

import {
  GET_ALL_BOOKS_PENDING,
  GET_ALL_BOOKS_SUCCESS,
  GET_ALL_BOOKS_FAILURE,
  GET_ALL_BOOKS_RESET,
  GET_BOOK_BY_ID_PENDING,
  GET_BOOK_BY_ID_SUCCESS,
  GET_BOOK_BY_ID_FAILURE,
  GET_BOOK_BY_ID_RESET
} from "./types";

import axios from "axios";

const URL = `${process.env.REACT_APP_DEVELOPMENT_SERVER_URL}/book`;

export const getBooksPending = () => ({
  type: GET_ALL_BOOKS_PENDING,
  dataLoading: true
});

export const getBooksSuccess = json => ({
  type: GET_ALL_BOOKS_SUCCESS,
  dataLoading: false,
  payload: json
});

export const getBooksFailure = error => ({
  type: GET_ALL_BOOKS_FAILURE,
  dataLoading: false,
  payload: error
});

export const getBooksReset = () => ({
  type: GET_ALL_BOOKS_RESET
});

export const getBooks = () => {
  return async dispatch => {
    try {
      let response = await axios.get(URL);
      dispatch(getBooksPending());
      let data = await response.data;
      dispatch(getBooksSuccess(data));
    } catch (error) {
      console.log(error);
      dispatch(getBooksFailure(error));
    }
  };
};

export const resetGetBooks = () => {
  return dispatch => {
    dispatch(getBooksReset());
  };
};

export const getBookByIdPending = () => ({
  type: GET_BOOK_BY_ID_PENDING,
  dataLoading: true
});

export const getBookByIdSuccess = json => ({
  type: GET_BOOK_BY_ID_SUCCESS,
  dataLoading: false,
  payload: json
});

export const getBookByIdFailure = error => ({
  type: GET_BOOK_BY_ID_FAILURE,
  dataLoading: false,
  payload: error
});

export const getBookByIdReset = () => ({
  type: GET_BOOK_BY_ID_RESET
});

export const getBookById = id => {
  return async dispatch => {
    try {
      let response = await axios.get(`${URL}/${id}`);
      dispatch(getBookByIdPending());
      let json = await response.data;
      dispatch(getBookByIdSuccess(json));
    } catch (error) {
      dispatch(getBookByIdFailure(error));
    }
  };
};

export const resetGetBookById = () => {
  return dispatch => {
    dispatch(getBookByIdReset());
  };
};

export const addBookPending = () => ({
  type: ADD_BOOK_PENDING,
  dataLoading: true
});

export const addBookSuccess = data => ({
  type: ADD_BOOK_SUCCESS,
  dataLoading: false,
  payload: data
});

export const addBookFailure = error => ({
  type: ADD_BOOK_FAILURE,
  dataLoading: false,
  payload: error
});

export const addBookReset = () => ({
  type: ADD_BOOK_RESET
});

export const addBook = data => {
  return async dispatch => {
    try {
      let response = await axios.post(`${URL}`, {
        grid: data.grId,
        title: data.bookTitle,
        descr: data.description,
        isbn: data.isbn,
        isbn13: data.isbn13,
        img_l: data.imgLinkLarge,
        img_m: data.imgLinkMedium,
        img_s: data.imgLinkSmall
      });
      dispatch(addBookPending());
      let id = await response.data;
      const data = { id, ...data };
      // console.log("res:" + JSON.stringify(response));
      // console.log("id:" + id);
      dispatch(addBookSuccess(data));
    } catch (error) {
      dispatch(addBookFailure(error));
    }
  };
};

export const resetaddBook = () => {
  return dispatch => {
    dispatch(addBookReset());
  };
};

b>BookDbReducer.js <--- 这是 Book reducerfile

import {
  GET_ALL_BOOKS_PENDING,
  GET_ALL_BOOKS_SUCCESS,
  GET_ALL_BOOKS_FAILURE,
  GET_BOOK_BY_ID_PENDING,
  GET_BOOK_BY_ID_SUCCESS,
  GET_BOOK_BY_ID_FAILURE,
  GET_BOOK_BY_ID_RESET
} from "../actions/types";

const initialState = {
  dataLoading: true,
  data: [],
  error: ""
};

const bookDbReducer = (state = initialState, action) => {
  switch (action.type) {
    case GET_ALL_BOOKS_PENDING:
      return {
        ...state,
        dataLoading: action.dataLoading
      };
    case GET_ALL_BOOKS_SUCCESS:
      return {
        ...state,
        dataLoading: action.dataLoading,
        data: action.payload
      };
    case GET_ALL_BOOKS_FAILURE:
      return {
        ...state,
        dataLoading: action.dataLoading,
        error: action.payload
      };
    case GET_BOOK_BY_ID_PENDING:
      return {
        ...state,
        dataLoading: action.dataLoading
      };
    case GET_BOOK_BY_ID_SUCCESS:
      return {
        ...state,
        dataLoading: action.dataLoading,
        data: action.payload
      };
    case GET_BOOK_BY_ID_FAILURE:
      return {
        ...state,
        dataLoading: action.dataLoading,
        error: action.payload
      };
    case GET_BOOK_BY_ID_RESET:
      return {
        ...state,
        dataLoading: false,
        data: null,
        error: null
      };
    default:
      return state;
  }
};

export default bookDbReducer;

标签: reactjsreduxreact-routerredux-thunk

解决方案


您应该尝试将书籍详细信息数据存储在另一个商店属性中。这样一来,书籍数组将被保留。

const initialState = {
  dataLoading: true,
  data: [],
  bookDetails: {},
  error: ""
};


case GET_BOOK_BY_ID_SUCCESS:
  return {
    ...state,
    dataLoading: action.dataLoading,
    bookDetails: action.payload
  };


推荐阅读