React Native: lodash debounce not working? - javascript

in my react native app I have a textinput for users to type and search, I want to use lodash debounce so that the automatice search is more performant but for some reason my results array is never populated and I'm not console login anything either...
Funny thing is if instead of changing state inside debounce action I change results array without debouncing it actually works right but doing a search each key change seems like a lot, is it? I'm not doing fetches but a javascript search which would be better performance wise.
Here is my code:
const handleSearchChange = (value)=>
{
setSearch(value);
let results = filterSearch(allProducts);
_.debounce(e => {
console.log('Debounced search:', e);
setResults(results);
}, 1000);
//setResults(results);
}
<TextInput onChangeText={value => handleSearchChange(value)} value={search} style={{ width:'90%', height:'100%', borderRadius:5, padding:10, color:'rgb(68,68,68)',fontSize:16, backgroundColor:'rgba(255,255,255,1)', alignItems:'center' }} placeholder="Donde vamos hoy?"></TextInput>

_.debounce returns a function that you need to call (which will be debounced). But in React, every re-render will create a new function with this sort of approach, so you need to create the debounced function only once, on mount, rather than on every render, which can be done with a ref:
const debounceSearchRef = useRef(_.debounce(
() => {
setResults(filterSearch(allProducts));
},
1000
));
const handleSearchChange = (value) => {
setSearch(value);
debounceSearchRef.current();
};

Related

useEffect hook spamming requests [duplicate]

I've been playing around with the new hook system in React 16.7-alpha and get stuck in an infinite loop in useEffect when the state I'm handling is an object or array.
First, I use useState and initiate it with an empty object like this:
const [obj, setObj] = useState({});
Then, in useEffect, I use setObj to set it to an empty object again. As a second argument I'm passing [obj], hoping that it wont update if the content of the object hasn't changed. But it keeps updating. I guess because no matter the content, these are always different objects making React thinking it keep changing?
useEffect(() => {
setIngredients({});
}, [ingredients]);
The same is true with arrays, but as a primitive it wont get stuck in a loop, as expected.
Using these new hooks, how should I handle objects and array when checking weather the content has changed or not?
Passing an empty array as the second argument to useEffect makes it only run on mount and unmount, thus stopping any infinite loops.
useEffect(() => {
setIngredients({});
}, []);
This was clarified to me in the blog post on React hooks at https://www.robinwieruch.de/react-hooks/
Had the same problem. I don't know why they not mention this in docs. Just want to add a little to Tobias Haugen answer.
To run in every component/parent rerender you need to use:
useEffect(() => {
// don't know where it can be used :/
})
To run anything only one time after component mount(will be rendered once) you need to use:
useEffect(() => {
// do anything only one time if you pass empty array []
// keep in mind, that component will be rendered one time (with default values) before we get here
}, [] )
To run anything one time on component mount and on data/data2 change:
const [data, setData] = useState(false)
const [data2, setData2] = useState('default value for first render')
useEffect(() => {
// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
}, [data, data2] )
How i use it most of the time:
export default function Book({id}) {
const [book, bookSet] = useState(false)
const loadBookFromServer = useCallback(async () => {
let response = await fetch('api/book/' + id)
response = await response.json()
bookSet(response)
}, [id]) // every time id changed, new book will be loaded
useEffect(() => {
loadBookFromServer()
}, [loadBookFromServer]) // useEffect will run once and when id changes
if (!book) return false //first render, when useEffect did't triggered yet we will return false
return <div>{JSON.stringify(book)}</div>
}
I ran into the same problem too once and I fixed it by making sure I pass primitive values in the second argument [].
If you pass an object, React will store only the reference to the object and run the effect when the reference changes, which is usually every singe time (I don't now how though).
The solution is to pass the values in the object. You can try,
const obj = { keyA: 'a', keyB: 'b' }
useEffect(() => {
// do something
}, [Object.values(obj)]);
or
const obj = { keyA: 'a', keyB: 'b' }
useEffect(() => {
// do something
}, [obj.keyA, obj.keyB]);
If you are building a custom hook, you can sometimes cause an infinite loop with default as follows
function useMyBadHook(values = {}) {
useEffect(()=> {
/* This runs every render, if values is undefined */
},
[values]
)
}
The fix is to use the same object instead of creating a new one on every function call:
const defaultValues = {};
function useMyBadHook(values = defaultValues) {
useEffect(()=> {
/* This runs on first call and when values change */
},
[values]
)
}
If you are encountering this in your component code the loop may get fixed if you use defaultProps instead of ES6 default values
function MyComponent({values}) {
useEffect(()=> {
/* do stuff*/
},[values]
)
return null; /* stuff */
}
MyComponent.defaultProps = {
values = {}
}
Your infinite loop is due to circularity
useEffect(() => {
setIngredients({});
}, [ingredients]);
setIngredients({}); will change the value of ingredients(will return a new reference each time), which will run setIngredients({}). To solve this you can use either approach:
Pass a different second argument to useEffect
const timeToChangeIngrediants = .....
useEffect(() => {
setIngredients({});
}, [timeToChangeIngrediants ]);
setIngrediants will run when timeToChangeIngrediants has changed.
I'm not sure what use case justifies change ingrediants once it has been changed. But if it is the case, you pass Object.values(ingrediants) as a second argument to useEffect.
useEffect(() => {
setIngredients({});
}, Object.values(ingrediants));
As said in the documentation (https://reactjs.org/docs/hooks-effect.html), the useEffect hook is meant to be used when you want some code to be executed after every render. From the docs:
Does useEffect run after every render? Yes!
If you want to customize this, you can follow the instructions that appear later in the same page (https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects). Basically, the useEffect method accepts a second argument, that React will examine to determine if the effect has to be triggered again or not.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
You can pass any object as the second argument. If this object remains unchanged, your effect will only be triggered after the first mount. If the object changes, the effect will be triggered again.
I'm not sure if this will work for you but you could try adding .length like this:
useEffect(() => {
// fetch from server and set as obj
}, [obj.length]);
In my case (I was fetching an array!) it fetched data on mount, then again only on change and it didn't go into a loop.
If you include empty array at the end of useEffect:
useEffect(()=>{
setText(text);
},[])
It would run once.
If you include also parameter on array:
useEffect(()=>{
setText(text);
},[text])
It would run whenever text parameter change.
I often run into an infinite re-render when having a complex object as state and updating it from useRef:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients({
...ingredients,
newIngedient: { ... }
});
}, [ingredients]);
In this case eslint(react-hooks/exhaustive-deps) forces me (correctly) to add ingredients to the dependency array. However, this results in an infinite re-render. Unlike what some say in this thread, this is correct, and you can't get away with putting ingredients.someKey or ingredients.length into the dependency array.
The solution is that setters provide the old value that you can refer to. You should use this, rather than referring to ingredients directly:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients(oldIngedients => {
return {
...oldIngedients,
newIngedient: { ... }
}
});
}, []);
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect.
I believe they are trying to express the possibility that one could be using stale data, and to be aware of this. It doesn't matter the type of values we send in the array for the second argument as long as we know that if any of those values change it will execute the effect. If we are using ingredients as part of the computation within the effect, we should include it in the array.
const [ingredients, setIngredients] = useState({});
// This will be an infinite loop, because by shallow comparison ingredients !== {}
useEffect(() => {
setIngredients({});
}, [ingredients]);
// If we need to update ingredients then we need to manually confirm
// that it is actually different by deep comparison.
useEffect(() => {
if (is(<similar_object>, ingredients) {
return;
}
setIngredients(<similar_object>);
}, [ingredients]);
The main problem is that useEffect compares the incoming value with the current value shallowly. This means that these two values compared using '===' comparison which only checks for object references and although array and object values are the same it treats them to be two different objects. I recommend you to check out my article about useEffect as a lifecycle methods.
The best way is to compare previous value with current value by using usePrevious() and _.isEqual() from Lodash.
Import isEqual and useRef. Compare your previous value with current value inside the useEffect(). If they are same do nothing else update. usePrevious(value) is a custom hook which create a ref with useRef().
Below is snippet of my code. I was facing problem of infinite loop with updating data using firebase hook
import React, { useState, useEffect, useRef } from 'react'
import 'firebase/database'
import { Redirect } from 'react-router-dom'
import { isEqual } from 'lodash'
import {
useUserStatistics
} from '../../hooks/firebase-hooks'
export function TMDPage({ match, history, location }) {
const usePrevious = value => {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
const userId = match.params ? match.params.id : ''
const teamId = location.state ? location.state.teamId : ''
const [userStatistics] = useUserStatistics(userId, teamId)
const previousUserStatistics = usePrevious(userStatistics)
useEffect(() => {
if (
!isEqual(userStatistics, previousUserStatistics)
) {
doSomething()
}
})
In case you DO need to compare the object and when it is updated here is a deepCompare hook for comparison. The accepted answer surely does not address that. Having an [] array is suitable if you need the effect to run only once when mounted.
Also, other voted answers only address a check for primitive types by doing obj.value or something similar to first get to the level where it is not nested. This may not be the best case for deeply nested objects.
So here is one that will work in all cases.
import { DependencyList } from "react";
const useDeepCompare = (
value: DependencyList | undefined
): DependencyList | undefined => {
const ref = useRef<DependencyList | undefined>();
if (!isEqual(ref.current, value)) {
ref.current = value;
}
return ref.current;
};
You can use the same in useEffect hook
React.useEffect(() => {
setState(state);
}, useDeepCompare([state]));
You could also destructure the object in the dependency array, meaning the state would only update when certain parts of the object updated.
For the sake of this example, let's say the ingredients contained carrots, we could pass that to the dependency, and only if carrots changed, would the state update.
You could then take this further and only update the number of carrots at certain points, thus controlling when the state would update and avoiding an infinite loop.
useEffect(() => {
setIngredients({});
}, [ingredients.carrots]);
An example of when something like this could be used is when a user logs into a website. When they log in, we could destructure the user object to extract their cookie and permission role, and update the state of the app accordingly.
my Case was special on encountering an infinite loop, the senario was like this:
I had an Object, lets say objX that comes from props and i was destructuring it in props like:
const { something: { somePropery } } = ObjX
and i used the somePropery as a dependency to my useEffect like:
useEffect(() => {
// ...
}, [somePropery])
and it caused me an infinite loop, i tried to handle this by passing the whole something as a dependency and it worked properly.
Another worked solution that I used for arrays state is:
useEffect(() => {
setIngredients(ingredients.length ? ingredients : null);
}, [ingredients]);

React object as useEffect dependency triggers callback on every render

I have a relatively simple React hook to debounce a value. The only twist is that I want to provide a callback which gets triggered whenever the debounced value changes. I'm aware that I could just have another useEffect inside App which listens for changes on debouncedValue, but I'd like to integrate this behavior into the reusable hook.
const useDebounce = (value, delay, { onChange }) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedValue(value);
onChange(value);
}, delay);
return () => {
clearTimeout(timeout);
};
}, [value, delay, onChange]);
return debouncedValue;
};
const App = () => {
const [value, setValue] = useState("");
const debouncedValue = useDebounce(value, 750, { onChange: (value) => console.log(value) });
return (
<div style={{ margin: 50 }}>
<input value={value} onChange={(event) => setValue(event.target.value)} />
<p>{value}</p>
<p>{debouncedValue}</p>
</div>
);
};
Now to the issue I have is that the onChange fires twice for every debounce. Apparently the onChange callback must be part of the dependency array of useEffect, even if I don't care when it updates. But this creates the problem than on every render a new object for { onChange: (value) => console.log(value) } gets created therefore reference equality thinks the useEffect dependency has changed. I could just create the object outside of my component, but that would also complicate the API of my hook.
So, my question: what is the easiest way to solve this problem? Ideally without pulling in any 3rd party packages. The fix I can think of would be to write a custom useEffect hook that does deep comparisons, but I hope someone can point me to a simpler solution.

