Reputation: 1940
I want to use 2 React Context for my app.
For simplicity, I use synchronous functions to load user and item info. See CodeSandbox for a full working sample.
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { ItemProvider, ItemConsumer } from "./ItemContext";
import { UserProvider, UserConsumer } from "./UserContext";
class App extends React.Component {
render() {
return (
<UserProvider>
<UserConsumer>
{({ user }) => (
<ItemProvider user={user}>
<ItemConsumer>
{({ item }) => <div>{JSON.stringify(item, null, 2)}</div>}
</ItemConsumer>
</ItemProvider>
)}
</UserConsumer>
</UserProvider>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// UserContext.js
import React from "react";
const { Provider, Consumer } = React.createContext();
class UserProvider extends React.Component {
state = {
isLoading: false,
value: { user: {} }
};
async componentDidMount() {
this.setState({ isLoading: true });
const user = { id: 1, name: "evan" };
this.setState({ value: { user }, isLoading: false });
}
render() {
const { isLoading, value } = this.state;
if (isLoading) {
return <div>Loading data...</div>;
}
return <Provider value={value}>{this.props.children}</Provider>;
}
}
export { UserProvider, Consumer as UserConsumer };
// ItemContext.js
import React from "react";
const { Provider, Consumer } = React.createContext();
const items = { 1: { name: "a red hat" }, 2: { name: "a blue shirt" } };
const getItemByUserId = userId => items[userId];
class ItemProvider extends React.Component {
state = {
isLoading: false,
value: { item: {} }
};
async componentDidMount() {
this.setState({ isLoading: true });
const item = getItemByUserId(1);
// console.log(this.props.user); // Object {}
// const item = getItemByUserId(this.props.user.id);
this.setState({ value: { item }, isLoading: false });
}
render() {
const { isLoading, value } = this.state;
if (isLoading) {
return <div>Loading data...</div>;
}
return <Provider value={value}>{this.props.children}</Provider>;
}
}
export { ItemProvider, Consumer as ItemConsumer };
I am not able to do const item = getItemByUserId(this.props.user.id);
in ItemContext
because this.props.user
is an empty object.
What is the solution?
Upvotes: 1
Views: 704
Reputation: 282160
User value is rendered from within UserContext asynchronously and won't be available until the children have rendered and hence you need to implement componentDidUpdate in ItemProvider
to get the context value based on user prop
import React from "react";
const { Provider, Consumer } = React.createContext();
const items = { 1: { name: "a red hat" }, 2: { name: "a blue shirt" } };
const getItemByUserId = userId => items[userId];
class ItemProvider extends React.Component {
state = {
isLoading: false,
value: { item: {} }
};
async componentDidMount() {
if (this.props.user && this.props.user.id) {
this.setState({ isLoading: true });
const item = getItemByUserId(this.props.user.id);
this.setState({ value: { item }, isLoading: true });
}
}
componentDidUpdate(prevProps) {
console.log(this.props.user, prevProps.user);
if (prevProps.user !== this.props.user) {
const item = getItemByUserId(this.props.user.id);
this.setState({
value: { item }
});
}
}
render() {
const { isLoading, value } = this.state;
if (isLoading) {
return <div>Loading data...</div>;
}
console.log(value);
return <Provider value={value}>{this.props.children}</Provider>;
}
}
export { ItemProvider, Consumer as ItemConsumer };
Upvotes: 1