反应挂钩状态未更新

时间:2020-11-12 08:50:07

标签: reactjs react-hooks state

在我的应用中,我有一个RoomsContainer父组件,其中有Room个子组件数组。在Room中,我希望有一个按钮,可以通过调用父组件的Room函数来向RoomsContainer添加其他addRoom元素。

这就是我所拥有的:

function RoomsContainer() {
  const [rooms, setRooms] = useState([]);

  const addRoom = () => {
    var uniqid = require('uniqid');
    let key = uniqid("room-");
    setRooms([
      ...rooms,
      <Room
        key={key}
        id={key}
        addRoom={addRoom}
        removeRoom={removeRoom}
      />
    ]);
    console.log(rooms);
  }

  useEffect(() => {
    addRoom();
  }, []);

  // ...

  return (
    <div id="room-wrapper">
      {rooms}
    </div>
  )
}

function Room(props) {
  // ...
  return (
    <button onClick={props.addRoom}>
      Add room
    </button>
  )
}

这会添加页面加载的第一个房间,但是当我单击Add Room组件中的Room按钮时,它不会添加任何其他房间。单击它似乎还可以使用新的RoomsContainer元素完全重新渲染整个Room组件,而不用修改其状态。而且console.log(rooms)总是显示一个空数组,即使在设置后也是如此。

2 个答案:

答案 0 :(得分:1)

您的addRoom函数中有一个stale closure,我认为您不应该将jsx保存到本地状态。以下应该起作用:

const id = ((id) => () => ++id)(0);

function RoomsContainer() {
  const [rooms, setRooms] = React.useState([]);
  //not sure why you want to use require in a react app
  //var uniqid = require('uniqid');

  //use React.useCallback so this function never changes
  const addRoom = React.useCallback(
    //setRooms takes a callback function that receives the current rooms
    //  only store the id in rooms, not the jsx
    () => setRooms((rooms) => [...rooms, id()]),
    []
  );
  const removeRoom = React.useCallback(
    //setRooms takes a callback function that receives the current rooms
    //  only store the id in rooms, not the jsx
    (id) =>
      setRooms((rooms) =>
        rooms.filter((room) => room !== id)
      ),
    []
  );

  React.useEffect(() => {
    addRoom();
  }, [addRoom]);

  return (
    // do not use id in html elements, id should be unique
    // in the DOM but you can't/shouldn't guarantee that your app doesn't
    // have multiple RoomContainer elements
    <ul>
      {rooms.map((id) => (
        <Room
          key={id}
          id={id}
          addRoom={addRoom}
          removeRoom={removeRoom}
        />
      ))}
    </ul>
  );
}
//make Room a pure component so there are no needless re renders
const Room = React.memo(function Room({
  addRoom,
  removeRoom,
  id,
}) {
  return (
    <li>
      <button onClick={addRoom}>Add room</button>
      <button onClick={() => removeRoom(id)}>
        Remove room {id}
      </button>
    </li>
  );
});

ReactDOM.render(
  <RoomsContainer />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

答案 1 :(得分:1)

console.log(rooms);最初显示一个空数组是正常的,因为设置状态是异步的。

这是您应该做的:

function RoomsContainer() {
  const [rooms, setRooms] = useState([]);

  const addRoom = useCallback(() => {
    var uniqid = require("uniqid");
    let key = uniqid("room-");
    setRooms(rooms => [...rooms, key]);
  }, []);

  useEffect(() => {
    addRoom();
  }, []);

  return (
    <div id="room-wrapper">
      {rooms.map((key) => {
        return <Room key={key} id={key} addRoom={addRoom} />;
      })}
    </div>
  );
}