We have a variable in our React application that is both:
- Defined as global state in
App.js
, passed globally with its setter method to other components via theGlobalContext.Provider
, and - Used separately as a route parameter for many of the app's routes.
Below is a short code snippet of the relevant section of our App.js
file then:
import React, { useState, useEffect } from 'react';
import GlobalContext from './context/GlobalContext';
import OtherComponents...
function App() {
const [competition, setCompetition] = useState({ value: 15, label: 'Season 1' });
return (
<GlobalContext.Provider value={{ // Pass Global State Through To Entire App
competition, setCompetition
}}>
<Navbar />
<Switch>
<Route exact path='/' render={(props) => <HomePage {...props} />} />
<Route exact path='/stats' component={StatsPage} />
<Route exact path='/about' component={AboutUs} />
<Route exact path='/persons/:competitionId/ component={PersonsComponent} />
<Route exact path='/teams/:competitionId component={TeamsComponent} />
</Switch>
</GlobalContext.Provider>
);
}
export default App;
The competition
global state has keys value
and label
, and the competitionId
in the url parameter is then the same valu as the competition.value
value.
The global state value for competition
is meant to be changed in the <Navbar>
component using a select
widget. When this widget is toggled, the global state is updated, and the useHistory
hook is used to push to app to the new route, using the updated competition.value
to set the competitionId
url parameter.
The value for competition
is needed in many components in our application, including those components where there are no url parameters (e.g. in the <HomePage>
component). For this reason, we feel that it is needed as a global variable, passed down to all other components. This is also very convenient for us as the variable is easily accessibly anywhere with the useContext
hook.
However, the value also seems needed in our url parameters. These components fetch different data based on the competitionId
passed, and them being in the url parameters is a big part of the routing of the app.
Our Problem is then that users can manually change the website's url, which can change the url parameters without also changing the global state of the variable. By changing the url manually, rather than with the select
widget, the global state and url parameters then go out of sync...
Edit: Here is the select
component that we use to toggle the competition
value (sorry the post is getting long). This select is up in our Navbar, and is globally acessable as it's outside our <Switch>
:
function CompetitionSelect({ currentPath }) {
// Grab History In Order To Push To Selected Pages
let history = useHistory();
let { competition, setCompetition } = useContext(GlobalContext);
// Fetch Data on All Competitions (dropdown options)
const competitionInfosConfig = {};
const [competitionInfos, isLoading1, isError1] = useInternalApi('competitionInfo', [], competitionInfosConfig);
// Messy digging of competitionId out of current path.
let competitionIds = competitionInfos.map(row => row.competitionId);
let pathCompetitionId = null;
competitionIds.forEach(id => {
if (currentPath.includes(`/${id}/`)) {
pathCompetitionId = id;
}
});
// Messy Handling State/Params Out Of Sync
if (pathCompetitionId === null) {
console.log('Not a page where testing is needed');
}
else if (competition.value !== pathCompetitionId) {
console.log('WERE OUT OF SYNC...');
let correctGlobalState = { value: pathCompetitionId, label: 'Label Set' };
setCompetition(correctGlobalState);
} else {
console.log('IN SYNC: ', competition.value, ' == ', pathCompetitionId);
}
// Handle updating state + pushing to new route
const handleSelect = (event) => {
let oldPath = JSON.parse(JSON.stringify(history.location.pathname));
let newPath = '';
competitionIds.forEach(id => {
if (oldPath.includes(`/${id}/`)) {
newPath = oldPath.replace(`/${id}/`, `/${event.value}/`)
}
});
if (newPath !== '') {
setCompetition(event);
history.push(newPath);
}
};
// Create The Select
const competitionSelect =
(<Select
styles={appSelectStyles}
value={competition}
options={competitionInfos}
onChange={handleSelect}
placeholder={'Select Competition'}
/>);
return (
{competitionSelect}
);
}
export default CompetitionSelect;
This component technically does resolve the out-of-sync issue in the if, if else, else
clause, however whenever setCompetition(correctGlobalState)
is called, React throws the following warning message:
Warning: Cannot update a component (
App) while rendering a different component (
CompetitionSelect). To locate the bad setState() call inside
CompetitionSelect, follow the stack trace as described...