简体   繁体   English

如何正确更新 React Context state 并避免重新渲染?

[英]How to properly update React Context state and avoid re-rendering?

I'm currently learning React Context API and I've a project with following files (I'm using create-create-app to generate the project):我目前正在学习 React Context API 并且我有一个包含以下文件的项目(我正在使用create-create-app生成项目):

Tree

├── package.json
├── node_modules
│   └── ...
├── public
│   └── ...
├── src
│   ├── components
│   │   ├── App.js
│   │   ├── Container.js
│   │   ├── Info.js
│   │   ├── PageHeading.js
│   │   ├── common
│   │   │   ├── Footer.js
│   │   │   ├── Header.js
│   │   │   ├── Helpers.js
│   │   │   ├── Loading.css
│   │   │   ├── Loading.js
│   │   │   └── SearchForm.js
│   │   └── list
│   │       ├── Pagination.js
│   │       └── Table.js
│   ├── css
│   │   └── ...
│   ├── fonts
│   │   └── ...
│   ├── images
│   │   └── ...
│   ├── index.css
│   ├── index.js
│   └── providers
│       └── GlobalProvider.js
└── yarn.lock

File: src/index.js文件:src/index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import Header from "./components/common/Header";
import Footer from "./components/common/Footer";

ReactDOM.render(
    [<Header key='1' />, <App key='2' />, <Footer key='3' />],
    document.getElementById("root")
);

File: src/providers/GlobalProvider.js文件:src/providers/GlobalProvider.js

import React, { Component } from "react";
import { handleResponse } from "../components/common/Helpers";

// Set up initial context
export const GlobalContext = React.createContext({});

// Create an exportable consumer that can be injected into components
export const GlobalConsumer = GlobalContext.Consumer;

// Create the provider using a traditional React.Component class
class GlobalProvider extends Component {
    state = {
        age: 100,
        error: null,
        isLoading: false,
        currentPage: 1,
        result: "",
        data: null,
        total_hg_customers: "",
        total_grams_by_customers: "",
        totalPages: "",
        hasNextPage: false,
        hasPrevPage: false
    };

    fetchCustomersByPage = () => {
        this.setState({ isLoading: true });
        const { currentPage } = this.state;

        fetch(
            `https://api-server.com/customer_gold?pageNum=${currentPage}`
        )
            .then(handleResponse)
            .then(response => {
                const {
                    currentPage,
                    result,
                    data,
                    total_hg_customers,
                    total_grams_by_customers,
                    totalPages,
                    hasNextPage,
                    hasPrevPage
                } = response;

                this.setState({
                    isLoading: false,
                    currentPage,
                    result,
                    data,
                    total_hg_customers,
                    total_grams_by_customers,
                    totalPages,
                    hasNextPage,
                    hasPrevPage
                });
                console.log("API request done and state has been updated"); // <-- this one get infinitely generated
            })
            .catch(error => {
                this.setState({
                    error: error.errorMessage,
                    loading: false
                });
            });
    };

    render() {
        return (
            // value prop is where we define what values
            // that are accessible to consumer components
            <GlobalContext.Provider
                value={{
                    state: this.state,
                    fetchCustomersByPage: this.fetchCustomersByPage
                }}>
                {this.props.children}
            </GlobalContext.Provider>
        );
    }
}

export default GlobalProvider;

File: src/components/common/Helpers.js文件:src/components/common/Helpers.js

export const handleResponse = response => {
        return response.json().then(json => {
            return response.ok ? json : Promise.reject(json);
        });
    };

File: src/components/App.js文件:src/components/App.js

import React, { Component } from "react";
import GlobalProvider from "../providers/GlobalProvider";
import PageHeading from "./PageHeading";
import Container from "./Container";

class App extends Component {
    render() {
        return (
            <GlobalProvider>
                <div className='root-container content'>
                    <PageHeading />
                    <Container />
                </div>
            </GlobalProvider>
        );
    }
}

export default App;

File: src/components/Container.js文件:src/components/Container.js

import React, { Component } from "react";
import Loading from "./common/Loading";
import Info from "./Info";
import SearchForm from "./common/SearchForm";
import Table from "./list/Table";
import Pagination from "./list/Pagination";
import { GlobalConsumer } from "../providers/GlobalProvider";

class Container extends Component {
    render() {
        return (
            <GlobalConsumer>
                {context => {
                    const { isLoading } = context.state;

                    if (isLoading) {
                        return (
                            <div className='loading-container'>
                                <Loading />
                            </div>
                        );
                    }

                    return (
                        <div className='middle-container'>
                            <div className='container'>
                                <div className='success-container'>
                                    <div className='row'>
                                        <Info />
                                        <SearchForm />
                                    </div>
                                    <div className='row'>
                                        <Table />
                                    </div>
                                    <div className='row pagination'>
                                        <Pagination />
                                    </div>
                                </div>
                            </div>
                        </div>
                    );
                }}
            </GlobalConsumer>
        );
    }
}

export default Container;

File: src/components/list/Table.js文件:src/components/list/Table.js

import React, { Component } from "react";
import { GlobalConsumer, GlobalContext } from "../../providers/GlobalProvider";

class Table extends Component {
    static contextType = GlobalContext;

    componentDidMount() {
        console.log("Hello");
        return this.context.fetchCustomersByPage();
    }

    render() {
        return (
            <div className='col-12'>
                <GlobalConsumer>
                    {context => {
                        const { data } = context.state;
                        return (
                            <table className='customer-list'>
                                <thead>
                                    <tr>
                                        <th width='50%'>Account Number</th>
                                        <th width='50%'>Gold Balance (g)</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {data &&
                                        data.map(customer => (
                                            <tr key={customer.account_number}>
                                                <td>{customer.account_number}</td>
                                                <td>{customer.gold_balance}</td>
                                            </tr>
                                        ))}
                                </tbody>
                            </table>
                        );
                    }}
                </GlobalConsumer>
            </div>
        );
    }
}

export default Table;

The problem right now is it continue making API calls to the API server (infinite call after one and another).现在的问题是它继续对 API 服务器进行 API 调用(一个接一个的无限调用)。 When I remove this.setState({isLoading: false, currentPage, ...}) part, it stopped making the infinite call to the API server.当我删除this.setState({isLoading: false, currentPage, ...})部分时,它停止了对 API 服务器的无限调用。 But of course, the app's isLoading state stucked at true and the loading spinner component persists in the view.但当然,应用程序的isLoading state 卡在true并且加载微调器组件仍然存在于视图中。

All I need is just submit one call to the API server → assign the respond to the provider/global state → remove the loading spinner → render the table view with the data from the API call.我只需要向 API 服务器提交一个调用 → 将响应分配给提供者/全局 state → 移除加载微调器 → 使用来自 ZDB974238714CA8DE634A7CE1D083A14F 调用的数据呈现表格视图。 I tried using why-did-you-update package to find out what was wrong but I didn't see any clue.我尝试使用Why-did-you-update package 来找出问题所在,但我没有看到任何线索。

What I did wrong here?我在这里做错了什么? Please point it out to me.请向我指出。 If I missed pasting any important files here, please let me know.如果我错过了在这里粘贴任何重要文件,请告诉我。 Thank you.谢谢你。

Is it possible use React Hook?是否可以使用 React Hook? Based on your code, I recommend you break it down into React Hook.根据您的代码,我建议您将其分解为 React Hook。 If so you can use useCallback or useMemo hook.如果是这样,您可以使用 useCallback 或 useMemo 挂钩。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM