useEffect makes onChange and typing lag/slow - javascript

I am creating a website where the user can type what he is looking for in a search bar.
Initially, I had a problem with the fact that onChange was one character behind the user search. For example, if the user search for "banana", the search was "banan".
I understood that the problem comes form the fact that setState is asynchronous.
onChange is one character on delay - Hooks
To avoid this problem, I introduced the useEffect component in my code. It works.
However now, if the user types some words, the words he types are not displayed immediately inside the search bar. They are displayed after few moments, as if it were a delay between what the user types and what it is displayed.
My searchbar component
export default function SearchBar({handlerSearchBar}) {
const classes = useStyles();
const [searchBarQuery, setSearchBarQuery] = React.useState([""])
function handleChange(event) {
setSearchBarQuery(event.target.value);
console.log(searchBarQuery)
}
useEffect(() => {
console.log("Search message inside useEffect: ", searchBarQuery);
handlerSearchBar(searchBarQuery)
});
return (
<form className={classes.container} noValidate autoComplete="off">
<TextField
required
id="standard-full-width"
label="Searchbar"
style={{ marginLeft: 40, marginRight: 40 }}
placeholder="Write your query"
// helperText="The results will appear below!"
fullWidth
margin="normal"
InputLabelProps={{
shrink: true,
}}
onChange={handleChange}
/>
</form>
);
}
handlerSearchBar function
It a function that is passing the results from my search-bar component to its parents and then, to its grandparents (SearchForm).
The grandparents SearchForm is setting one of its state to what is passed via the searchbar handlerSearchBar function:
function SearchForm() {
const [searchBarQueryParent, setSearchBarQueryParent] = React.useState([""])
function handlerSearchBar(searchBarQuery) {
setSearchBarQueryParent(searchBarQuery)
console.log(searchBarQuery)
}
return (something)
}
My question is: why is the display of the search words so much delayed than the their actual typing?
I think what is happening is that useEffect is called for each key stroke, and that is what it is so slow.
I tried to call useEffect on onSubmit but there is no improvement.
Or it is my handlerSearchBar function that makes everything slow

I resolved it by changing from onChange to onBlur.
I am not totally sure why the change works, but it does work.

You might wanna try to make it so that the useEffect fires only when searchBarQuery has been updated. i.e. as a callback upon setSearchBarQuery. You might do;
//...
useEffect(() => {
console.log("Search message inside useEffect: ", searchBarQuery);
handlerSearchBar(searchBarQuery)
}, [searchBarQuery]);
// ...
Note the second argument, an array, in useEffect to make that only run when that item has changed.

I might be a bit late to the game here, but what I did was to use a debounce function. This will check the input values every 1 sec, and will remove the lag as well
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { TextField } from '#esgian/esgianui';
function SomeComp(props) {
const [myValue, setMyValue] = useState(null);
const [fetchDataSwitch, setFetchDataSwitch] = useState(false);
// Limit the number of requests sent
const debouncedFetchData = debounce((cb) => {
cb(!fetchDataSwitch);
}, 1000);
useEffect(() => {
debouncedFetchData((res) => {
setFetchDataSwitch(res);
});
}, [myValue]);
useEffect(() => {
// check if input Is valid
// fire API request
return () => {
// Clean up
};
}, [fetchDataSwitch]);
return (
<TextField
InputProps={{
inputProps: { min: 0 }
}}
fullWidth
placeholder={'Max transit time...'}
type={'number'}
value={myValue || ''}
onChange={({ target }) => setMyValue(target.value)}
/>
);
}
SomeComp.propTypes = {};
SomeComp.defaultProps = {};
export default SomeComp;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Related

Why is useFetcher causing an re-render infinite loop?

