Skip to content

Instantly share code, notes, and snippets.

@mjsarfatti
Created January 5, 2019 16:24
Show Gist options
  • Save mjsarfatti/6e1261f0be5f3c9eef5da1cda7bd3ffe to your computer and use it in GitHub Desktop.
Save mjsarfatti/6e1261f0be5f3c9eef5da1cda7bd3ffe to your computer and use it in GitHub Desktop.
Async setState in React (and especially often when used w/ state management libs like 'unstated') can cause the caret in a form to jump
import React, { Component } from 'react';
export default function withSyncChange(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
value: props.value, // initial value
parentValue: props.value, // tracking the parent value
};
this.handleChange = this.handleChange.bind(this);
}
// This is all basically to mimic componentWillReceiveProps.
// Do you have a better solution?
static getDerivedStateFromProps(props, state) {
// Any time the value from the parent Component changes:
if (props.value !== state.parentValue) {
if (props.value !== state.value) {
// Update both the tracker and the input value if needed,
// this will affect the <input> field, but not reset the caret
// since handleChange will have already updated the value.
return {
parentValue: props.value,
value: props.value,
};
} else {
// Otherwise update our tracker to remain in sync,
// this will not reset the caret either.
return {
parentValue: props.value,
};
}
}
return null;
}
handleChange(e, data) {
this.setState({ value: e.target.value });
if (this.props.onChange) {
e.persist();
// According to https://stackoverflow.com/a/43232616/416714 we need to
// debounce this to avoid race conditions when typing really fast. I
// tested random-typing really really fast and it never failed me though.
this.props.onChange(e, data);
}
}
render() {
return (
<WrappedComponent
{...this.props}
value={this.state.value}
onChange={this.handleChange}
/>
);
}
};
}
@mjsarfatti
Copy link
Author

Example usage:

import React from 'react';
import { Input, TextArea } from 'semantic-ui-react';
import withSyncChange from './withSyncChange';

const SyncInput = withSyncChange(Input);
const SyncTextArea = withSyncChange(TextArea);

// blah blah blah

  <SyncInput
    name={this.props.name}
    value={this.props.value}
    onChange={this.props.handleChange}
  />

// blah blah blah

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment