UseEffect导致路线可滑动的无限循环

时间:2019-09-05 21:31:41

标签: reactjs

我正在使用https://www.npmjs.com/package/react-swipeable-routes库在我的React应用程序中设置一些可滑动查看的视图。

我有一个自定义上下文,其中包含一个动态视图列表,需要将这些视图呈现为可滑动路由器的子级,并且为桌面用户添加了两个按钮,分别用于“下一个”和“上一个”视图。

现在我被困在如何从模块数组中获取下一个和上一个项目。

我曾想通过自定义上下文和自定义钩子对其进行修复,但是使用它时,我陷入了无限循环。

我的自定义钩子:

import { useContext } from 'react';
import { RootContext } from '../context/root-context';

const useShow = () => {
  const [state, setState] = useContext(RootContext);

  const setModules = (modules) => {
    setState((currentState) => ({
      ...currentState,
      modules,
    }));
  };

  const setActiveModule = (currentModule) => {
    // here is the magic. we get the currentModule, so we know which module is visible on the screen
    // with this info, we can determine what the previous and next modules are
    const index = state.modules.findIndex((module) => module.id === currentModule.id);

    // if we are on first item, then there is no previous
    let previous = index - 1;
    if (previous < 0) {
      previous = 0;
    }

    // if we are on last item, then there is no next
    let next = index + 1;
    if (next > state.modules.length - 1) {
      next = state.modules.length - 1;
    }

    // update the state. this will trigger every component listening to the previous and next values
    setState((currentState) => ({
      ...currentState,
      previous: state.modules[previous].id,
      next: state.modules[next].id,
    }));
  };

  return {
    modules: state.modules,
    setActiveModule,
    setModules,
    previous: state.previous,
    next: state.next,
  };
};

export default useShow;

我的自定义上下文:

import React, { useState } from 'react';

export const RootContext = React.createContext([{}, () => {}]);

export default (props) => {
  const [state, setState] = useState({});

  return (
    <RootContext.Provider value={[state, setState]}>
      {props.children}
    </RootContext.Provider>
  );
};

在我的Content.js中出现错误的地方

import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
import SwipeableRoutes from 'react-swipeable-routes';
import useShow from '../../hooks/useShow';
import NavButton from '../NavButton';

// for this demo we just have one single module component
// when we have real data, there will be a VoteModule and CommentModule at least
// there are 2 important object given to the props; module and match
// module comes from us, match comes from swipeable views library
const ModuleComponent = ({ module, match }) => {
  // we need this function from the custom hook
  const { setActiveModule } = useShow();

  // if this view is active (match.type === 'full') then we tell the show hook that
  useEffect(() => {
    if (match.type === 'full') {
      setActiveModule(module);
    }
  },[match]);

  return (
    <div style={{ height: 300, backgroundColor: module.title }}>{module.title}</div>
  );
};

const Content = () => {
  const { modules, previousModule, nextModule } = useShow();

  // this is a safety measure, to make sure we don't start rendering stuff when there are no modules yet
  if (!modules) {
    return <div>Loading...</div>;
  }

  // this determines which component needs to be rendered for each module
  // when we have real data we will switch on module.type or something similar
  const getComponentForModule = (module) => {
    // this is needed to get both the module and match objects inside the component
    // the module object is provided by us and the match object comes from swipeable routes
    const ModuleComponentWithProps = (props) => (
      <ModuleComponent module={module} {...props} />
    );

    return ModuleComponentWithProps;
  };

  // this renders all the modules
  // because we return early if there are no modules, we can be sure that here the modules array is always existing
  const renderModules = () => (
    modules.map((module) => (
      <Route
        path={`/${module.id}`}
        key={module.id}
        component={getComponentForModule(module)}
        defaultParams={module}
      />
    ))
  );

  return (
    <div className="content">
      <div>
        <SwipeableRoutes>
          {renderModules()}
        </SwipeableRoutes>
        <NavButton type="previous" to={previousModule} />
        <NavButton type="next" to={nextModule} />
      </div>
    </div>
  );
};

export default Content;

为了完整起见,还有我的NavButton.js:

import React from 'react';
import { NavLink } from 'react-router-dom';

const NavButton = ({ type, to }) => {
  const iconClassName = ['fa'];
  if (type === 'next') {
    iconClassName.push('fa-arrow-right');
  } else {
    iconClassName.push('fa-arrow-left');
  }
  return (
    <div className="">
      <NavLink className="nav-link-button" to={`/${to}`}>
        <i className={iconClassName.join(' ')} />
      </NavLink>
    </div>
  );
};

export default NavButton;

在Content.js中,包含以下部分:

// if this view is active (match.type === 'full') then we tell the show hook that
  useEffect(() => {
    if (match.type === 'full') {
      setActiveModule(module);
    }
  },[match]);

这将导致无限循环。如果我注释掉setActiveModule调用,那么无限循环就消失了,但是当然,我也不会得到想要的结果。

我肯定在useEffect的使用和/或我创建的自定义钩子方面做错了,但我只是无法弄清楚它是什么。

非常感谢您的帮助

2 个答案:

答案 0 :(得分:0)

我认为这是您在Route中使用组件方式的问题。 尝试使用:

<Route
    path={`/${module.id}`}
    key={module.id}
    component={() => getComponentForModule(module)}
    defaultParams={module}
/>

编辑:

我感觉是因为您的HOC。 你可以尝试

component={ModuleComponent}
defaultParams={module}

并从match对象获取模块。


const ModuleComponent = ({ match }) => {
  const {type, module} = match;
  const { setActiveModule } = useShow();

  useEffect(() => {
    if (type === 'full') {
      setActiveModule(module);
    }
  },[module, setActiveModule]);

答案 1 :(得分:0)

match是一个对象,在useEffect中求值总是会导致代码被执行。改为跟踪match.type。另外,您需要在那里跟踪模块。如果这是一个对象,则需要将其包装在深比较钩子中:https://github.com/kentcdodds/use-deep-compare-effect

useEffect(() => {
    if (match.type === 'full') {
      setActiveModule(module);
    }
  },[match.type, module]);