I have an input. On every change to the input, I want to call an API.
Here's a simplified version of the code:
// updated by input
const [urlText, setUrlText] = useState("");
const fetcher = useFetcher();
useEffect(() => {
if (urlText === "" || !fetcher) return;
fetcher.load(`/api/preview?url=${urlText}`);
}, [urlText]);
The issue is, when I put urlText inside of the dependencies array, there is an infinite rendering loop, and React claims the issue is I might be updating state inside of the useEffect. However, as far as I can tell, I'm not updating any state inside of the hook, so I'm not sure why an infinite re-render is happening.
Any thoughts?
The fuller version of the code is:
Note: The bug still happens without the debounce, or the useMemo, all of that stuff is roughly irrelevant.
export default function () {
const { code, higlightedCode } = useLoaderData<API>();
const [urlText, setUrlText] = useState("");
const url = useMemo(() => getURL(prefixWithHttps(urlText)), [urlText]);
const debouncedUrl = useDebounce(url, 250);
const fetcher = useFetcher();
useEffect(() => {
if (url === null || !fetcher) return;
fetcher.load(`/api/preview?url=${encodeURIComponent(url.toString())}`);
}, [debouncedUrl]);
return (
<input
type="text"
placeholder="Paste URL"
className={clsx(
"w-full rounded-sm bg-gray-800 text-white text-center placeholder:text-white"
//"placeholder:text-left text-left"
)}
value={urlText}
onChange={(e) => setUrlText(e.target.value)}
></input>
);
}
The problem you're having is that fetcher is updated throughout the fetch process. This is causing your effect to re-run, and since you are calling load again, it is repeating the cycle.
You should be checking fetcher.state to see when to fetch.
useEffect(() => {
// check to see if you haven't fetched yet
// and we haven't received the data
if (fetcher.state === 'idle' && !fetcher.data) {
fetcher.load(url)
}
}, [url, fetcher.state, fetcher.data])
https://remix.run/docs/en/v1/api/remix#usefetcher
You might by setting state in useFetcher hook, please check code of load method from useFetcher.
Update: I'm silly. useDebounce returns an array.

How to delegate responsibilities to each component correctly in React?

