web-dev-qa-db-fra.com

React Hooks UseState + UseEffect + événement donne état

J'essaie d'utiliser un émetteur d'événement avec React useEffect et useState, mais cela obtient toujours l'état initial au lieu de l'état mis à jour. Cela fonctionne si J'appelle le gestionnaire d'événements directement, même avec un setTimeout.

Si je passe la valeur à la fonction useEffect() 2e argument, il le fait fonctionner, mais cela provoque une nouvelle soumission à l'émetteur d'événement chaque fois que la valeur change de valeur (qui est déclenchée des frappes de frappe).

Qu'est-ce que je fais mal? J'ai essayé useState, useRef, useReducer et useCallback et ne pouviez pas avoir de travail.

Voici une reproduction:

import React, { useState, useEffect } from "react";
import { Controlled as CodeMirror } from "react-codemirror2";
import "codemirror/lib/codemirror.css";
import EventEmitter from "events";

let ee = new EventEmitter();

const initialValue = "initial value";

function App(props) {
  const [value, setValue] = useState(initialValue);

  // Should get the latest value, both after the initial server load, and whenever the Codemirror input changes.
  const handleEvent = (msg, data) => {
    console.info("Value in event handler: ", value);
    // This line is only for demoing the problem. If we wanted to modify the DOM in this event, we would instead call some setState function and rerender in a React-friendly fashion.
    document.getElementById("result").innerHTML = value;
  };

  // Get value from server on component creation (mocked)
  useEffect(() => {
    setTimeout(() => {
      setValue("value from server");
    }, 1000);
  }, []);

  // Subscribe to events on component creation
  useEffect(() => {
    ee.on("some_event", handleEvent);
    return () => {
      ee.off(handleEvent);
    };
  }, []);

  return (
    <React.Fragment>
      <CodeMirror
        value={value}
        options={{ lineNumbers: true }}
        onBeforeChange={(editor, data, newValue) => {
          setValue(newValue);
        }}
      />
      {/* Everything below is only for demoing the problem. In reality the event would come from some other source external to this component. */}
      <button
        onClick={() => {
          ee.emit("some_event");
        }}
      >
        EventEmitter (doesnt work)
      </button>
      <div id="result" />
    </React.Fragment>
  );
}

export default App;

Voici une boîte à sable de code avec la même chose en App2:

https://codesandbox.io/s/ww2v80ww4l

App composant a 3 implémentations différentes - EventMitter, Pubsub-JS et Settimeout. Seulement des œuvres de sécurité.

Éditer

Pour clarifier mon objectif, je veux simplement la valeur dans handleEvent pour correspondre à la valeur Codemirror dans tous les cas. Lorsque vous cliquez sur n'importe quel bouton, la valeur CODEMIRROR actuelle doit être affichée. Au lieu de cela, la valeur initiale est affichée.

38
Tony R

uSECallback aurait dû travailler ici.

import React, { useState, useEffect, useCallback } from "react";
import PubSub from "pubsub-js";
import { Controlled as CodeMirror } from "react-codemirror2";
import "codemirror/lib/codemirror.css";
import EventEmitter from "events";

let ee = new EventEmitter();

const initialValue = "initial value";

function App(props) {
  const [value, setValue] = useState(initialValue);

  // Should get the latest value
  const handler = (msg, data) => {
    console.info("Value in event handler: ", value);
    document.getElementById("result").innerHTML = value;
  };

  const handleEvent = useCallback(handler, [value]);

  // Get value from server on component creation (mocked)
  useEffect(() => {
    setTimeout(() => {
      setValue("value from server");
    }, 1000);
  }, []);

  // Subscribe to events on component creation
  useEffect(() => {
    PubSub.subscribe("some_event", handleEvent);
    return () => {
      PubSub.unsubscribe(handleEvent);
    };
  }, [handleEvent]);
  useEffect(() => {
    ee.on("some_event", handleEvent);
    return () => {
      ee.off(handleEvent);
    };
  }, []);

  return (
    <React.Fragment>
      <CodeMirror
        value={value}
        options={{ lineNumbers: true }}
        onBeforeChange={(editor, data, newValue) => {
          setValue(newValue);
        }}
      />
      <button
        onClick={() => {
          ee.emit("some_event");
        }}
      >
        EventEmitter (works)
      </button>
      <button
        onClick={() => {
          PubSub.publish("some_event");
        }}
      >
        PubSub (doesnt work)
      </button>
      <button
        onClick={() => {
          setTimeout(() => handleEvent(), 100);
        }}
      >
        setTimeout (works!)
      </button>
      <div id="result" />
    </React.Fragment>
  );
}

export default App;

Vérifiez la codesandbox ici https://codesandbox.io/s/react-base-forked-i9ro7

0
Lokii