最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React Context api - Consumer Does Not re-render after context changed - Stack Overflow

programmeradmin0浏览0评论

I searched for an answer but could not find any, so I am asking here, I have a consumer that updates the context, and another consumer that should display the context. I am using react with typescript(16.3)

The Context(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);

The Producer(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;

The updating context consumer(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;

and finaly the display consumer(WithdrawTransactionsTable.tsx):

class WithdrawTransactionsTable extends React.Component {

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

export default WithdrawTransactionsTable;

It is my understanding that after fileLoadedEvent function is called the context.setJsonTran should re-render the other consumers and WithdrawTransactionsTable component should be re-rendered , but it does not.

what am I doing wrong?

I searched for an answer but could not find any, so I am asking here, I have a consumer that updates the context, and another consumer that should display the context. I am using react with typescript(16.3)

The Context(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);

The Producer(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;

The updating context consumer(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;

and finaly the display consumer(WithdrawTransactionsTable.tsx):

class WithdrawTransactionsTable extends React.Component {

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

export default WithdrawTransactionsTable;

It is my understanding that after fileLoadedEvent function is called the context.setJsonTran should re-render the other consumers and WithdrawTransactionsTable component should be re-rendered , but it does not.

what am I doing wrong?

Share Improve this question asked Feb 22, 2019 at 16:33 Roie BeckRoie Beck 1,1753 gold badges19 silver badges33 bronze badges 1
  • This probably was not OP's cause, but I had a very similar issue. In my case I had accidentally included another AppContext.Provider in one of my nested components. This was distorting behaviour, because any deeper children were not speaking to the Provider that I was expecting. – defraggled Commented Jan 24, 2021 at 13:58
Add a comment  | 

2 Answers 2

Reset to default 7

When you update the state, you aren't triggering a re-render of the Provider and hence the consumer data doesn't change. You should update the state using setState and assign context value to provider like

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;

Your setJsonTran just mutates the default value of the context which will not cause the value given to the Provider to change.

You could instead keep the jsonTransactions in the topmost state and pass down a function that will change this state and in turn update the value.

Example

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>

发布评论

评论列表(0)

  1. 暂无评论