Accent and deferred React's setState on input onChange - javascript

I'm creating a little utility where I need call a setState deferred to next tick fired by an input's onChange handler. Below a simple snippet showing the basic concept.
https://jsfiddle.net/samuelsimoes/q3p44sz1/
class MyComponent extends React.Component {
constructor () {
super(...arguments);
this.state = {};
}
onChange (value) {
setTimeout(v => {
this.setState({ name: v });
}.bind(this, value), 0);
}
render () {
return (
<div>
<input
type="text"
value={this.state.name}
onChange={evt => this.onChange(evt.target.value)} />
</div>
);
}
};
ReactDOM.render(
<MyComponent/>,
document.getElementById("app-container")
);
If you run this snippet in a browser on Mac OS and try to type some letter with an accent you get an awkward behavior different on each browser. On Chrome the accent only works for the first time, after the accent isn't applied anymore (take a look on the gif below). On Firefox, the accent and letter don't appear.
Do you guys have any clue about this?
p.s.: I tested this behavior on React 0.13, 0.14 and 15.0.2.

Basically you shouldn't defer the setState. React won't work properly in this situation.
Look: https://github.com/facebook/react/issues/6563
What is happening:
Let's suppose that you press the letter A.
When you trigger the onChange in the field, React processes all the state mutations.
After the state mutation process, React does the DOM diff to update the component and at this stage the state value for this field is an empty value, so React does a node.value = "".
On the next tick our deferred setState is triggered applying the letter A with the node.value = "A".
This behavior breaks the browsers on MacOS where they replace the accent on the "intermediate state" to a typed accent preventing user to type the accentuated character.
So, sadly there's no solution.

Related

React setState not firing in first time

i saw similar questions but can't get some solution.
i have some input when you paste some text to input i'm trying to set it to state and render this text. But when paste in first time state not change in second time when paste everything works.
I don't understand why it happen i already used some setTimeOut function too for it but not helped.
here is my code:
import React from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor() {
super();
this.state = {
no: ""
};
}
pasteSomeText = e => {
this.setState({
no: e.target.value
});
};
render() {
return (
<div style={styles}>
{this.state.no}
<input onPaste={event => this.pasteSomeText(event)} />
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here is codeSandBox example: https://codesandbox.io/s/1-react-hello-world-tvscf?fontsize=14
To get the data from the event, use e.clipboardData.getData('text'). See https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
e.target.value gives you the value for the text field before it updates with the paste data, so you can cancel/modify the paste if you want.
pasteSomeText(e) {
this.setState({
no: e.clipboardData.getData('text')
});
};
Side note: with the call from the HTML, you don't need to use this arrow function (which uses more memory), see my example above
Basically the reason why you get empty state on firts onpaste call is that onpaste fires before the input's value is changed. So you get empty event.target.value and for all other calls you get previous value.
And if you still decide to keep going with onpaste you should keep in mind that you can get pasted value with event.clipboardData.getData('Text') but it can differ from what have been pasted in input because of different behavior of clipboardData for different input types.
https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
So for work with clipboard data I will recommend to use DataTransfer module from https://www.npmjs.com/package/fbjs to get more consistant behavior in different systems and browsers.
Is a diferente kind of event you will need to get the clipboard data from the event and set it to the state
const value = e.clipboardData.getData('text')
use
onChange
instead of using
onPaste
Updated https://codesandbox.io/s/1-react-hello-world-h2jgv

React only re-renders modified contenteditable once

This is a bit of a weird one to reproduce, had to use timeouts to hack it but it does emulate the issue I'm having.
https://jsfiddle.net/mwqyub1v/13/
1) React renders the div with abc123. Manipulate this string (add a 4 or something)
2) After the first timeout, React will remove the content -- type something in to the box manually now, before the second timer triggers..
3) At the second timer, React does run the render method (note the logging fires) with this.props.value as null, but the div does not update (it won't remove any text you've typed in since abc123 went away).
I know playing with the contents of a contenteditable can prevent React from being able to manage that content (hence me suppressing the warning). But why does the component update correctly after I've modified text in step 1 above? Furthermore, if the second timer sets value to be some other text, as opposed to null, then once again the component updates properly.
How can I get this component to always reset the contenteditable contents, even when value is null?
The reason that it's not updated with the second timeout because the virtual dom didn't detect any difference. This will set the innerHTML based on the props: https://jsfiddle.net/mwqyub1v/35/.
class App extends React.Component {
componentDidMount() {
this.node = React.findDOMNode(this);
}
componentDidUpdate() {
this.node.innerHTML = this.props.value;
}
render() {
console.log('value', this.props.value);
return React.createElement('div', {
contentEditable : true,
suppressContentEditableWarning : true
}, this.props.value);
}
}

