0

我们的 React 应用程序中有一个变量:

  1. 在 中定义为全局状态App.js,通过其 setter 方法全局传递给其他组件GlobalContext.Provider,和
  2. 单独用作许多应用程序路由的路由参数。

下面是我们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全局状态有键valuelabel,然后competitionIdurl 参数中的competition.value值与值相同。

的全局状态值旨在使用小部件在组件competition中进行更改。当这个小部件被切换时,全局状态被更新,并且钩子用于将应用推送到新路由,使用更新设置url 参数。<Navbar>selectuseHistorycompetition.valuecompetitionId

competition我们应用程序中的许多组件都需要for 的值,包括那些没有 url 参数的组件(例如在<HomePage>组件中)。出于这个原因,我们觉得它需要作为一个全局变量,传递给所有其他组件。这对我们来说也很方便,因为该变量可以在任何地方通过useContext钩子轻松访问。

但是,我们的 url 参数中似乎也需要该值。这些组件根据competitionId传递的数据获取不同的数据,它们在 url 参数中是应用程序路由的很大一部分。

那么我们的问题是用户可以手动更改网站的 url,这可以更改 url 参数而无需更改变量的全局状态。通过手动更改 url,而不是使用select小部件,全局状态和 url 参数然后不同步......

编辑:这是select我们用来切换competition值的组件(抱歉帖子越来越长)。这个选择在我们的导航栏中,并且在我们之外是全局可访问的<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 (应用) while rendering a different component (比赛). To locate the bad setState() call inside 选择比赛选择, follow the stack trace as described...

4

1 回答 1

1

I think a good way to deal with this is

  • attempt to get competition data out of the URL
  • use that as the initial value for the state if available, or a default if not
  • use a useEffect to update the state with this competition data from the URL if it's available, on location change

For example

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

  ...

With that, you get what you want - competition in global state, the ability to set it manually with a select, but also have it auto-synced when it appears in the relevant URL parameters.

于 2020-08-03T21:00:00.850 回答