cannot update a component from inside the function body of a different component - React Native?

I have a child component "Text Input" and passes the value to as a prop like this
export default function MobileInput(props) {
const [mobileNumber, setMobileNumber] = React.useState('');
return (
<View style={styles.inputBox}>
<TextInput
value={mobileNumber}
onChangeText={(number) => setMobileNumber(number)}
onEndEditing={props.saveMobileNumber(mobileNumber)} // here
/>
</View>
);
}
In Parent, I got the value from child
const [mobile, setMobile] = useState('');
const getMobile = (number) => {
number ? setMobile(number) : null; // here's I got this warnning
console.log('getMobile-number-from-child', number);
};
const reSendMobile = () => { // other function I want to call passed on mobile number I got from child component
if (mobile?.length) {
alert('Done');
setReSend(false);
setSeconds(10);
} else {
alert('Please write your number before press send!');
}
};
<MobileInput saveMobileNumber={getMobile} />
I see this issue But I'm already using React 16.13.1
TextInputs property onEndEditing accepts a function that is called when text input ends.. Instead of a function, you are passing the result of your props.saveMobileNumber function that is called when the component renders. Try passing a function that calls saveMobileNumber instead:
onEndEditing={() => props.saveMobileNumber(mobileNumber)}
Your code will be much easier to read/debug if you avoid keeping the same state in multiple components. You can pass mobile and setMobile to the child through props and avoid having to create separate state for the same data.
Try this:
<View style={styles.inputBox}>
<TextInput
value={mobileNumber}
onChangeText={(number) => setMobileNumber(number)}
onEndEditing={() => props.saveMobileNumber(mobileNumber)} // change here
/>
</View>
The event onEndEditing accepts a function call
Just update to call a arrow function :
onEndEditing={() => props.saveMobileNumber(mobileNumber)}
For me, i was updating activity title outside the useEffect hook. When i moved the code
into useEffect hook, the error just gone.

