React Hooki useState+useEffect + event daje stan nieświeży
Próbuję użyć emitera zdarzeń z Reactem useEffect
i useState
, ale zawsze dostaje stan początkowy zamiast stanu zaktualizowanego. Działa, jeśli wywołam obsługę zdarzenia bezpośrednio, nawet za pomocą setTimeout
.
Jeśli przekażę wartość do drugiego argumentu useEffect()
, spowoduje to, że będzie działać, jednak spowoduje to ponowne zapisanie do emitera zdarzeń za każdym razem, gdy zmieni się wartość (co jest wyzwalane po naciśnięciu klawisza).
useState
, useRef
, useReducer
, i useCallback
, i nie mógł pracować.
Oto reprodukcja:
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;
Oto piaskownica kodu z tym samym w App2
:
Https://codesandbox.io/s/ww2v80ww4l
App
komponent ma 3 różne implementacje-EventEmitter, pubsub-js i setTimeout. Działa tylko setTimeout.
Edit
Aby wyjaśnić mój cel, chcę po prostu, aby wartość w handleEvent
była zgodna z wartością Codemirror we wszystkich przypadkach. Gdy jakiekolwiek kliknięcie przycisku spowoduje wyświetlenie aktualnej wartości codemirror. Zamiast tego wyświetlana jest wartość początkowa.
3 answers
value
jest przestarzały w obsłudze zdarzenia, ponieważ pobiera swoją wartość z zamknięcia, w którym została zdefiniowana. Jeśli nie zapiszemy ponownie nowego programu obsługi zdarzeń za każdym razem, gdy value
się zmieni, nie otrzyma on nowej wartości.
Rozwiązanie 1: dodać drugi argument do efektu publish [value]
. Powoduje to, że obsługa zdarzeń otrzymuje poprawną wartość, ale także powoduje, że efekt będzie uruchamiany ponownie przy każdym naciśnięciu klawisza.
Rozwiązanie 2: Użyj ref
, Aby zapisać najnowszą value
W Zmiennej instancji komponentu. Następnie wykonaj efekt, który nie robi nic poza aktualizacją tej zmiennej za każdym razem, gdy zmieni się stan value
. W programie obsługi zdarzenia użyj ref
, a nie value
.
const [value, setValue] = useState(initialValue);
const refValue = useRef(value);
useEffect(() => {
refValue.current = value;
});
const handleEvent = (msg, data) => {
console.info("Value in event handler: ", refValue.current);
};
Https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often
Wygląda na to, że są inne rozwiązania na tej stronie, które mogą również działać. Wielkie podziękowania dla @ Dinesh za pomoc.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-20 04:21:30
Zaktualizowana Odpowiedź.
Problem nie dotyczy hooków. Wartość stanu początkowego została zamknięta i przekazana do EventEmitter i była używana wielokrotnie.
Nie jest dobrym pomysłem używanie wartości stanu bezpośrednio w handleEvent
. Zamiast tego musimy przekazać je jako parametry podczas emitowania zdarzenia.
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);
const [isReady, setReady] = useState(false);
// Should get the latest value
function handleEvent(value, msg, data) {
// Do not use state values in this handler
// the params are closed and are executed in the context of EventEmitter
// pass values as parameters instead
console.info("Value in event handler: ", value);
document.getElementById("result").innerHTML = value;
}
// Get value from server on component creation (mocked)
useEffect(() => {
setTimeout(() => {
setValue("value from server");
setReady(true);
}, 1000);
}, []);
// Subscribe to events on component creation
useEffect(
() => {
if (isReady) {
ee.on("some_event", handleEvent);
}
return () => {
if (!ee.off) return;
ee.off(handleEvent);
};
},
[isReady]
);
function handleClick(e) {
ee.emit("some_event", value);
}
return (
<React.Fragment>
<CodeMirror
value={value}
options={{ lineNumbers: true }}
onBeforeChange={(editor, data, newValue) => {
setValue(newValue);
}}
/>
<button onClick={handleClick}>EventEmitter (works now)</button>
<div id="result" />
</React.Fragment>
);
}
export default App;
Oto działa codesandbox
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-14 05:23:17
UseCallback powinien działać tutaj.
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;
Sprawdź codesandbox tutaj https://codesandbox.io/s/react-base-forked-i9ro7
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-18 05:11:07