debounced function not being called in a react component - javascript

I have the react component below.
The raiseCriteriaChange method is called but the this.props.onCriteriaChange(this.state.criteria) line is never reached.
Any ideas why the code never reaches this.props.onCriteriaChange?
import * as React from 'react';
import { debounce } from 'lodash';
interface CustomerSearchCriteriaProps {
onCriteriaChange: (criteria: string) => void;
}
interface CustomerSearchCriteriaState {
criteria: string;
}
class CustomerSearchCriteria extends React.Component<CustomerSearchCriteriaProps, CustomerSearchCriteriaState> {
constructor() {
super();
this.state = {
criteria: ''
};
}
// problem method:
raiseCriteriaChange = () => {
// the call reaches here fine ...
debounce(
() => {
// ... but the call never reaches here ... why is this?
this.props.onCriteriaChange(this.state.criteria);
},
300);
}
handleCriteriaChange = (e: React.FormEvent<HTMLInputElement>) => {
this.setState({ criteria: e.currentTarget.value }, () => {
this.raiseCriteriaChange();
});
}
render() {
return (
<div className="input-group">
<input
type="text"
value={this.state.criteria}
onChange={this.handleCriteriaChange}
className="form-control"
/>
</div>
);
}
}
export default CustomerSearchCriteria;

_.debounce simply returns a function that must be called. Try changing your code like:
raiseCriteriaChange = debounce(() => {
this.props.onCriteriaChange(this.state.criteria);
}, 300);

See _.debounce, which says it
(Function): Returns the new debounced function.
so you need to call it like
var debounced = debounce(
() => {
// ... but the call never reaches here ... why is this?
this.props.onCriteriaChange(this.state.criteria);
},
300
);
debounced();

Related

How can I detect browser back button in react class component

I am trying to create a function when user hit browser back button it will run function deleteHeldResort that I created.
Here is my code for deleteHeldResort:
deleteHeldResorts(ReservedInventoryID: number | null = null, refreshHeldResorts: boolean = true) {
return new Promise((resolve, reject) => {
this.setState({heldResortsShowLoader: true});
this.reservationService.deleteHeldResorts(ReservedInventoryID)
.then(() => {
refreshHeldResorts && this.getHeldResorts();
resolve();
})
.catch((error: any) => {
this.catchHeldResortsError(error);
reject(error);
});
this.setState({
heldResortsShowLoader: false
});
});
}
Updated code base:
handleNavigateBack = useCallback(
(event) => {
// call your function here with whatever argument your code provides
this.reservationService.deleteHeldResorts(this.props.resId);
}
, []);
useEffect(() => {
document.addEventListener('popstate', this.handleNavigateBack );
return () => {
document.removeEventListener('popstate', this.handleNavigateBack)
}
}, [this.handleNavigateBack]);
Updated answer: class component based
Since you're using class components, I'm adding this update.
Inside the class component having deleteHeldResorts function, you can listen to popstate event:
Remark how bind keyword helps us keep the right this.
import React from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.handleNavigateBack = this.handleNavigateBack.bind(this);
}
componentDidMount() {
document.addEventListener("popstate", this.handleNavigateBack);
}
componentWillUnmount() {
document.removeEventListener("popstate", this.handleNavigateBack);
}
handleNavigateBack(event) {
console.log("inside callback", event);
// change arguments as you want
this.deleteHeldResorts(null, false);
}
deleteHeldResorts(ReservedInventoryID = null, refreshHeldResorts = true) {
// your function goes down there content
console.log("inside deleteHeldResorts");
}
render() {
return <h2>popstate browser listener</h2>;
}
}
render(<App />, document.getElementById("root"));
Initial answer: function component based
Inside the component having deleteHeldResorts function, you can listen to popstate event:
// your-component.js
function YourComponent() {
function deleteHeldResorts(ReservedInventoryID: number | null = null,
refreshHeldResorts: boolean = true) {
// your function content
}
const handleNavigateBack = useCallback(
(event) => {
// call your function here with whatever argument your code provides
deleteHeldResorts(reservedInventoryID,refreshHeldResorts);
}
// depending on your logic, add deps to this array dependency
}, [])
useEffect(() => {
document.addEventListener('popstate', handleNavigateBack );
return () => {
document.removeEventListener('popstate', handleNavigateBack)
}
}, [handleNavigateBack])
return ( <>something dope</>);
}

ReactJS - Pass multiple functions to child components as a single prop

I am passing multiple function={this.function} as singular props from the parent component to its child components. I don't get any errors or issues with this, but I'm wondering if there's a better / cleaner way to code it.
To illustrate, here is a sample code (the parent component):
import React, { Component } from 'react';
export default class App extends Component {
function1 = () => {
// Does something
};
function2 = () => {
// Does something
};
function3 = () => {
// Does something
};
function4 = () => {
// Does something
};
function5 = () => {
// Does something
};
render() {
return (
<div>
Parent Component
<ChildComponent
function1={this.function1}
function2={this.function2}
function3={this.function3}
function4={this.function4}
function5={this.function5} />
</div>
);
}
}
It's really just a matter of code becoming a bit long. I'm wondering if there's a short way to pass functions 1 through 5, perhaps in a single line.
Thanks in advance!
Sure, there are at least two options:
import React, { Component } from 'react';
export default class App extends Component {
functions = {
function1: () => {
// Does something
},
function2: () => {
// Does something
},
function3: () => {
// Does something
},
function4: () => {
// Does something
},
function5: () => {
// Does something
}
}
render() {
return (
<div>
Parent Component
<ChildComponent functions={this.functions} />
OR
<ChildComponent {...this.functions} />
</div>
);
}
}

How to use data from a callback function in the render method in a react class

I'm new to javascript and react and for my instance I need to use the data from a xmlHttpRequest inside the render function of a class that extends react.component
I have used a window.localstorage to save the result and use it in other places. But that doesn't look like the way to do it properly.
What I need:
class MyClass extends Component {
MyFunction() { //the function that includes a callback function
setInterval(function() { //the callback function
//get some data
}, 1000)
}
render() {
return (
<div>
</div>
)
}
}
Now how can I use this data in render?
Take whatever response you get in your callback, which you earlier used to save to localstorage, and set it to state.
const response = res
this.setState({response})
Then refer the same in your render
render(){
return(
<div>{this.state.response}</div>
)
}
Use state in your class
class MyClass extends Component {
constructor() {
super()
this.state = {
data:[]
}
}
MyFunction() {
fetch("url").then(res => res.json()).then(data => this.setState({data}))
.catch(err => console.error(err))
}
render() {
return (
<div>
{this.state.data}
</div>
)
}
}
This is a great example of where to use state.
myFunction() {
setInterval(function() {
let newCount = this.state.count + 1
this.setState({count: newCount})
}, 1000)
}
render() {
return (
<div>
Count is {this.state.count}
</div>
)
}
You'll need to call myFunction either as part of your constructor or in componentDidMount and you should set an initial state
constructor(props) {
super(props)
this.state = {
count: 0
}
this.myFunction = this.myFunction.bind(this)
}
componentDidMount() {
this.myFunction()
}
NB: Depending on your compiler you might need some bindings in your function:
setInterval(function() {
...
}.bind(this), 1000)
...or your could use fat arrow notation, which binds implicitly:
setInterval(() => {
...
}, 1000)

ReactJS/Jest: How to test/mock a function, which is passed to component

I'm trying to do some unit testing using jest/enzyme for my react components.
But I'm facing problems with an function I'm passing to a second component.
I don't understand if I have to test or mock this function. If I have to mock it, I don't know how to do that for a function.
Parent Component
export default class Parent extends Component {
togglePosition (term, event) {
this.setState({
top: term.length >= 3
})
}
render () {
return (
<div>
<Child togglePosition={this.togglePosition} />
</div>
)
}
}
Child component
export default class Child extends Component {
handleChange (event) {
const term = event.target.value
this.props.togglePosition(term) // <-- Test/mock it?
this.setState({
loading: 'loading',
term
})
}
render () {
return (
<div>
<Input id="target-input" onChange={this.handleChange} />
</div>
)
}
}
This is how I do a test for the Child component - testing handleChange:
Unit test (Child)
it('handleChange() should set state.term', () => {
const event = { target: { value: 'test' } }
const wrapper = shallow(<Child />)
wrapper.find('#target-input').simulate('change', event)
const state = wrapper.instance().state
expect(state).toEqual({ loading: 'loading', term: 'test' })
})
Do get this error: TypeError: this.props.togglePosition is not a function
Without actually testing it, I believe this is what you need:
it('handleChange() should set state.term', () => {
const togglePosition = jest.fn();
const event = { target: { value: 'test' } };
const wrapper = shallow(<Child togglePosition={togglePosition} />);
wrapper.find('#target-input').simulate('change', event);
const state = wrapper.instance().state;
expect(state).toEqual({ loading: 'loading', term: 'test' });
expect(togglePosition).toHaveBeenCalledWith('test');
})
Mock the passed function: const togglePosition = jest.fn();, and test the condition/response: expect(togglePosition).toHaveBeenCalledWith('test');.

React Native: Using lodash debounce

I'm playing around with React Native and lodash's debounce.
Using the following code only make it work like a delay and not a debounce.
<Input
onChangeText={(text) => {
_.debounce(()=> console.log("debouncing"), 2000)()
}
/>
I want the console to log debounce only once if I enter an input like "foo". Right now it logs "debounce" 3 times.
Debounce function should be defined somewhere outside of render method since it has to refer to the same instance of the function every time you call it as oppose to creating a new instance like it's happening now when you put it in the onChangeText handler function.
The most common place to define a debounce function is right on the component's object. Here's an example:
class MyComponent extends React.Component {
constructor() {
this.onChangeTextDelayed = _.debounce(this.onChangeText, 2000);
}
onChangeText(text) {
console.log("debouncing");
}
render() {
return <Input onChangeText={this.onChangeTextDelayed} />
}
}
2019: Use the 'useCallback' react hook
After trying many different approaches, I found using 'useCallback' to be the simplest and most efficient at solving the multiple calls problem.
As per the Hooks API documentation, "useCallback returns a memorized version of the callback that only changes if one of the dependencies has changed."
Passing an empty array as a dependency makes sure the callback is called only once. Here's a simple implementation.
import React, { useCallback } from "react";
import { debounce } from "lodash";
const handler = useCallback(debounce(someFunction, 2000), []);
const onChange = (event) => {
// perform any event related action here
handler();
};
Hope this helps!
Updated 2021
As other answers already stated, the debounce function reference must be created once and by calling the same reference to denounce the relevant function (i.e. changeTextDebounced in my example).
First things first import
import {debounce} from 'lodash';
For Class Component
class SomeClassComponent extends React.Component {
componentDidMount = () => {
this.changeTextDebouncer = debounce(this.changeTextDebounced, 500);
}
changeTextDebounced = (text) => {
console.log("debounced");
}
render = () => {
return <Input onChangeText={this.changeTextDebouncer} />;
}
}
For Functional Component
const SomeFnComponent = () => {
const changeTextDebouncer = useCallback(debounce(changeTextDebounced, 500), []);
const changeTextDebounced = (text) => {
console.log("debounced");
}
return <Input onChangeText={changeTextDebouncer} />;
}
so i came across the same problem for textInput where my regex was being called too many times did below to avoid
const emailReg = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w\w+)+$/;
const debounceReg = useCallback(debounce((text: string) => {
if (emailReg.test(text)) {
setIsValidEmail(true);
} else {
setIsValidEmail(false);
}
}, 800), []);
const onChangeHandler = (text: string) => {
setEmailAddress(text);
debounceReg(text)
};
and my debounce code in utils is
function debounce<Params extends any[]>(
f: (...args: Params) => any,
delay: number,
): (...args: Params) => void {
let timer: NodeJS.Timeout;
return (...args: Params) => {
clearTimeout(timer);
timer = setTimeout(() => {
f(...args);
}, delay);
};
}

Categories