HyperHTML - is it possible to update a component state without re rendering the entire component DOM?

I have the following hyperHTML component, everything is working as intended, the only issue is that the entire component DOM re renders on every this.setState() call.
My question:
Is there a way to update the notification string on the DOM without re rendering the entire component DOM ?
const { hyper } = hyperHTML;
class searchComponent extends hyper.Component {
constructor(data) {
super();
this.setState({ notification: '' });
}
onChange() {
// ...
if ( condition ) {
this.setState(() => ({ notification: 'notification text!' }));
}
// ...
}
async listTags() {
//...
// async content
//...
}
render() {
this.html `
<div id="search_container">
<select name="tags" id="tags_search" class='form-control m0' multiple="multiple" onChange=${this.onChange}>
${{
any: this.listTags(),
placeholder: 'incoming..!',
}}
</select>
<div class="search_notification">
<!-- I want to re render only this part of the DOM -->
${this.state.notification}
</div>
</div>
`
}
}
There are few gotchas in your code.
To start with, that onChange won't work as expected, you are losing the context as it is. You either just use onchange HTML event, and you call the method onchange and you use simply this as event listener:
const { hyper } = hyperHTML;
class SearchComponent extends hyper.Component {
onchange() {
this instaneof SearchComponent; // true
}
render() { return this.html`
<div id="search_container">
<select onchange=${this}>
${...}
</select>
</div>`
}
}
or you bind the onChange method within the constructor.
You also forgot to return this.html inside render(), which is something needed if you want to place your component on the DOM.
However, assuming these were irrelevant errors there for quick demo code sake, you need to understand that async world is async.
If you have an asynchronous operation there's no way you can tell if that resolved or not, you just pass it along, and this is exactly what you are doing.
You are changing the state of the component expecting that previously async operations would not be triggered again.
But how can the component know if such state change would produce a different layout? The TL;DR answer is that it cannot, the slightly longer one is that you have an asynchronous, unrelated, behavior, attached to each updates but you want that asynchronous behavior to be triggered only once, and not per update.
Possible solutions
You can either create a sub component a part, and instantiate it once during the SearchComponent initialization (constructor) or simply use a placeholder that makes more sense than the current one.
Indeed, right now you are creating output like <select>incoming..!</select> which makes no sense as standard HTML.
A select can have option tags, using it as a generic container of some text is like abusing its potentials.
What you want is something like a disabled=${this.optionsResolved} on the select, and ${this.options} as array of its content.
In this way you have no changes whatsoever while resolving your options, and a proper list of options once that happens.
You can see an example in this Code Pen

Testing React components - mount vs shallow and simulating events

I have the following React components that I'm trying to test by simulating entry into the input field with id=componentCount. I started using React for the first time less than one week ago, so this framework is very new to me and any help would be appreciated.
I'm also using Semantic UI React for the CSS framework, hence the Form.Group and Form.Input tags
export class SoftwareForm extends React.Component {
constructor(props) {
super(props);
this.state = {
componentCount: 0
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<Form id='softwareForm'>
<Form.Group>
<Form.Input label='Name' placeholder='Component Name' id='componentName' type='text'/>
<Form.Input label='Description' placeholder='Component Description' id='componentDescription' type='text'/>
<Form.Input name='componentCount'
label='Count'
placeholder='Count'
id='componentCount'
type='number'
min='0'
value={this.state.componentCount}
onChange={this.handleInputChange}/>
</Form.Group>
</Form>
);
}
}
Test script
describe('<SoftwareForm />', () => {
it('Count field accepts text input', () => {
const softwareFormComponent = mount(<SoftwareForm />);
const countField = softwareFormComponent.find('#componentCount');
countField.simulate('change', {target: {value: 3}});
expect(softwareFormComponent.state('componentCount')).toBe(3);
});
});
The above test script uses mount for full rendering, but I get the following error, "Method “simulate” is only meant to be run on a single node. 4 found instead."
If I use a shallow mount, the test output is the following
Expected value to be (using ===):
3
Received:
0
This tells me that the simulate method is not working as expected.
Several questions:
Can a shallow render be used in this scenario since I can use the find method to search for the element with the 'componentCount' id attribute or is a full render with a mount necessary since I'm trying to manipulate an input element which is a child of the Form.Group?
Regardless of the answer the question 1, when mount is used, the wrapper that is returned contains 4 nodes - what are these 4 nodes and which one am I supposed to call simulate on if a full render is in fact needed in this case?
I'm struggling due to not having a clear understanding of the structure of the wrapper object that is returned by either shallow or mount.
The idea of the last two statements in the test script is to simulate the change to the value of the number in the input field, have the onChange event handler trigger an update of the componentCount state value and then the perform a comparison using the assert statement. Is this the correct approach?
Trying to answering your questions:
You could use mount, which create test on a full DOM rendering and it is ideal for use cases where you have components that may interact with DOM APIs, or may require the full lifecycle in order to fully test the component.
You can see that find returns using .debug().
After a few more attempts, I was able to get the test to work.
A shallow mount ended up being sufficient in this case even though I was interacting with input DOM element.
The simulate method used in the test script was changed to the following,
"countField.simulate('change',{
target: { name: "componentCount", value: 3 } });

React Checkbox Does Not Update

I want to update an array each time a checkbox is toggled to true. With this current code, if I click on a checkbox it will log that it's false. Even though I have just updated the state. Does setState just take some time, like an API call? That doesn't make sense to me.
import React, {Component} from 'react';
class Person extends Component {
constructor(props) {
super(props);
this.state = {
boxIsChecked: false
};
this.checkboxToggle = this.checkboxToggle.bind(this);
}
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked });
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked} onClick={this.checkboxToggle} />
{this.props.person.name} ({this.props.person.age})
</div>
);
}
}
export default Person;
I have tried onChange instead of onClick. I feel like I'm already following advice I've read about for the basic component formulation from here and here. Is the fact I'm using Redux for other values affecting anything? Is there a way to just read what the checkbox is, instead of trying to control it? (The checkbox itself works fine and the DOM updates wether it's checked or not correctly.)
I know that this thread is answered but i also have a solution for this, you see the checkbox didn't update with the provided value of setState, i don't know the exact reason for this problem but here is a solution.
<input
key={Math.random()}
type="checkbox"
name="insurance"
defaultChecked={data.insurance}
/>
by giving the key value of random the checkbox gets re-rendered and the value of the checkbox is updated, this worked for me. Also i am using hooks, but it should work for class based implementation to.
reference: https://www.reddit.com/r/reactjs/comments/8unyps/am_i_doing_stupid_or_is_this_a_bug_checkbox_not/
setState() is indeed not reflected right away:
Read here in the docs:
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
and here some experiments
So it might be better to catch the event and check it, and not depend on the this.setState()
Something like that:
handleChange: function (event) {
//you code
if (event.target.checked) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
and
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked}
onChange={this.handleChange.bind(this)}/>
{this.props.person.name} ({this.props.person.age})
</div>
);
}
You can't access the updated value of state just after calling setState().
If you want to do stuff with updated value of state, you can do like this. i.e inside setState() callback function
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked },()=>{
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
});
React solution:
HTML:
<input type="checkbox" ref={checkboxRef} onChange={handleChange}/>
JS:
const checkboxRef = useRef("");
const [checkbox, setCheckbox] = useState(false);
const handleChange = () => {
setCheckbox(checkboxRef.current.checked);
};
Now your true/false value is stored in the checkbox variable
To expand no newbies answer; what worked for me:
remove preventDefault() from onChange Event-Listener but this didn't feel right for me since I want only react to update the value. And it might not apply in all cases like OPs.
include the checked value in the key like key={"agree_legal_checkbox" + legalcheckboxValue} checked={legalcheckboxValue}
I got both these solutions from newbies answer but having a math.random key just felt wrong. Using the value for checked causes the key only to change when when the checked value changes as well.
(not enough reputation to simply post this as a comment, but still wanted to share)
I arrived here trying to figure out why my checkbox, rendered using the HTM tagged template library, wasn't working.
return html`
...
<input type="checkbox" checked=${state.active ? "checked" : null} />
...
`;
It turns out I was just trying too hard with that ternary operator. This works fine:
return html`
...
<input type="checkbox" checked=${state.active} />
...
`;

Categories

Resources