Sync React state to Meteor collection using debounce - javascript

I have a textbox in my Meteor + React application. I want to sync its value to a Mongo collection. However, I don't want to update the collection after every keystroke, only when the user has stopped typing for a few seconds.
The textbox in my render() function looks like this:
<input type="text" ref="answer" onChange={this.onChange} value={this.state.someValue} />
I store the textbox value in this.state instead of this.data because this.data reflects the Mongo collection, which might have not been updated yet.
So far, all of this works.
The problem:
If another client updates the collection, I want the textbox to show the updated value. For this I have to update this.state inside the getMeteorData() function, but that's disallowed, and I get an error: "Calling setState inside getMeteorData can result in infinite loop".
Right now I have a workaround where I manually update the textbox value in componentDidMount() and getMeteorData(), but it feels hackish and I don't like it at all.
Is there a better way to do this? Can I maybe force state updates inside getMeteorData() if I promise I'll be a good boy and behave nicely?

I would get rid of getMeteorData at all and turn to createContainer. Data flow gets clear and simple most of the time, including this specific case. Here it goes.
First thing first, create a container to fetch data.
export default theContainer = createContainer(() => {
// Subscribe to the publication which publishes the data.
const subscription = Meteor.subscribe(...);
// Derive the data for the input box and form the props to pass down.
const props = {
answer: getAnswer(subscription)
};
return props;
}, theComponent);
theContainer acts as a container component and transferes the contained data to the presentational component theComponent by props. Be noted that the function given to createContainer is responsive, meaning that changes to reactive data sources in that function trigger rerun and result in rerender of theComponent.
By now we are all armed. Since data in the Mongo collection (Minimongo exactly) is synced by the props passed down, theComponent is aware of the synchronization by a prop transition.
export default class theComponent extends React.Component {
...
componentWillReceiveProps(nextProps) {
if (this.props.answer !== nextProps.answer) {
this.setState({
answer: nextProps.answer
});
}
}
render() {
return <input value={this.state.answer} onChange={this.onChange} />;
}
}
While such transition occurs, the upcoming value is updated to the state, and this controlled component will render the input based on the updated new value.
On the other hand, while the user starts typing, the change handler this.onChange updates the user's input to the state with every key stoke for this is a controlled component. However, the handler updates the Mongo collection (again, Minimongo exactly) only when the preset duration has elapsed to save data transmission.

Related

React best practice for changing state of parent component from child without rerendering all children?

I have a project where I'm displaying cards that contain attributes of a person in a textfield, and the user can edit the textfield to directly change that person's attribute values. However every time they're editing the textfield it causes a rerender of all cards which slows down the app. Here is an example:
export default Parent() {
const [personList, setPersonList] = useState(/* list of person objects*/);
const modifyPerson(index, property, value) {
const newPersonList = _.cloneDeep(personList);
newPersonList[index][property] = value;
setPersonList(newPersonList);
}
const children = personList.map((person, index) => {
<Person
modifyPerson={modifyPerson}
index=index
/*properties on the person */
/>
});
return <div> {children} </div>
}
export default Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
}
So essentially when the child's text field is updated, it triggers a state change in the parent that stores the new value, then refreshes what the Child looks like. There's no button that the user clicks to "save" the values-- as soon as they edit the textfield it's a permanent change. And also the parent needs to know the new values of every Person because there's some functions that require knowledge of the current state of the person list. The Person component contains images that slow down the rendering if done inefficiently.
Is there a better way to make this design more performant and reduce rerenders? I attempted to use useCallback to preserve the functions but I don't it works in this specific design because the property and values are different-- do I have to create a new "modifyPerson" for each exact attribute?
Use React.memo()
React.Memo will check the props passed to the component and only if the props changes , it will re-render that particular component.
So, if you have multiple Person component which are getting props which are explicit to those Person, and when you change a particular Person component which leads to Parent state getting updated, only that Person component which you had modified will re-render because its props will change(assuming that you pass the changed value to it).
The other components will have the same props and wont change.
export default React.memo(Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
})
As others have already said React.memo() is the way to go here, but this will still re-render because you recreate modifyPerson every time. You can use useCallback to always get the same identity of the function, but with your current implementation you would have to add personList as dependency so that doesn't work either.
There is a trick with setPersonList that it also accepts a function that takes the current state and returns the next state. That way modifyPerson doesn't depend on any outer scope (expect for setPersonList which is guaranteed to always have the same identity by react) and needs to be created only once.
const modifyPerson = useCallback((index, property, value) {
setPersonList(currentPersonList => {
const newPersonList = _.cloneDeep(currentPersonList);
newPersonList[index][property] = value;
return newPersonList;
})
}, []);

