Состояние внутри useEffect всегда ссылается на начальное состояние с помощью React Hooks

Каждый раз, когда я отправляю сообщение из другого компонента, я не могу получить полный список сообщений. Вот компонент ловушки и вида:

export function useChat() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = openSocket("http://localhost:3003");
    socket.on("chat message", msg => {
      const newState = update(messages, { $push: [msg] });
      setMessages(newState);
    });
  }, []);

  return { messages };
}

К сожалению, состояние не сохраняется и показывает всегда последнее сообщение:

export const HookSockets = () => {
  const { messages } = useChat();
  return (
    <div>
      {messages.map((message, index) => (
        <div key={index}>{message}</div>
      ))}
    </div>
  );
};

Если я делаю это обычным способом, все работает как задумано:

export class ClassSockets extends Component {
  state = {
    socket: openSocket("http://localhost:3003"),
    messages: [],
    message: ""
  };

  componentDidMount() {
    this.state.socket.on("chat message", msg => {
      const newState = update(this.state, {
        messages: { $push: [msg] }
      });
      this.setState(newState);
    });
  }

  handleClick = () => {
    this.state.socket.emit("chat message", this.state.message);
    this.setState({ message: "" });
  };

  handleChange = event => {
    this.setState({ message: event.target.value });
  };

  render() {
    return (
      <div>
        <div>Sockets</div>
        <div>{this.state.messages}</div>
        <input
          type="text"
          value={this.state.message}
          onChange={this.handleChange}
        />
        <button onClick={this.handleClick}>Send Message</button>
      </div>
    );
  }
}

2 ответа

Решение

Поскольку вы написали свой useEffect для выполнения при первоначальном монтировании компонента, он создает замыкание, которое ссылается на начальное значение messages и даже если сообщения обновляются, они все равно будут ссылаться на то же значение при последующих вызовах

Вы должны вместо этого настроить useEffect запускать при первоначальном монтировании и изменении сообщений

export function useChat() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = openSocket("http://localhost:3003");
    socket.on("chat message", msg => {
      const newState = update(messages, { $push: [msg] });
      setMessages(newState);
    });
  }, [messages]);

  return { messages };
} 

или же вы можете использовать шаблон обратного вызова для обновления состояния

export function useChat() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = openSocket("http://localhost:3003");
    socket.on("chat message", msg => {
      setMessages(prevMessages => update(prevMessages, { $push: [msg] }););
    });
  }, []);

  return { messages };
}

Поскольку вы пишете обработчик сокета внутри useEffect()с пустым массивом, поэтому этот эффект будет работать только один раз, когда ваш компонент будет монтироваться в первый раз. Функция (или замыкание) запомнит начальное значение сообщений, и даже если сообщения изменят socket.on()замыкание по-прежнему будет ссылаться на его начальное значение. Решением этой проблемы будет регистрация наших сообщений в массиве зависимостей эффекта.

      export function useChat() {   
const [messages, setMessages] = 
useState([]);

useEffect(() => {
const socket = openSocket("http://localhost:3003");
socket.on("chat message", msg => {
  const newState = update(messages, { $push: [msg] });
  setMessages(newState);
});   }, [messages]);
return { messages }; 
}

Здесь вы столкнетесь с новой проблемой, заключающейся в том, что каждый раз, когда сообщения изменяются, создается новый сокет с обработчиком «сообщения чата», что может привести к неожиданному и многократному запуску дополнительного кода. Чтобы решить эту проблему, вам придется отменить регистрацию предыдущего обработчика. И я рекомендую вам создать сокет только один раз (например, внутри App.js) и передать его как реквизит.

      export function useChat(socket) {   
const [messages, setMessages] = useState([]);

useEffect(() => { 
socket.on("chat message", msg => {
  const newState = update(messages, { $push: [msg] });
  setMessages(newState);
});   
//De-register old handler   
 return function(){
socket.off("chat message")   } }, [messages]);

return { messages }; }
Другие вопросы по тегам