I have a hook, which basically renders a Loading Indicator at the right side of my Stack Header.
Here is how it looks like:
export default function useHeaderRightLoadingIndicator(loading = false) {
const navigation = useNavigation();
const renderLoading = useCallback(() => {
if (!loading) return null;
return (
<Loading
size={20}
style={styles.loadingIndicator}
/>
);
}, [loading]);
useEffect(() => {
navigation.setOptions({
headerRight: renderLoading,
});
}, [navigation, renderLoading]);
}
Now, I want to use it during a delete operation. Basically, I have a screen with a lot of erasable items rendered in a FlatList.
The screen contains the stateful array of data... and pass it to the FlatList component.
In order to avoid repeating my self each time I wanna delete the same item (but in a different screen), I have decided to move the responsibility of the deletion to the item itself (I mean, to the FlatList's item). For this, I have been forced to use a context in order to update the graphical interface without having to pass the "setState" node by node.
So... I have the following:
function MyScreen() {
const statefulData = useData(); // consuming context...
return <CustomFlatList data={statefulData} />
}
...
function ErasableItem({ id }) {
const { isLoading, error, deleteItem } = useDeleteItem(id);
return <Text onPress={deleteItem}>Delete!</Text>;
}
Inside the logic of useDeleteItem, I just make an api call and update my context in order to update the UI (delete the item from the list).
This has sense to me... I don't have a super screen which does everything, instead, each node does its own stuff.
But... what if I wanna use the useHeaderRightLoadingIndicator() I described at the beginning? As I have delegated the deletion responsibility to an unmountable component, I will not be able to stop loading it. I mean, if I do:
function ErasableItem({ id }) {
const { isLoading, error, deleteItem } = useDeleteItem(id);
useHeaderRightLoadingIndicator(isLoading);
return <Text onPress={deleteItem}>Delete!</Text>;
}
As the item will be unmounted from the screen, the useEffect of the useHeaderRightLoadingIndicator will not be called the last time, when isLoading toggles from true to false.
In order to fix that, I will have to run a cleanup function, like this:
export default function useHeaderRightLoadingIndicator(loading = false) {
const navigation = useNavigation();
const renderLoading = useCallback(() => {
if (!loading) return null;
return (
<Loading
size={20}
style={styles.loadingIndicator}
/>
);
}, [loading]);
useEffect(() => {
navigation.setOptions({
headerRight: renderLoading,
});
return () => {
// cleanup
navigation.setOptions({ headerRight: null });
};
}, [navigation, renderLoading]);
}
Something that makes me doubt, since if I had delegated the delete operation to the screen, which is not unmountable, I would not have any problem and I would not have to execute that cleanup function.
What is the logic to follow when delegating responsibilities? Is my delegation incorrect in React?

How can I acess value from textfield in another module in reactJS

I want to have access to the value of a textField in another module in reactjs. The module that wants to have access does not import the whole textfield but only needs access to the value. What is the best way to access the value variable in another module in reactjs?
Here is my functional textField component:
export default function textField(props) {
const [value, setValue] = React.useState("");
const handleChange = (event) => {
setValue(value);
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
You can send onTextFieldChange function as props whenever textField's value changes you can pass a value to onTextFieldChange function and you can use it in the parent component.
There is an alternate way, Redux
You should try to use redux for the shared state between components which are either not related directly(i.e. sibling components or have a lengthy hierarchy). For small applications, redux is overkilled so should be avoided.
The most likely option that comes to mind here is the concept of lifting state, in which the nearest ancestor component has some means by which it also keeps track of the state, and the passes it into the sibling that needs to track it. You could make this an optional feature of your module by allowing a onChangeCallback prop that is called on each change; this prop could then be passed a setSharedState hook that would set the state on the ancestor:
const ParentComponent = () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
And you update your module to something like:
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
This is just a rough example. Other options for passing around state freely would be using Redux or the Context API, but the former might be overkill for this one case and the latter probably not a great fit for a specific, single-use datapoint.
there are may option but the proper option in pass as props
const modle1= () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
or you can use create context and use context and provide the parent to provide and the child as a consumer but the best option pass as a prop
I would recommend you use the parent-child nature of react in order to handle this. Since I'm not sure the relationship between the other module and this module, I'll provide a rough skeleton.
In a parent component:
export default function ParentComponent(){
const [status, updateStatus] = useState("")
return(
<TextView updateParent={updateStatus}>
</TextView>
)
}
Then in your child component:
const handleChange = (event) => {
setValue(value);
props.updateParent(value);
};
If they are siblings, I would use a parent component and then pass the state down to the sibling. Otherwise, as appropriate, use this parent child relationship to pass and update state.
HTH

Don't understand how to update React hook

I've tried to ask this ungooglable to me question dozens of times. I've made almost the simpliest example possible to ask this question now.
I change the value of the hook in the handleChange method. But then console.log always shows previous value, not new one. Why is that?
I need to change the value of the hook and then instead of doing console.log use it to do something else. But I can't because the hook always has not what I just tried to put into it.
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
console.log(value);
};
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
</div>
);
}
You can try it here.
https://codesandbox.io/s/awesome-lumiere-y2dww?file=/src/App.js
I believe the problem is that you are logging the value in the handleChange function. Console logging the value outside of the function logs the correct value. Link: https://codesandbox.io/s/async-fast-6y71b
Hooks do not instantly update the value you want to update, as you might have expected with classes (though that wasn't guaranteed either)
State hook, when calling setValue will trigger a re-render. In that new render, the state will have the new value as you expected. That's why your console.log sees the old value.
Think of it as in each render, the state values are just local variables of that component function call. And think as the result of your render as a result of your state + props in that render call. Whenever any of those two changes (the props from your parent component; the state, from your setXXX function), a new render is triggered.
If you move out the console.log outside of the callback handler (that is, in the body of your rendered), there you will see in the render that happens after your interaction that the state is logged correctly.
In that sense, in your callbacks events from interactions, you just should worry about updating your state properly, and the next render will take care to, given the new props/state, re-render the result
The value doesn't "change" synchronously - it's even declared with a const, so even the concept of it changing inside the same scope doesn't make sense.
When changing state with hooks, the new value is seen when the component is rerendered. So, to log and do stuff with the "new value", examine it in the main body of the function:
const ControllableStates = () => {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
};
// ADD LOG HERE:
console.log('New or updated value:', value);
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
</div>
);
}
You're printing out the old value in handleChange, not the new val.
i.e.
const handleChange = val => {
setValue(val);
console.log(value);
};
Should be:
const handleChange = val => {
setValue(val);
console.log(val);
};
Actually, lets get a little back and see the logic behind this scenario.
You should use the "handleChange" function ONLY to update the state hook, and let something else do the logic depends on that state hook value, which is mostly accomplished using "useEffect" hook.
You could refactor your code to look like this:
const handleChange = val => {
setValue(val);
};
React.useEffect(() => {
console.log(value);
// do your logic here
}, [value])
So I think that the main problem is that you're not understanding how React
deals with components and states.
So, I'll vastly simplify what React does.
React renders a new component and remembers it's state, it's inputs (aka
props) and it's the state and inputs of the children.
If at any given point an input changes or a state changes, React will render
the component again by calling the component function.
Consider this:
function SomeComponent(text) {
return (<div>The <i>text</i> prop has the value {text}</div>)
}
Let's say the initial prop value is "abc", React will call SomeComponent("abc"), then the function returns
<div>The <i>text</i> prop has the value abc</div> and React will render that.
If the prop text does not change, then React does nothing anymore.
Now the parent component changes the prop to "def", now React will call
SomeComponent("def") and it will return
<div>The <i>text</i> prop has the value def</div>, this is different from
last call, so React will update the DOM to reflect the change.
Now let's introduce state
function SomeComponent() {
const [name, setName] = React.useState("John")
function doSomething()
{
alert("The name is " + name)
}
return (
<p>Current name: {name}</p>
<button onClick={() => setName("Mary")}>Set name to Mary</button>
<button onClick={() => setName("James")}>Set name to James</button>
<button onClick={() => doSomething()}>Show current name</button>
)
}
So here React will call SomeComponent() and render the name John and the 3
button. Note that the value of the name variable does not change during the
current execution, because it's declared as const. This variable only reflects
the latest value of the state.
When you press the first button, setName() is executed. React will internally store
the new value for the state and because of the change of state, it will render
the component again, so SomeComponent() will be called once again. Now the variable name will
reflect again the latest value of the state (that's what useStatedoes), so in this case Mary. React
will realize that the DOM has to be updated and it prints the name Mary.
If you press the third button, it will call doSomething() which will print the
latest value of the name variable because every time React calls
SomeComponent(), the doSomething() function is created again with the latest
value of name. So once you've called setName(), you don't need to do
anything special to get the new value. React will take care of calling the
component function again.
So when you don't use class components but function components, you have to think
differently: the function gets called all the time by React and at any single
execution it reflects the latest state at that particular point in time. So when you
call the setter of a useState hook, you know that the component function will
be called again and useState will return the new value.
I recommend that you read this article, also read again Components and
Props from the React documentation.
So how should you do proceed? Well, like this:
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
console.log(value);
};
const handleClick = () => {
// DOING SOMETHING WITH value
alter(`Now I'm going to do send ${value}`);
}
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
<button type="button" onClick={handleClick}>Send selected option</button>
</div>
);
}
See CodeSandbox.

