-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change event fires extra times before IME composition ends #3926
Comments
cc @salier :) – What should we do here? |
I think we should not fire One way to handle this in I did some quick testing on OSX Chrome and Firefox with Simplified Pinyin and 2-Set Korean, and the event order and data seem correct enough. (I predict that we'll have problems with IE Korean, but we may get lucky.) I think we may continue to see issues with alternative input methods like the Google Input Tools extension, but there may be workarounds for that. |
Sorry this seems to not be related. My apologies. |
Is there any update? Suffering from this issue too. |
None currently – this is not a high priority for us right now. I'd be happy to look at a pull request if anyone dives into fixing this. |
@salier It seems like IE does not fire |
Similar problem with: Change event fires too many times when inputing Chinese characters facebook/react#3926
Similar problem with: Change event fires too many times when inputing Chinese characters facebook/react#3926
in ie 9, Change event fires too many times when inputing Chinese characters again |
Hello, Facebook guys, in fact this issue causes a SERIOUS problem: we can't update input asynchronously with Chinese input. I really hope you can fix this quickly, so that we won't waste efforts to workaround it here and there and over and over again. Thanks. Here is my workaround. If anyone face the same problem, you can have a look |
I made a simple example to demo how to use |
@eyesofkids nice work, this could be made as the default implementation of |
nice work ! |
I was hitting the same issue and @eyesofkids' workaround works perfectly (thank you!). After having the workaround in place, I was diving into React's source code to at least try to add a failing test for this —hoping to later add the expected behavior to the library— although it seems a bit complicated for someone unfamiliar with the internals. Initially I was expecting that a test similar to what's already available for Hence I was thinking that maybe checking for var EventConstants = require('EventConstants');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var topLevelTypes = EventConstants.topLevelTypes;
function extract(node, topLevelEvent) {
return ChangeEventPlugin.extractEvents(
topLevelEvent,
ReactDOMComponentTree.getInstanceFromNode(node),
{target: node},
node
);
}
function cb(e) {
expect(e.type).toBe('change');
}
var input = ReactTestUtils.renderIntoDocument(
<input onChange={cb} value='foo' />
);
ReactTestUtils.SimulateNative.compositionStart(input);
var change = extract(input, topLevelTypes.topChange);
expect(change).toBe(null); I'm afraid I don't know exactly how one is supposed to debug these tests—otherwise I'd have a clearer picture of what's going on. Any guidance on how to proceed or any other pointers would be highly appreciated. |
The workaround suddenly broke in Chrome 53+ and it seems it is not valid anymore because they changed the order |
https://github.com/suhaotian/react-input maybe help someone |
There is a tricky solution for Chrome v53. To call the handlechange after handleComposition = (event) => {
if(event.type === 'compositionend'){
onComposition = false
//fire change method to update for Chrome v53
this.handleChange(event)
} else{
onComposition = true
}
} check the demo here: https://jsfiddle.net/eyesofkids/dcxvas28/11/ |
@chenxsan did you find out the solution? |
Hi, I face the same issue when I want to do some operation in handleChange function, and my solution is just use composition event to handle it. If it works for you, extract it to hooks and we use reuse it easily. import React, { useState } from 'react';
export default function Solution() {
const [value, setValue] = useState('');
const [onComposition, setOnComposition] = useState(false);
function handleComposition(event) {
if (event.type === 'compositionstart') {
setOnComposition(true);
}
if (event.type === 'compositionend') {
setOnComposition(false);
}
}
function handleChange(e) {
let temp = e.target.value;
if (!onComposition) {
// any operation you want to do...
temp = temp.replace(/z/g, 'Z');
}
setValue(temp);
}
return (
<input
onChange={handleChange}
value={value}
onCompositionStart={handleComposition}
onCompositionEnd={handleComposition}
/>
);
} |
also this code https://zhuanlan.zhihu.com/p/415365117 |
A simpler component: // refs: https://zhuanlan.zhihu.com/p/415365117
import { useRef, useState, forwardRef } from 'react'
function InputIME(props, ref) {
const { onChange, value, ...otherProps } = props
// log if on composition
const onComposition = useRef(false)
// temp input
const [inputValue, setInputValue] = useState('')
const _onChange = (event) => {
setInputValue(event.target.value)
// IME method start
if (event.type === 'compositionstart') {
onComposition.current = true
return
}
// IME method end
if (event.type === 'compositionend') {
onComposition.current = false
}
// handle parent onChange
if (!onComposition.current) {
onChange(event)
}
}
return (
<input
{...otherProps}
ref={ref}
value={inputValue}
onChange={_onChange}
onCompositionEnd={_onChange}
onCompositionStart={_onChange}
/>
)
}
export default forwardRef(InputIME) |
Another hook-ish workaround: import { useEffect, useState, useRef } from "react";
export function useAsyncInputRef<T extends HTMLElement & { value: string }>(
value: string,
forwardRef?: React.RefObject<T>
) {
const [compositionFlag, setComposition] = useState(false);
const newRef = useRef<T>(null);
const ref = forwardRef || newRef;
useEffect(() => {
const el = ref.current;
if (el) {
const compositionStart = () => setComposition(true);
const compositionEnd = () => setComposition(false);
el.addEventListener("compositionstart", compositionStart);
el.addEventListener("compositionend", compositionEnd);
return () => {
el.removeEventListener("compositionstart", compositionStart);
el.removeEventListener("compositionend", compositionEnd);
setComposition(false);
};
}
}, [ref]);
useEffect(() => {
if (ref.current && compositionFlag === false) {
ref.current.value = value;
}
}, [compositionFlag, value, ref]);
return ref;
} render(
<input
type="text"
ref={useAsyncInputRef(value)}
onChange={(e) => setValue(e.target.value)}
/>
); Live demo: https://codepen.io/kamikat/pen/OJwMJLr |
Here is another one implementation: interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
onChange: (e) => void;
onRealChange?: (e) => void;
comp?: React.FunctionComponent;
}
const CompositionEventInput: FC<Props> = ({
onChange,
onRealChange,
comp: Comp,
...rest
}: Props) => {
const [isComposition, setIsComposition] = useState(false);
const [value, setValue] = useState("");
const common = (e) => {
const { type } = e;
switch (type) {
case "compositionstart":
setIsComposition(true);
break;
case "compositionend":
if (isComposition) {
onChange(e);
}
setIsComposition(false);
break;
case "change":
const newValue = e.target.value.trim();
setValue(newValue);
if (onRealChange) {
onRealChange(newValue);
}
if (!isComposition) {
onChange(e);
}
break;
}
};
const events: Partial<HTMLAttributes<Function>> = {
onKeyDown: common,
onKeyUp: common,
onCompositionEnd: common,
onCompositionStart: common,
onCompositionUpdate: common,
onChange: common
};
const props = {
value,
...events
};
if (Comp) {
return <Comp {...props} />;
}
// @ts-ignore
return <input {...props} {...rest} />;
}; |
Is there an official improvement plan for this? I'm suffering from using workarounds anywhere. |
Nice,thank you! ——weihong
|
To bypass react's IME glitch. facebook/react#3926
|
Nice,thank you! ——weihong
|
Extra details
Original Issue
When I was trying this example from https://facebook.github.io/react/blog/2013/11/05/thinking-in-react.html, any Chinese characters inputted by Chinese pinyin input method would fire too many renders like:
Actually I would expect those not to fire before I confirm the Chinese character.
Then I tried another kind of input method - wubi input method, I got this:
It's weird too. So I did a test in jQuery:
Only after I press the space bar to confirm the character, the
keyup
event would fire.I know it might be different between the implementation of jQuery
keyup
and reactonChange
, but I would expect the way how jQuerykeyup
handles Chinese characters instead of react'sonChange
.The text was updated successfully, but these errors were encountered: