React Context API - Consumer在上下文更改后不重新渲染

19
我搜索了答案,但没有找到任何解决方法,所以我在这里提问。我有一个消费者,可以更新上下文,并且另一个消费者应该显示上下文。我正在使用带有TypeScript(16.3)的React。

上下文(AppContext.tsx):

export interface AppContext {
    jsonTransactions: WithdrawTransactionsElement | null;
    setJsonTran(jsonTransactions: WithdrawTransactionsElement | null): void;
}

export const appContextInitialState : AppContext = {
    jsonTransactions: null,
    setJsonTran : (data: WithdrawTransactionsElement) => {
        return appContextInitialState.jsonTransactions = data;
    }
};

export const AppContext = React.createContext(appContextInitialState);

制作人(App.tsx):

interface Props {}

class App extends React.Component<Props, AppContext> {

  state: AppContext = appContextInitialState;

  constructor(props : Props) {
    super(props);
  }

  render() {
    return (
        <AppContext.Provider value={this.state}>
          <div className="App">
            <header className="App-header">
              <SubmitTransactionFile/>
              <WithdrawTransactionsTable />
            </header>
          </div>
        </AppContext.Provider>
    );
  }
}

export default App;

更新上下文消费者(SubmitTransactionFile.tsx)

class SubmitTransactionFile extends React.Component {

    private fileLoadedEvent(file: React.ChangeEvent<HTMLInputElement>, context: AppContext): void{
        let files = file.target.files;
        let reader = new FileReader();
        if (files && files[0]) {
            reader.readAsText(files[0]);
            reader.onload = (json) =>  {
                if (json && json.target) {
                    // @ts-ignore -> this is because result field is not recognized by typescript compiler
                    context.setJsonTran(JSON.parse(json.target.result))
                }
            }
        }
    }

    render() {
        return (
            <AppContext.Consumer>
                { context  =>
                    <div className="SubmitTransactionFile">
                        <label>Select Transaction File</label><br />
                        <input type="file" id="file" onChange={(file) =>
                            this.fileLoadedEvent(file, context)} />
                        <p>{context.jsonTransactions}</p>
                    </div>
                }
            </AppContext.Consumer>
        )
    }
}


export default SubmitTransactionFile;

最后是显示消费者(WithdrawTransactionsTable.tsx):

class WithdrawTransactionsTable extends React.Component {

    render() {
        return (
            <AppContext.Consumer>
                { context  =>
                    <div>
                        <label>{context.jsonTransactions}</label>
                    </div>
                }
            </AppContext.Consumer>
        )
    }
}

export default WithdrawTransactionsTable;

我了解的是在调用fileLoadedEvent函数后,context.setJsonTran应该重新渲染其他相关组件,并且WithdrawTransactionsTable组件也应该重新渲染,但实际上并没有。

我做错了什么?


这可能不是 OP 的原因,但我遇到了非常相似的问题。在我的情况下,我在一个嵌套组件中意外地包含了另一个 AppContext.Provider。这会扭曲行为,因为任何更深层次的子级都无法与我期望的提供程序通信。 - defraggled
2个回答

7
当你更新状态时,你并不会触发Provider的重新渲染,因此消费者数据不会发生变化。你应该使用setState来更新状态,并将上下文值赋值给提供者,例如:
class App extends React.Component<Props, AppContext> {
  constructor(props : Props) {
    super(props);
    this.state = {
         jsonTransactions: null,
         setJsonTran: this.setJsonTran
    };
  }

  setJsonTran : (data: WithdrawTransactionsElement) => {
        this.setState({
             jsonTransactions: data
        });
  }

  render() {
    return (
        <AppContext.Provider value={this.state}>
          <div className="App">
            <header className="App-header">
              <SubmitTransactionFile/>
              <WithdrawTransactionsTable />
            </header>
          </div>
        </AppContext.Provider>
    );
  }
}

export default App;

4

您的 setJsonTran 只是改变了上下文的默认值,这不会导致传递给 Providervalue 发生更改。

您可以将 jsonTransactions 保留在最顶层的状态中,并传递一个函数来更改此状态,从而更新 value

示例

const AppContext = React.createContext();

class App extends React.Component {
  state = {
    jsonTransactions: null
  };

  setJsonTran = data => {
    this.setState({ jsonTransactions: data });
  };

  render() {
    const context = this.state;
    context.setJsonTran = this.setJsonTran;

    return (
      <AppContext.Provider value={context}>
        <div className="App">
          <header className="App-header">
            <SubmitTransactionFile />
            <WithdrawTransactionsTable />
          </header>
        </div>
      </AppContext.Provider>
    );
  }
}

const AppContext = React.createContext();

class App extends React.Component {
  state = {
    jsonTransactions: null
  };

  setJsonTran = data => {
    this.setState({ jsonTransactions: data });
  };

  render() {
    const context = this.state;
    context.setJsonTran = this.setJsonTran;

    return (
      <AppContext.Provider value={context}>
        <div className="App">
          <header className="App-header">
            <SubmitTransactionFile />
            <WithdrawTransactionsTable />
          </header>
        </div>
      </AppContext.Provider>
    );
  }
}

class SubmitTransactionFile extends React.Component {
  fileLoadedEvent(file, context) {
    let files = file.target.files;
    let reader = new FileReader();
    if (files && files[0]) {
      reader.readAsText(files[0]);
      reader.onload = json => {
        if (json && json.target) {
          // slice just to not output too much in this example
          context.setJsonTran(json.target.result.slice(0, 10));
        }
      };
    }
  }

  render() {
    return (
      <AppContext.Consumer>
        {context => (
          <div className="SubmitTransactionFile">
            <label>Select Transaction File</label>
            <br />
            <input
              type="file"
              id="file"
              onChange={file => this.fileLoadedEvent(file, context)}
            />
            <p>{context.jsonTransactions}</p>
          </div>
        )}
      </AppContext.Consumer>
    );
  }
}

class WithdrawTransactionsTable extends React.Component {
  render() {
    return (
      <AppContext.Consumer>
        {context => (
          <div>
            <label>{context.jsonTransactions}</label>
          </div>
        )}
      </AppContext.Consumer>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>


这帮助我解决了我的问题。在我的情况下,我使用我的上下文来表示一个数组。当我尝试推送或拼接数组并在我的reducer中返回它时,React不会将其视为更新。我通过将数组嵌套为上下文状态中的属性来解决了我的问题,例如{ myArray:[] }。正是你使用“mutates”一词让我意识到我所忽视的东西...谢谢! - Kevin M. Lapio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接