【发布时间】:2020-07-31 00:23:34
【问题描述】:
我们的 React 应用程序中有一个变量:
- 在
App.js中定义为全局状态,通过GlobalContext.Provider使用其setter 方法全局传递给其他组件,并且 - 单独用作许多应用路由的路由参数。
下面是我们App.js文件相关部分的短代码sn-p 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;
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 组件(抱歉,帖子太长了)。这个选择在我们的导航栏中,并且在我们的<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当作状态吗?
-
也许吧?我不确定什么是最好的,虽然看起来 url 参数和全局状态是重复的......
-
我会尽量避免重复状态,让数据只向一个方向流动。当用户更改下拉值时,更新 url,并重新渲染组件。
-
全局状态的一个优点是可以从所有组件全局访问,而我只能从顶级
route组件(而不是嵌套组件)获取路由参数。当useParams()在App.js<Switch/>中未定义的嵌套组件中运行时,它返回一个空对象... -
拥有一个事实来源(url 参数)并将其传递到您需要的任何地方会容易得多。如果没有在
useParams响应中提供它,我不明白你会在哪里需要它。你能举一个你不能使用useParams的例子吗?
标签: reactjs react-router-dom use-context