在 React 中,当在 url 中手动更改路由参数时,全局状态会不同步
In React, global state goes out of sync when route parameters are manually changed in url
我们的 React 应用程序中有一个变量是:
- 在
App.js
中定义为全局状态,通过 GlobalContext.Provider
和 以其 setter 方法全局传递给其他组件
- 单独用作许多应用程序路由的路由参数。
下面是我们 App.js
文件相关部分的简短代码片段:
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;
competition
全局状态有键 value
和 label
,然后 url 参数中的 competitionId
与 competition.value
值。
competition
的全局状态值旨在使用 select
小部件在 <Navbar>
组件中更改。当这个小部件被切换时,全局状态被更新,并且 useHistory
挂钩用于将应用程序推送到新路由,使用更新的 competition.value
设置 competitionId
url 参数.
competition
的值在我们应用程序的许多组件中是 需要的,包括那些没有 url 参数的组件(例如在 <HomePage>
组件)。出于这个原因,我们觉得它需要作为一个全局变量传递给所有其他组件。这对我们来说也非常方便,因为可以使用 useContext
挂钩在任何地方轻松访问变量。
但是,在我们的 url 参数中,该值似乎 需要 。这些组件根据传递的 competitionId
获取不同的数据,它们在 url 参数中是应用程序路由的重要组成部分。
我们的问题那么用户可以手动更改网站的url,这可以更改url参数而无需更改变量的全局状态。通过手动更改 url 而不是使用 select
小部件,全局状态和 url 参数就会不同步...
编辑: 这是我们用来切换 competition
值的 select
组件(抱歉 post 变长了) .这个 select 在我们的导航栏中,并且在我们的 <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;
该组件在技术上确实解决了 if, if else, else
子句中的不同步问题,但是每当调用 setCompetition(correctGlobalState)
时,React 都会抛出以下警告消息:
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...
我认为解决这个问题的好方法是
- 尝试从 URL
中获取比赛数据
- 如果可用,则将其用作状态的初始值,如果不可用,则使用默认值
- 使用
useEffect
使用来自 URL 的比赛数据更新状态(如果可用),在位置更改时
例如
function App() {
const location = useLocation();
const params = useParams();
// implement getCompetitionFromParams as appropriate
// returns undefined if no competition data in URL params
const competitionFromParams = getCompetitionFromParams(location, params)
const [competition, setCompetition] =
// init state on first load,
// with default if competitionFromParams not available
useState(competitionFromParams || { value: 15, label: 'Season 1' });
// update state on location change,
// if competitionFromParams available
useEffect(() => {
if (competitionFromParams !== undefined) {
setCompetition(competitionFromParams);
}
}, [location]); // runs useEffect only on location change
...
有了它,你就得到了你想要的 - competition
处于全局状态,可以使用 select 手动设置它,但也可以 auto-synced 当它出现在相关的 URL 参数。
我们的 React 应用程序中有一个变量是:
- 在
App.js
中定义为全局状态,通过GlobalContext.Provider
和 以其 setter 方法全局传递给其他组件
- 单独用作许多应用程序路由的路由参数。
下面是我们 App.js
文件相关部分的简短代码片段:
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;
competition
全局状态有键 value
和 label
,然后 url 参数中的 competitionId
与 competition.value
值。
competition
的全局状态值旨在使用 select
小部件在 <Navbar>
组件中更改。当这个小部件被切换时,全局状态被更新,并且 useHistory
挂钩用于将应用程序推送到新路由,使用更新的 competition.value
设置 competitionId
url 参数.
competition
的值在我们应用程序的许多组件中是 需要的,包括那些没有 url 参数的组件(例如在 <HomePage>
组件)。出于这个原因,我们觉得它需要作为一个全局变量传递给所有其他组件。这对我们来说也非常方便,因为可以使用 useContext
挂钩在任何地方轻松访问变量。
但是,在我们的 url 参数中,该值似乎 需要 。这些组件根据传递的 competitionId
获取不同的数据,它们在 url 参数中是应用程序路由的重要组成部分。
我们的问题那么用户可以手动更改网站的url,这可以更改url参数而无需更改变量的全局状态。通过手动更改 url 而不是使用 select
小部件,全局状态和 url 参数就会不同步...
编辑: 这是我们用来切换 competition
值的 select
组件(抱歉 post 变长了) .这个 select 在我们的导航栏中,并且在我们的 <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;
该组件在技术上确实解决了 if, if else, else
子句中的不同步问题,但是每当调用 setCompetition(correctGlobalState)
时,React 都会抛出以下警告消息:
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...
我认为解决这个问题的好方法是
- 尝试从 URL 中获取比赛数据
- 如果可用,则将其用作状态的初始值,如果不可用,则使用默认值
- 使用
useEffect
使用来自 URL 的比赛数据更新状态(如果可用),在位置更改时
例如
function App() {
const location = useLocation();
const params = useParams();
// implement getCompetitionFromParams as appropriate
// returns undefined if no competition data in URL params
const competitionFromParams = getCompetitionFromParams(location, params)
const [competition, setCompetition] =
// init state on first load,
// with default if competitionFromParams not available
useState(competitionFromParams || { value: 15, label: 'Season 1' });
// update state on location change,
// if competitionFromParams available
useEffect(() => {
if (competitionFromParams !== undefined) {
setCompetition(competitionFromParams);
}
}, [location]); // runs useEffect only on location change
...
有了它,你就得到了你想要的 - competition
处于全局状态,可以使用 select 手动设置它,但也可以 auto-synced 当它出现在相关的 URL 参数。