React useState with addEventListener

Can someone explain to me exactly why the callback from addEventListener after changing value (by click button) in useState from "init" to "new state" shows the value "init" while text in button is "new state"
function Test() {
const [test, setTest] = useState("init")
useEffect(() => {
window.addEventListener("resize", () => console.log(test))
}, [])
return (
<button style={{ marginTop: "100px" }} onClick={() => setTest("new state")}>
{test}
</button>
)
}
Add test to the dependency array in your useEffect:
useEffect(() => {
const func = () => console.log(test);
window.addEventListener("resize", func)
return () => {
window.removeEventListener("resize", func)
}
}, [test])
An explanation of why this is needed is covered in the Hooks FAQ: Why am I seeing stale props or state inside my function?:
another possible reason you’re seeing stale props or state is if you
use the “dependency array” optimization but didn’t correctly specify
all the dependencies. For example, if an effect specifies [] as the
second argument but reads someProp inside, it will keep “seeing” the
initial value of someProp. The solution is to either remove the
dependency array, or to fix it.
Empty dependency array acts as componentDidMount. You should add test as one of the dependencies so it fires again
function Test() {
const [test, setTest] = useState("init")
useEffect(() => {
window.addEventListener("resize", fun)
return () => {
window.removeEventListener("resize", fun)
}
}, [test])
return (
<button style={{ marginTop: "100px" }} onClick={() => setTest("new state")}>
{test}
</button>
)
}
Another reason this is happening is the functional component is using stale state value.
Event is registered once on component mount with useEffect. It's the same function during entire component lifespan and refers to stale state that was fresh at the time when this eventListener was defined the first time.
The solution for that is using refs
Check out https://codesandbox.io/s/serverless-thunder-5vqh9 to understand the solution better
State inside an effect fired once is never updated (if you use [])
The value of "test" inside the scope of your useEffect is never updated since the effect was fired only once at mount.
The State changes you make afterwards will never change this value unless you fire the Effect again but passing an argument into the square brackets.

React when to use a Function in Props?

