在 React 中没有本地状态的情况下使用新数据重新渲染组件

Re-render the component with new data without having local state in React

我正在练习 React 和 redux,我正在创建一个简单的应用程序,其中有一个侧边栏,显示每条路线上可见的类别列表,以及最初显示我拥有的所有书籍和时间的主要区域单击侧栏上的类别 link 主区域加载另一个组件,其中包含与该类别相关的所有书籍。

这是我在 App.js 文件中设置的路线 ...

class App extends Component {
    async componentDidMount() {
        try {
            await this.props.asyncLoadBooks();
            await this.props.asyncLoadCategories();
        } catch (error) {
            console.log(error);
        }
    }

    render() {
        return (
            <>
                <Header />
                <div className="global-wrapper">
                    <div className="container">
                        <aside className="side-bar">
                            <Categories />
                        </aside>

                        <main className="main-content">
                            <Switch>
                                <Route exact path="/" component={Books} />
                                <Route
                                    exact
                                    path="/category/:id"
                                    component={Category}
                                />
                                <Route component={NotFound} />
                            </Switch>
                        </main>
                    </div>
                </div>
            </>
        );
    }
}

App.js 中,如您所见,我正在通过本地 JSON 文件加载数据,在 booksActionsActions 文件中使用 axios 和 categoriesAction,非常简单。

这是类别组件...

class Categories extends Component {
    render() {
        const { categories } = this.props;

        let categoriesList;

        if (categories && categories.length !== 0) {
            categoriesList = categories.map(category => (
                <li key={category.id}>
                    <Link to={`/category/${category.id}`}>{category.name}</Link>
                </li>
            ));
        } else {
            categoriesList = <Loading />;
        }
        return (
            <div>
                <h2>Categories</h2>
                <ul>{categoriesList}</ul>
            </div>
        );
    }
}

const mapState = state => ({
    categories: state.categories.categories
});

export default connect(mapState)(Categories);

我正在单个类别组件的 ComponentDidMount() 中触发另一个操作,以获取与该组件相关的所有书籍并呈现它们...

class Category extends Component {
    componentDidMount() {
        this.props.getCategoryBooks(this.props.match.params.id);
    }

    componentDidUpdate(prevProps) {
        if (prevProps.match.params.id !== this.props.match.params.id) {
            this.props.getCategoryBooks(this.props.match.params.id);
        }
    }

    render() {
        const { categoryBooks } = this.props;
        return (
            <div>
                {/* <h1>{this.props.match.params.id}</h1> */}
                {categoryBooks &&
                    categoryBooks.map(book => {
                        return <div key={book.id}>{book.title}</div>;
                    })}
            </div>
        );
    }
}

const mapState = state => ({
    categories: state.categories.categories,
    categoryBooks: state.books.categoryBooks
});

const mapActions = {
    getCategoryBooks
};

export default connect(
    mapState,
    mapActions
)(Category);

现在,第一次一切正常,但是,当我单击另一个类别时,<Category /> 组件没有更新,因为我正在 componentDidMount() 中调度操作,因此组件已经第一次挂载,所以在我点击另一个类别后它不会再次派发操作,现在最好的处理方法是什么?

第二个问题是我在类别路由 http://localhost:3000/category/9967c77a-1da5-4d69-b6a9-014ca20abd61 上,我尝试刷新页面,类别列表在侧边栏上加载正常,但单个组件显示为空,当我查看时在 redux-devtools 上,我发现 GET_CATEGORY_BOOKS 操作在 App.js 文件中的 LOAD_BOOKSLOAD_CATEGORIES 之前被触发,因为子 componentDidMount() 方法被调用在其父等效方法之前。这个怎么解决?

希望大家能帮帮我。

编辑

正如@@NguyễnThanhTú 所注意到的,componentDidupate 有一个拼写错误,现在单击另一个类别时它可以工作。 这给我们留下了第二个问题,当在类别路由中重新加载页面时,数据不显示,因为 App.js componentDidMount 在其子组件之后触发。

编辑

这里是 Github 上这个项目的一个 repo ... https://github.com/Shaker-Hamdi/books-app

在您的 booksActions.js 中添加:

export const getCategoryBooksV2 = categoryId => {
    return async (dispatch, getState) => {
        const { books } = getState();
        if (books.books.length === 0) {
            console.log('Only executing once')  // testing purpose only
            const response = await axios.get("books.json");
            const data = response.data.books;
            dispatch(loadBooks(data));
            dispatch(getCategoryBooks(categoryId));
        }
        dispatch(getCategoryBooks(categoryId));
    };
};

在您的 Category.js 中,使用新的动作创建器:

import { getCategoryBooksV2 } from "../books/booksActions";
...
componentDidMount() {
    this.props.getCategoryBooksV2(this.props.match.params.id);
}
...
const mapActions = {
    getCategoryBooksV2
};

这个解决方案的灵感来自这个例子:

function incrementIfOdd() {
  return (dispatch, getState) => {
    const { counter } = getState();

    if (counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

来自Redux-Thunk Documentation

这是演示: