太多的重新渲染。 React 限制渲染次数以防止无限循环。 useState 问题?

时间:2021-02-07 11:35:27

标签: javascript reactjs

我是反应的新手,我正在学习如何使用 useState。我想要实现的任务是允许用户在 UserMenu 组件的菜单中选择一个项目,该组件将设置 userId,它是主 App 中的一种状态。结果是页面刷新以显示新 userId 的相关信息。

我尝试将 setUserIdApp 传递给 UserMenu 的方法是使用回调函数 updateUserIdgetNewUserId。但是,遇到无限循环错误,我不确定是什么原因。非常感谢任何帮助!

以下是相关部分。

App.js

function App() {
  const classes = useStyles()

  const [userId, setUserId] = useState(1)
  const [user, setUser] = useState(null)
  const [posts, setPosts] = useState([])
  const [userList, setUserList] = useState([])

  useEffect(() => {
    const getUser = async () => {
      const userFromServer = await fetchUser()
      if (userFromServer) {
        setUser(userFromServer)
      } else {
        console.log("error")
      }
    }
    getUser()
  }, [userId])

  useEffect(() => {
    const getPosts = async () => {
      const postsFromServer = await fetchPosts()
      setPosts(postsFromServer)
    }

    getPosts()
  },[userId])

  useEffect(() => {
    const getUserList = async () => {
      const userListFromServer = await fetchUserList()
      setUserList(userListFromServer)
    }
    getUserList()
  }, [])

  // Fetch user 
  const fetchUser = async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
    const data = await res.json()

    return data
  }

  // Fetch posts
  const fetchPosts = async () => {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts?userId=1')
    const data = await res.json()

    return data
  }

  // Fetch list of users
  const fetchUserList = async () => {
    const res = await fetch('https://jsonplaceholder.typicode.com/users/')
    const data = await res.json()

    return data
  }

  const updateUserId = updatedUserId => {
    setUserId(updateUserId)
  }

  return (
    <div>
      <Box className={classes.headerImage}>
        <UserMenu getNewUserId={updateUserId} userList = {userList} />
      </Box>
      <Container maxWidth="lg" className={classes.userContainer}>
        {user ? <UserInfo user = {user} /> : 'loading...'}
      </Container>
      <Container maxWidth="lg" className={classes.blogsContainer}>
        {user ? <PostList name = {user.name} posts = {posts} /> : 'loading...'}
      </Container>
    </div>
  );
} 

export default App;

完整的 UserMenu.js 供参考。我从材料 UI 中复制粘贴了代码。我修改的唯一部分是在上面的代码段中。 用户菜单.js

import Button from '@material-ui/core/Button';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import Grow from '@material-ui/core/Grow';
import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper';
import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';
import { makeStyles } from '@material-ui/core/styles';
import { useState, useRef, useEffect } from 'react'

const useStyles = makeStyles((theme) => ({
  root: {
    display: 'flex',
  },
  paper: {
    marginRight: theme.spacing(2),
  },
  button: {
      backgroundColor: "rgba(0,0,0,0.1)",
      opacity: 0.7,
  }
}));

const UserMenu = ({ getNewUserId, userList }) => {
  const classes = useStyles();
  const [open, setOpen] = useState(false);
  const anchorRef = useRef(null);

  const handleToggle = () => {
    setOpen((prevOpen) => !prevOpen);
  };

  const handleClose = (user, event) => {
    if (anchorRef.current && anchorRef.current.contains(event.target)) {
      return;
    }
    console.log(user)
    getNewUserId(user.id)
    setOpen(false);
  };

  function handleListKeyDown(event) {
    if (event.key === 'Tab') {
      event.preventDefault();
      setOpen(false);
    }
  }

  // return focus to the button when we transitioned from !open -> open
  const prevOpen = useRef(open);
  useEffect(() => {
    if (prevOpen.current === true && open === false) {
      anchorRef.current.focus();
    }

    prevOpen.current = open;
  }, [open]);

  return (
    <div className={classes.root}>
      <div>
        <Button className={classes.button}
          ref={anchorRef}
          aria-controls={open ? 'menu-list-grow' : undefined}
          aria-haspopup="true"
          onClick={handleToggle}
        >
          Change User
        </Button>
        <Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
          {({ TransitionProps, placement }) => (
            <Grow
              {...TransitionProps}
              style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
            >
              <Paper>
                <ClickAwayListener onClickAway={handleClose}>
                  <MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
                    {userList.map((user) => (
                        <MenuItem onClick={(event) => handleClose(user, event)}>{user.name}</MenuItem>
                    ))}
                  </MenuList>
                </ClickAwayListener>
              </Paper>
            </Grow>
          )}
        </Popper>
      </div>
    </div>
  );
}

export default UserMenu

1 个答案:

答案 0 :(得分:1)

setUserId(updateUserId) 应该是 setUserId(updatedUserId)。如果您将函数 (updateUserId) 传递给 setState,它实际上会执行函数更新,如下所述:https://reactjs.org/docs/hooks-reference.html#usestateupdateUserId 很可能被无限多次递归调用。