In React,
when to use a function in Props?
For example in the code below sometimes you don't use a function in prop,
but sometimes you do.
import React, { useState } from 'react';
const IterationSample = () => {
const input = React.createRef();
const [names, setNames] = useState(
[
{ id: 1, text: 'John' },
{ id: 2, text: 'Jake' },
{ id: 3, text: 'JJ' },
{ id: 4, text: 'Jesus' }
])
const [inputText, setInputText] = useState('');
const [nextId, setNextId] = useState(5);
const onChange = e => setInputText(e.target.value);
const onRemove = id => {
const nextNames = names.filter(name => name.id !== id);
setNames(nextNames);
}
const namesList = names.map(name => <li key={name.id} onDoubleClick={() => onRemove(name.id)}>{name.text}</li>);
const onClick = () => {
const nextNames = names.concat({
id: nextId,
text: inputText
});
setNextId(nextId + 1);
setNames(nextNames);
setInputText('');
input.current.focus();
}
const onEnter = (e) => {
if (e.key === 'Enter') {
onClick();
}
}
return (
<>
<input ref={input} value={inputText} onChange={onChange} onKeyPress={onEnter} />
<button onClick={onClick} >Add</button>
<ul>{namesList}</ul>
</>
);
};
export default IterationSample;
Here you use ()=> a function in onDoubleClick={() => onRemove(name.id)
but, not either in onKeyPress={onEnter} or onChange={onChange}
So when do you use a function in props?
All three of your examples use a function:
onDoubleClick={() => onRemove(name.id)}
onKeyPress={onEnter}
onChange={onChange}
The only difference is that one of them needs to specify an argument to the function (onRemove), whereas the other two do not.
If the arguments being passed by the property (in this case onKeyPress and onChange) are exactly what the function needs, then you just reference the function as-is. But if you need to customize what you're passing to the function (in this case onRemove), you can wrap it in a new function to enclose that customization.
You basically use it when you need to pass extra paremeters (except event itselft).
ex. when you want to pass id in your case you do:
onClick={(e) => onRemove(name.id)}
//or together with the event
onClick={(e) => onRemove(e, name.id)}
Otherwise when you dont need to pass a parameter inside a function, or you only need to pass event you can write it like:
onClick={onRemove}
This
onClick={onClick}
is equivalent to
onClick={(e) => onClick(e)}
For the first case, you are passing the function itself as the props.
For the second case, you are defining an inline function that calls onClick function with the parameter (e).
Generally you can just pass the function itself as the props unless you want to add additional parameters when calling your function. For example,
onClick={(e) => onClick(e, name.id)}
:)
David and Ertan answer are really good and complete.
I will just add two more things.
First, considering reactjs good practice and perfomance do not use arrow function in render.
According to eslint rules eslint rules :
A bind call or arrow function in a JSX prop will create a brand new function on every single render. This is bad for performance, as it may cause unnecessary re-renders if a brand new function is passed as a prop to a component that uses reference equality check on the prop to determine if it should update.
Secondly in the case you need to customize what you're passing to the function (in this case on Remove) you can use an alternative from lodash : partial
the official doc
Here an exemple with your code :
const namesList = names.map(name =>
<li key={name.id}
onDoubleClick={ _.partial(this.onRemove, name.id)}
>
{name.text}
</li>
);
have a good day
Just to clarify, since it seems like you may not be getting quite the beginner-friendly answer that you're looking for:
Something like "onKeyPress" or "onChange" comes with an implicit event. What does that mean? It means that whatever function you tie into that component, it will be triggered with an "event," and that will be the argument of any function that you feed in. In fact, if you look at the functions themselves, you'll notice that they accept an argument, 'e' for event. So, if it helps, you can think of some of those commands as saying
onKeyPress={()=>onEnter(keyPressEvent)}
The events will have some useful data in them, but typically the only thing that we'll care about is something like which key the user pressed, or (in the event of a change) what the new content of an input is.
For something like onDoubleClick, you're giving it some extra data. You can do some fancy footwork with javascript to identify what was double clicked and what its attributes were, but that's a pain in the butt, adds unnecessary code, and tends to obfuscate your intent in whatever it is you were coding. So it's ultimately easier to just feed the function whatever you want it to have.

Categories

Resources