React Hooks: accessing state across functions without changing event handler function references

In a class based React component I do something like this:
class SomeComponent extends React.Component{
onChange(ev){
this.setState({text: ev.currentValue.text});
}
transformText(){
return this.state.text.toUpperCase();
}
render(){
return (
<input type="text" onChange={this.onChange} value={this.transformText()} />
);
}
}
This is a bit of a contrived example to simplify my point. What I essentially want to do is maintain a constant reference to the onChange function. In the above example, when React re-renders my component, it will not re-render the input if the input value has not changed.
Important things to note here:
this.onChange is a constant reference to the same function.
this.onChange needs to be able to access the state setter (in this case this.setState)
Now if I were to rewrite this component using hooks:
function onChange(setText, ev) {
setText(ev.currentValue.text);
};
function transformText(text) {
return text.toUpperCase();
};
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={onChange} value={transformText()} />
);
}
The problem now is that I need to pass text to transformText and setText to onChange methods respectively. The possible solutions I can think of are:
Define the functions inside the component function, and use closures to pass the value along.
Inside the component function, bind the value to the methods and then use the bound methods.
Doing either of these will change the constant reference to the functions that I need to maintain in order to not have the input component re-render. How do I do this with hooks? Is it even possible?
Please note that this is a very simplified, contrived example. My actual use case is pretty complex, and I absolutely don't want to re-render components unnecessarily.
Edit:
This is not a duplicate of What useCallback do in React? because I'm trying to figure out how to achieve a similar effect to what used to be done in the class component way, and while useCallback provides a way of doing it, it's not ideal for maintainability concerns.
This is where you can build your own hook (Dan Abramov urged not to use the term "Custom Hooks" as it makes creating your own hook harder/more advanced than it is, which is just copy/paste your logic) extracting the text transformation logic
Simply "cut" the commented out code below from Mohamed's answer.
function SomeComponent(props) {
// const [text, setText] = React.useState("");
// const onChange = ev => {
// setText(ev.target.value);
// };
// function transformText(text) {
// return text.toUpperCase();
// }
const { onChange, text } = useTransformedText();
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
And paste it into a new function (prefix with "use*" by convention).
Name the state & callback to return (either as an object or an array depending on your situation)
function useTransformedText(textTransformer = text => text.toUpperCase()) {
const [text, setText] = React.useState("");
const onChange = ev => {
setText(ev.target.value);
};
return { onChange, text: textTransformer(text) };
}
As the transformation logic can be passed (but uses UpperCase by default), you can use the shared logic using your own hook.
function UpperCaseInput(props) {
const { onChange, text } = useTransformedText();
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
function LowerCaseInput(props) {
const { onChange, text } = useTransformedText(text => text.toLowerCase());
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
You can use above components like following.
function App() {
return (
<div className="App">
To Upper case: <UpperCaseInput />
<br />
To Lower case: <LowerCaseInput />
</div>
);
}
Result would look like this.
You can run the working code here.
Define the callbacks inside the component function, and use closures to pass the value along. Then what you are looking for is useCallback hook to avoid unnecessary re-renders. (for this example, it's not very useful)
function transformText(text) {
return text.toUpperCase();
};
function SomeComponent(props) {
const [text, setText] = useState('');
const onChange = useCallback((ev) => {
setText(ev.target.value);
}, []);
return (
<input type="text" onChange={onChange} value={transformText(text)} />
);
}
Read more here
I know it's bad form to answer my own question but based on this reply, and this reply, it looks like I'll have to build my own custom hook to do this.
I've basically built a hook which binds a callback function with the given arguments and memoizes it. It only rebinds the callback if the given arguments change.
If anybody would find a need for a similar hook, I've open sourced it as a separate project. It's available on Github and NPM.
The case isn't specific to hooks, it would be the same for class component and setState in case transformText and onChange should be extracted from a class. There's no need for one-line functions to be extracted, so it can be assumed that real functions are complex enough to justify the extraction.
It's perfectly fine to have transform function that accepts a value as an argument.
As for event handler, it should have a reference to setState, this limits ways in which it can be used.
A common recipe is to use state updater function. In case it needs to accept additional value (e.g. event value), it should be higher-order function.
const transformText = text => text.toUpperCase();
const onChange = val => _prevState => ({ text: val });
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={e => setText(onChange(e.currentValue.text)} value={transformText(text)} />
);
}
This recipe doesn't look useful in this case because original onChange doesn't do much. This also means that the extraction wasn't justified.
A way that is specific to hooks is that setText can be passed as a callback, in contrast to this.setState. So onChange can be higher-order function:
const transformText = text => text.toUpperCase();
const onChange = setState => e => setState({ text: e.currentValue.text });
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={onChange(setText)} value={transformText(text)} />
);
}
If the intention is to reduce re-renders of children caused by changes in onChange prop, onChange should be memoized with useCallback or useMemo. This is possible since useState setter function doesn't change between component updates:
...
function SomeComponent(props) {
const [text, setText] = useState('');
const memoizedOnChange = useMemo(() => onChange(setText), []);
return (
<input type="text" onChange={memoizedOnChange} value={transformText(text)} />
);
}
The same thing can be achieved by not extracting onChange and using useCallback:
...
function SomeComponent(props) {
const [text, setText] = useState('');
const onChange = e => setText({ text: e.currentValue.text });
const memoizedOnChange = useCallback(onChange, []);
return (
<input type="text" onChange={memoizedOnChange} value={transformText(text)} />
);
}

Categories