【问题标题】:React component not detecting change in redux stateReact 组件未检测到 redux 状态的变化
【发布时间】:2021-01-18 07:51:19
【问题描述】:

我正在做一个聊天服务。

一切正常,期待以下功能:

  • 当有新消息出现时,我希望更新聊天室列表(左侧)。新消息的聊天室应该移动到第一个位置。但是,组件不会自动检测到更改。我需要点击列表进行更新。

为此,我尝试了以下减速器:

case NEW_CHAT_MESSAGE:
      const setRoomFirst = (array, roomId) => {
        const chatIndex = array.findIndex((arr) => arr._id === roomId);
        array.push(...array.splice(0, chatIndex));
        return { arr: array, hasBeenUpdated: chatIndex !== -1 };
      };

      const rooms = state.rooms;

      // move room to first position
      const updatedRooms = setRoomFirst(rooms, action.payload.message.chatRoom);

      if (updatedRooms.hasBeenUpdated) {
        // will always be the first room
        updatedRooms.arr[0].messages.push(action.payload.message);
      }

      return { ...state, rooms: updatedRooms.arr };
    default:
      return state;
  }

将管理聊天状态的组件:

function Chat() {
  const classes = useStyle();
  const dispatch = useDispatch();

  const callback = useCallback(
    (rooms) => {
      console.log("dispatch");
      dispatch(newChatMessage(rooms));
    },
    [dispatch]
  );

  const { roomId } = useParams();
  const rooms = useSelector(ChatRoomsSelector);

  const [selectedRoom, setSelectedRoom] = useState(
    roomId || (rooms.length ? rooms[0]._id : null)
  );

  const oldRoomsValue = usePrevious(rooms);

  useEffect(() => {
    console.log("rooms has changed", rooms);
    if (!selectedRoom) {
      setSelectedRoom(roomId || (rooms.length ? rooms[0]._id : null));
    }
    if (oldRoomsValue !== rooms) {
      console.log("a change in rooms has been detected");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [rooms]);

  const onSelect = useCallback((roomId) => {
    setSelectedRoom(roomId);
  }, []);

  const current = rooms.filter((room) => room._id === selectedRoom)[0];

  return (
    <Container>
      <Typography variant="h1" className={classes.title}>
        Messagerie
      </Typography>
      <Grid container spacing={2}>
        <Grid item xs={4}>
          <ChatListContainer
            rooms={rooms}
            roomSelected={selectedRoom}
            onSelect={onSelect}
          />
        </Grid>
        <Grid item xs={8}>
          {current && <ChatContentContainer room={current} />}
        </Grid>
      </Grid>
    </Container>
  );
}

export default Chat;

提前感谢您的帮助! :)

【问题讨论】:

    标签: reactjs redux state


    【解决方案1】:

    你正在改变 redux 状态并且你没有表明你正在使用 immer 所以这是你不应该做的事情:

    case NEW_CHAT_MESSAGE:
      const {
        payload: { message },
      } = action;
      //find or create room
      const room = state.rooms.find(
        (room) => room._id === message.chatRoom
      ) || { _id: message.chatRoom, messages: [] };
      //add message to room
      const roomWithMessage = {
        ...room,
        messages: [...room.messages, message],
      };
      return {
        ...state,
        //set room as first in rooms
        rooms: [
          //room with message is the first
          roomWithMessage,
          //other rooms without the room with message
          ...state.rooms.filter(
            (room) => room._id !== roomWithMessage._id
          ),
        ],
      };
    

    您的效果缺少依赖项,当您希望效果运行时,这可能会导致一些问题,并且我发现了其他一些不一致之处(请参阅代码中的 cmets):

    function Chat() {
      const dispatch = useDispatch();
    
      // your code does not show how you need to use this
      //  if this is to dispatch a new chatmessage then the
      //  paremeter passed would not be rooms but message {chatRoom:roomid, ...other}
      // const callback = useCallback(
      //   (rooms) => {
      //     console.log('dispatch');
      //     dispatch(newChatMessage(rooms));
      //   },
      //   [dispatch]
      // );
    
      const { roomId } = useParams();
      const rooms = useSelector(ChatRoomsSelector);
    
      const [selectedRoom, setSelectedRoom] = useState(
        //use optional chaining for cleaner syntax:
        //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
        roomId || rooms?.[0]?._id
      );
      //use Array.prototype.find if you want to find one item
      const current = rooms.find(
        (room) => room._id === selectedRoom
      );
    
      useEffect(() => {
        //a console.log does not show what you want to do here
        //  the only thing you actually did is change selectedRoom
        //  when current room changes and no selectedRoom was provided
        setSelectedRoom(selectedRoom=>selectedRoom || current?._id);
      }, [current]);
    
      const onSelect = useCallback((roomId) => {
        setSelectedRoom(roomId);
      }, []);
    
      return 'jsx';
    }
    

    【讨论】:

      【解决方案2】:

      非常感谢HMR 的回答! 如果有机会,我会给你一个拥抱。 ^^

      只需要纠正一件事:我需要在 reducer 中枚举过滤器的结果以使其工作。

      return {
              ...state,
              // set room as first in rooms
              rooms: [
                // room with message is the first
                roomWithMessage,
                // other rooms without the room with message
                ...state.rooms.filter((room) => room._id !== roomWithMessage._id),
              ],
            };
      

      【讨论】:

      • 是的,我忘了传播 state.rooms.filter 数组,更新了答案。很高兴听到它有帮助
      猜你喜欢
      • 2019-02-01
      • 2017-08-03
      • 2016-10-02
      • 2019-04-27
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 2018-07-31
      • 1970-01-01
      相关资源
      最近更新 更多