In my React project I wanted to share the currency, which selected by the user, within the components using a context. Context functionality works fine, however, there is a small issue that I want to fix. It is setting default currency to the context. So, when the web is started at the very beginning there will be a default currency set to the context, which is going to come from the selection that is provided from the endpoint. I used an if-statement as shown in CurrencySelector.js but I was getting that error "Maximum Update Depth Exceeded." I provided my code for the context and currency selection component.
import React, { Component, createContext } from "react";
const CurrencyContext = createContext();
class CurrencyContextProvider extends Component {
state = { selectedCurrency: "uuu" };
setCurrency = (c) => {
this.setState({ selectedCurrency: c });
};
render() {
return (
<CurrencyContext.Provider
value={{ ...this.state, setCurrency: this.setCurrency }}
>
{this.props.children}
</CurrencyContext.Provider>
);
}
}
class CurrencySelector extends React.Component {
constructor(props) {
super(props);
console.log("CurrencySelector constructor");
this.state = { currencies: [] };
}
async componentDidMount() {
let currencies = (await this.getCurrencies()).data
.currencies;
this.setState({ currencies: currencies });
}
async getCurrencies() {
return await fetchAnyQuery(`query{ currencies }`);
}
render() {
return (
<CurrencyContext.Consumer>
{(currencyContext) => {
const {
selectedCurrency,
setCurrency,
} = currencyContext;
return (
<select
name="currency-selector"
onChange={(event) => {
setCurrency(event.target.value);
}}
>
{this.state.currencies.map((currency, index) => {
//When I wrote this if-statement I got the error
if (index == 0) {
setCurrency(currency);
}
return (
<option value={currency}>{currency}</option>
);
})}
</select>
);
}}
</CurrencyContext.Consumer>
);
}
}
//When I wrote this if-statement I got the error
if (index == 0) {
setCurrency(currency);
}
Yes – you would get an error, since you're mutating state during render, which is expressly forbidden. (This isn't specific to using a context.)
You have a couple options I can think of:
Here's an example of the first option.
See how getCurrencies() has been hoisted up to the componentDidMount() of the provider.
A bit of extra care will need to be applied, since it might be that the currencies haven't been loaded yet; see how we only render "Loading..." for that time.
import React, { Component, createContext } from "react";
const CurrencyContext = createContext();
// Fake fetcher function that just takes a bit of time.
function fetchAnyQuery() {
return new Promise((resolve) =>
setTimeout(() => resolve({ data: { currencies: ["a", "b", "c"] } }), 1000)
);
}
class CurrencyContextProvider extends Component {
state = { selectedCurrency: "uuu", currencies: undefined };
setCurrency = (c) => {
this.setState({ selectedCurrency: c });
};
async componentDidMount() {
const currencies = (await this.getCurrencies()).data.currencies;
this.setState(({ selectedCurrency }) => {
if (!currencies.includes(selectedCurrency)) {
// If the selected currency is invalid, reset it to the first one
selectedCurrency = currencies[0];
}
return { currencies, selectedCurrency };
});
}
async getCurrencies() {
return await fetchAnyQuery(`query{ currencies }`);
}
render() {
return (
<CurrencyContext.Provider
value={{ ...this.state, setCurrency: this.setCurrency }}
>
{this.state.currencies ? this.props.children : <>Loading...</>}
</CurrencyContext.Provider>
);
}
}
class CurrencySelector extends React.Component {
render() {
return (
<CurrencyContext.Consumer>
{(currencyContext) => {
const { selectedCurrency, currencies, setCurrency } = currencyContext;
return (
<div>
<select
name="currency-selector"
value={selectedCurrency}
onChange={(event) => {
setCurrency(event.target.value);
}}
>
{currencies.map((currency) => (
<option value={currency} key={currency}>
{currency}
</option>
))}
</select>
<br />
Selected: {selectedCurrency}
</div>
);
}}
</CurrencyContext.Consumer>
);
}
}
export default function App() {
return (
<CurrencyContextProvider>
<CurrencySelector />
</CurrencyContextProvider>
);
}