How to update React Context from inside a child component?
Using hooks
Hooks were introduced in 16.8.0 so the following code requires a minimum version of 16.8.0 (scroll down for the class components example). CodeSandbox Demo
1. Setting parent state for dynamic context
Firstly, in order to have a dynamic context which can be passed to the consumers, I'll use the parent's state. This ensures that I've a single source of truth going forth. For example, my parent App will look like this:
const App = () => {
const [language, setLanguage] = useState("en");
const value = { language, setLanguage };
return (
...
);
};
The language
is stored in the state. We will pass both language
and the setter function setLanguage
via context later.
2. Creating a context
Next, I created a language context like this:
// set the defaults
const LanguageContext = React.createContext({
language: "en",
setLanguage: () => {}
});
Here I'm setting the defaults for language
('en') and a setLanguage
function which will be sent by the context provider to the consumer(s). These are only defaults and I'll provide their values when using the provider component in the parent App
.
Note: the LanguageContext
remains same whether you use hooks or class based components.
3. Creating a context consumer
In order to have the language switcher set the language, it should have the access to the language setter function via context. It can look something like this:
const LanguageSwitcher = () => {
const { language, setLanguage } = useContext(LanguageContext);
return (
<button onClick={() => setLanguage("jp")}>
Switch Language (Current: {language})
</button>
);
};
Here I'm just setting the language to 'jp' but you may have your own logic to set languages for this.
4. Wrapping the consumer in a provider
Now I'll render my language switcher component in a LanguageContext.Provider
and pass in the values which have to be sent via context to any level deeper. Here's how my parent App
look like:
const App = () => {
const [language, setLanguage] = useState("en");
const value = { language, setLanguage };
return (
<LanguageContext.Provider value={value}>
<h2>Current Language: {language}</h2>
<p>Click button to change to jp</p>
<div>
{/* Can be nested */}
<LanguageSwitcher />
</div>
</LanguageContext.Provider>
);
};
Now, whenever the language switcher is clicked it updates the context dynamically.
CodeSandbox Demo
Using class components
The latest context API was introduced in React 16.3 which provides a great way of having a dynamic context. The following code requires a minimum version of 16.3.0. CodeSandbox Demo
1. Setting parent state for dynamic context
Firstly, in order to have a dynamic context which can be passed to the consumers, I'll use the parent's state. This ensures that I've a single source of truth going forth. For example, my parent App will look like this:
class App extends Component {
setLanguage = language => {
this.setState({ language });
};
state = {
language: "en",
setLanguage: this.setLanguage
};
...
}
The language
is stored in the state along with a language setter method, which you may keep outside the state tree.
2. Creating a context
Next, I created a language context like this:
// set the defaults
const LanguageContext = React.createContext({
language: "en",
setLanguage: () => {}
});
Here I'm setting the defaults for language
('en') and a setLanguage
function which will be sent by the context provider to the consumer(s). These are only defaults and I'll provide their values when using the provider component in the parent App
.
3. Creating a context consumer
In order to have the language switcher set the language, it should have the access to the language setter function via context. It can look something like this:
class LanguageSwitcher extends Component {
render() {
return (
<LanguageContext.Consumer>
{({ language, setLanguage }) => (
<button onClick={() => setLanguage("jp")}>
Switch Language (Current: {language})
</button>
)}
</LanguageContext.Consumer>
);
}
}
Here I'm just setting the language to 'jp' but you may have your own logic to set languages for this.
4. Wrapping the consumer in a provider
Now I'll render my language switcher component in a LanguageContext.Provider
and pass in the values which have to be sent via context to any level deeper. Here's how my parent App
look like:
class App extends Component {
setLanguage = language => {
this.setState({ language });
};
state = {
language: "en",
setLanguage: this.setLanguage
};
render() {
return (
<LanguageContext.Provider value={this.state}>
<h2>Current Language: {this.state.language}</h2>
<p>Click button to change to jp</p>
<div>
{/* Can be nested */}
<LanguageSwitcher />
</div>
</LanguageContext.Provider>
);
}
}
Now, whenever the language switcher is clicked it updates the context dynamically.
CodeSandbox Demo
How to update data from child component to parent component using context api in react?
msg
needs to be state:
const App = () => {
const state = useState('hi')
return (
<div>
<val.Provider value={state}>
<Posts />
</val.Provider>
</div>
)
}
and then:
const Posts = () => {
const [greet,setGreet] = useContext(val)
return (
<>
<div>{greet}</div>
<button onClick={() => setGreet('hello')}>Change to hello</button>
</>
)
}
It's helpful to look at context as just a prop from far above that you don't want to keep passing down. It's helpful, because that's all that it is.
A reason to use context instead of a prop:
You've got to pass it down through a lot of components, some of which really don't have anything to do with that prop
A reason not to use context:
You're tightly coupling your component to that context.
It's up to you to decide if the tradeoffs of tight coupling are worth it. Sometimes they are, sometimes they aren't. No hard and fast rules.
How to read and update React Context from child with function components?
React doesn't render Boolean, null or undefined values
. Check the documentation on it here
In order to render the authenticated value you need to convert it to string
return (
<div>
{authenticated.toString()}
<button onClick={() => setAuthenticated(false)}>
Logout
</button>
</div>
);
Update Child state from Parent using Context in React
Issue
The issue seems to be that you are mixing up the management of the boolean activeViewAll
state with the selected
state.
Solution
When activeViewAll
is true, pass the data
array as the selected
prop value to the ToggleContainer
component, otherwise pass what is actually selected, the selected
state.
Simplify the handlers. The handleViewAll
callback only toggles the view all state to true, and the handleChange
callback toggles the view all state back to false and selects/deselects the data item.
function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]); // none selected b/c view all true
const [activeViewAll, setActiveViewAll] = useState(true); // initially view all
const handleChange = (val, action) => {
setActiveViewAll(false); // deselect view all
setSelected(selected => {
if (action) {
return [...selected, val];
} else {
return selected.filter(v => v !== val)
}
});
};
const handleViewAll = () => {
setActiveViewAll(true); // select view all
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected} // pass all data, or selected only
/>
</div>
</Context.Provider>
);
}
In the ToggleContainer
don't use the array index as the React key since you are mutating the array. Use the element value since they are unique and changing the order/index doesn't affect the value.
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update
Since it is now understood that you want to not remember what was previously selected before toggling activeViewAll
then when toggling true clear the selected
state array. Instead of duplicating the selected state in the children components, pass the selected
array in the context and computed a derived isSelected
state. This maintains a single source of truth for what is selected and removes the need to "synchronize" state between components.
const ToggleItem = ({ id, styles, description }) => {
const { handleChange, selected } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = () => {
handleChange(description);
};
return (
<>
<Avatar
className={isSelected ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(isSelected)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update the handleChange
component to take only the selected value and determine if it needs to add/remove the value.
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(true);
const handleChange = (val) => {
setActiveViewAll(false);
setSelected((selected) => {
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [...selected, val];
}
});
};
const handleViewAll = () => {
setActiveViewAll(true);
setSelected([]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange, selected }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={d}>
<ToggleItem id={id} styles={classes} description={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected}
/>
</div>
</Context.Provider>
);
}
How to update react context between components?
What you are seeing in the console log is after updating the model object. It is no longer referring to the context. It has a new reference now after model=check
.
First of all, if you want to pass the context between 2 siblings, the common parent should be wrapped around with context providers. You can create a context provider component.
const ModelContext = useContext({model: ''});
const ContextProvider = ({children, modelData}) => {
const [model, setModel] = useState(modelData);
return <ModelContext.Provider value={{model, setModel}}>{children}</ModelContext.Provider>
}
const Parent = () => {
return (
<ContextProvider modelData={your model data}>
<Models />
<Fields/>
</ContextProvider>
)
}
const Models = () => {
const [check, setCheck] = useState({});
const [alert, setAlert] = useState(false);
const {model, setModel} = useContext(ModelContext);
const handleChange = (event) => {
const arr = Object.keys(check);
if (arr.length === 1 && arr.includes(event.target.name) === false) {
setAlert(true);
}
else if (arr.length === 1 && arr.includes(event.target.name)===true) {
setAlert(false);
setCheck({});
setModel(check);
console.log(model);
} else {
setCheck({ ...check, [event.target.name]: event.target.checked });
setAlert(false);
}
};
const Fields = () => {
const {model: modelSelected} = useContext(ModelContext);
console.log(modelSelected);
}
You have to do this because, the context doesn't automatically get to the siblings if you do not wrap their parent with the context provider. This
blog might be helpful for you to understand the usage of context
Related Topics
Getting a Unhandledpromiserejectionwarning When Testing Using Mocha/Chai
Browserify, Babel 6, Gulp - Unexpected Token on Spread Operator
What Is the Meaning of an Underscore in JavaScript Function Parameter
JavaScript - Get Array Element Fulfilling a Condition
Case Insensitive String Replacement in JavaScript
Script438: Object Doesn't Support Property or Method Ie
How to Use Cors to Implement JavaScript Google Places API Request
Map Isn't Showing on Google Maps JavaScript API V3 When Nested in a Div Tag
How to Get the Containing Form of an Input
Why Isn't This Object Being Passed by Reference When Assigning Something Else to It
Wrapping Lines/Polygons Across the Antimeridian in Leaflet.Js
Rotating Image/Marker Image on Google Map V3
JavaScript Custom Event Listener
How to Use Blob Url, Mediasource or Other Methods to Play Concatenated Blobs of Media Fragments
Jquery .Find() Doesn't Return Data in Ie But Does in Firefox and Chrome
Is It a Good Idea to Learn JavaScript Before Learning Jquery