React.js: Should parent or component manage state?

I have a "Date input" component that contains a <input type="text">.
It's purpose is to allow users to type in a valid date (e.g., mm/dd/yyyy).
Once a user enters a valid date, the parent of the component should receive that date.
Based on this - I'm trying to figure out whether the component or the parent should manage state.
If I set it up so the parent manages state:
render() {
return (
<div>
<input type="text" value={this.props.value} onChange={this.props.handleChange} />
</div>
);
}
... this is no good, because the parent will be notified with every single change, and the prop will be set to all the "draft" values (e.g., "07/2") while the user is typing.
That suggests that I should set this up so that the component manages it's own state:
class InputDateWithLabel extends Component {
constructor(props) {
super(props);
this.state = {
value: formatDate(this.props.value) // formatDate() formats a date object into an 'mm/dd/yyyy' string
};
this.handleChange = this.handleChange.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
handleChange(event) {
// use setState() to update this.state.value so the <input> shows what the user types
}
handleBlur(event) {
// if the user entered a valid date,
// convert the user's input to a date object,
// and communicate that to the parent. E.g.,:
this.props.onChange([new date]);
// Otherwise, handle the "error"
}
render() {
return (
<input type="text" value={this.state.value} onChange={this.handleChange} onBlur={this.handleBlur} />
);
}
}
This version works exactly the way I want it to, except for one more requirement...
Based on things that might happen elsewhere in my application, the parent may need to set the date in this component. However - now that the component is managing it's own state - when the parent changes props.value, my component will ignore it.
The React documents address this scenario here: You Probably Don't Need Derived State
But their solutions don't seem to apply:
One way to avoid the problems mentioned above is to remove state from our component entirely.
This is no good, because I don't want to make the parent responsible for validating the user's date input. My component should be self-contained, including the date validation, which means it needs to manage the "draft states" of the user's input.
Another alternative would be for our component to fully own the “draft” state. In that case, our component could still accept a prop for the initial value, but it would ignore subsequent changes to that prop
This is no good, because I need to retain the ability for the parent to change the value when appropriate.
The rest of the React documentation mentions a few other possibilities (getDerivedStateFromProps), but it goes to great lengths to stress that they're probably not correct. (Note the title of the article!)
This does not seem like an uncommon situation, so there must be a clear, simple, well-documented way to handle it, that's done the right "React-way". What is that?
Having a component manage it's own state doesn't seem that bad in your case, but you will need to add componentWillReceiveProps which adds another piece of code to manage.
componentWillReceiveProps(nextProps) {
this.setState({
value: formatDate(nextProps.value)
});
}

ReactJS one time initialisation

I have a Component say HomePage where I'm calling getCurrentLocation to get the location using GPS. This value will be used to select a value from a Drop Down. User may then use the dropdown to change the value.
When I navigate away from this page and then come back using
const appHistory = createHashHistory();
appHistory.goBack();
the constructor and the componentDidMount are executed again. So the user selected value is lost and i get the default value again.
So where do I put by initialisation code? I come from Ionic where there something like ionViewDidLoad which is executed only when the page loads the first time. Is there an equivalent for this is React.
Below is my code
export class HomePage extends React.Component {
constructor(props){
super(props);
state = {
currentLocationCoordinates:[],
};
this.getCurrentLocation = this.getCurrentLocation.bind(this);
}
getCurrentLocation(){
navigator.geolocation.getCurrentPosition((success)=>{
console.log("Current Location: "+success.coords);
this.setState({
currentLocationCoordinates:[success.coords.latitude,success.coords.longitude],
userLocationCoordinates:[success.coords.latitude,success.coords.longitude]
});
});
}
}
componentDidMount(){
this.getCurrentLocation();
}
}
I would avoid using componentDidMount for this. Make your components as resilient to re-renders and re-mounts as possible.
By the sounds of it you are after an application level state container that will hold your application state regardless of whether your component is mounted or not.
Most of the React community relies on Redux for this although other state containers do exist. I'd suggest having a look at Redux and using it to hold those location details as part of Redux store. They will then be always accessible on your component as props, regardless of whether it re-renders or not.
You need to use ComponentDidMount() together with a flag that detects whether the component has been initialized or not. You are on the right track.
componentDidMount(){
initialized ? "" : this.getCurrentLocation();
}

Which React lifecycle method is a good replacement for componentWillRecieveProps?

I have heard of getDerivedStateFromProps but it does not work how I want it to.
I have this code:
class App extends Component {
static getDerivedStateFromProps(props, state) {
console.log("Get derived state from props called");
return null;
}
render() {
return (
<button onClick={() => this.setState({})}>Change State</button>
);
}
}
Clicking the button calls getDerivedStateFromProps. This is not what I need. I want a function that is called only when new props are received. When internal state changes, this function should not be called.
I intend to use this in a modal form scenario. When the component receives an object as a prop, the component should translate this object into a form, then place that form into the state. If I use getDerivedStateFromProps, since it is called in every this.setState(), it would not reflect the changes in the form since whenever the user types, a this.setState() is fired, and instead of the changes being set into the state, the initial object is.
Which React lifecycle method should I use?
There are a couple of ways to approach this problem depending on different use cases.
If new props are added, those new props can be checked in the Child component and user defined functions can be called in case the new object exists.
getDerivedStateFromProps(props, state, prevProps) is a static method and this does not exist in the same, which means that you'll have to return a state based on various conditions. That is, let it work on default on most cases. On cases where you want to alter its functionality, place checks and return the modified state.
Hope it answers :)

How do I change input field value the way it doesn't run through the whole Redux state update cycle every time it is changed?

I have a component that has an input field bound to the application state, like this
import {updateTitle} from '../actions/sales';
class Sale extends Component {
onTitleChange(event) {
const {value} = event.target;
const {id} = this.props;
this.props.updateTitle(id, value);
}
render() {
return (
<input
placeholder="The Title"
value={this.props.title}
onChange={::this.onTitleChange} />
);
}
}
export default connect(({sales}) => ({
title: sales.title
}), {
updateTitle
}))(Sale);
but a lot heavier in markup.
Everything is nice except that when I try to type something into this field fast, the browser lags significantly since every input change, which implies every key press, runs through the whole loop from event handler, to action creator, to the actual action, to the reducer, to the store update, to component props update and render. It's super slow. Is there any way to optimize that?
Am I missing something obvious?
I tried using debounced function passed as onChange prop value but, this way, the app state wouldn't update at all. I also tried using component-level state and setState along with app-level state but I think this approach contradicts the idea of Redux and therefore shouldn't be used.
React components can be controlled or uncontrolled. A controlled input receives its value as a prop and fires an event handler for each change in the value. An uncontrolled control keeps the user input in the local state and fires event handlers for changes.
Your input is currently controlled, but if you'd like to keep changes more local, why not make it uncontrolled? You can then call updateTitle in the onBlur event when the user is done typing, or debounce the onChange event to call updateTitle less often while the user is typing.
You have at least two options. The first: try connecting to the redux store closer to the input component (so less components that aren't affected by the value update on the change)
Generally though we store the intermediate values in the parent component state, and either flush the value occasionally (like a normal debounce) to the redux store, or do it on something like onBlur. So update the value in state on every change and flush the value to the store occasionally. It involves more care to make sure the values are in-sync but those are some of the trade-offs for optimizing hot paths.

Categories