How to debounce a function? - javascript

I am trying to set a timer for onChange but I don't understand why it doesn't work. I tried several methods, but none of them worked. What can I change to make it work?
import React, {useState} from 'react'
import { debounce } from "lodash";
const Search = ({getQuery}) => {
const [text, setText] = useState('')
const onChange = (q) => {
setText(q)
getQuery(q)
}
const handleDebounce = () => {
debounce(onChange, 3000);
};
return (
<section className='search'>
<form>
<input
type='text'
className='form-control'
placeholder='Search characters'
value={text}
onChange={(e) => handleDebounce( onChange(e.target.value), 3000)}
autoFocus
/>
</form>
</section>
)}
export default Search

removce handleDebounce completely and call your own onChange at the input onChange
onChange={onChange}
then adjust your onChange implementation as:
const onChange = (e) => {
const query = e.target.value;
setText(query);
debounce(() => getQuery(query), 3000);
}

Related

How to debounce an input which is bound to a state value?

I am trying to debounce an input. I have memoized the debounce handler so that it does not change references on each render. I need the input to be bound to a state value as I need to set it elsewhere in my app. The issue is that the input value is never able to be updated as when inside the debounced changeHandler e.target.value always contains previous value, not a new value entered. How can i debounce the input that is bound to a state value?
xport function App() {
const [query, setQuery] = useState("a value");
const changeHandler = (event) => {
console.log(event.target.value); // wrong value
setQuery(event.target.value);
};
const debouncedChangeHandler = useCallback(debounce(changeHandler, 1000), []);
return (
<div>
<input
value={query}
onChange={debouncedChangeHandler}
type="text"
placeholder="Type a query..."
/>
</div>
);
}
sandbox: https://codesandbox.io/s/react-debounce-5pidiy?file=/src/index.js:130-596
I wouldn't try to debounce the keyboard input itself, I'd just wait to validate until the value hadn't changed for while, and then either directly validate it:
const { useState, useEffect, useCallback } = React;
function App() {
const [query, setQuery] = useState("a value");
const changeHandler = (event) => {
setQuery(event.target.value);
};
const validate = useCallback((query) => {
console.log(`Validating "${query}"...`);
});
useEffect(() => {
const timer = setTimeout(() => {
validate(query)
}, 1000);
return () => {
clearTimeout(timer);
}
}, [query]);
return (
<div>
<input
value={query}
onChange={changeHandler}
type="text"
placeholder="Type a query..."
/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
...or use a separate state member for it:
const { useState, useEffect, useCallback } = React;
function App() {
const [rawQuery, setRawQuery] = useState("a valid value");
const [query, setQuery] = useState(rawQuery);
const changeHandler = (event) => {
console.log(`raw query: ${event.target.value}`);
setRawQuery(event.target.value);
};
const validate = useCallback((query) => {
console.log(`Validating "${query}"...`);
return query.includes("valid");
});
useEffect(() => {
const timer = setTimeout(() => {
console.log(`validate: ${rawQuery}`);
if (validate(rawQuery)) {
console.log("Valid!");
setQuery(rawQuery);
} else {
console.log("Invalid!");
setRawQuery(query);
}
}, 1000);
return () => {
clearTimeout(timer);
}
}, [rawQuery, query]);
return (
<div>
<input
value={rawQuery}
onChange={changeHandler}
type="text"
placeholder="Type a query..."
/>
<div>Value to validate: {query}</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
That makes it easier to do what you said in a comment you wanted, putting the input back to the previous valid value if an invalid one has provided.
(That updating can be wrapped in a hook for reuse.)

Pulling a setState from child component and then using it as a value in parent component

I created a stopwatch and I am attempting to take the value of the stopwatch and pass it through a form component. Currently, when trying to push it through using 'props', it isn't connecting to the specific 'setTime' const determined in the StopWatch component.
I am using react-hook-form, and Styled Components throughout the project. And currently I don't have anything passed through my "value" in my controller because everything I'm trying to do just isn't working.
Here is the stop watch component:
import React, { useEffect, useState } from "react";
const StopWatch = (props) => {
const [time, setTime] = useState(0);
const [timerOn, setTimeOn] = useState(false);
useEffect(() => {
let interval = null;
if (timerOn) {
interval = setInterval(() => {
setTime((prevTime) => prevTime + 10);
}, 10);
} else {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [timerOn]);
// const updateTimeLogged = (e) => {
// props.setTimeOn(e.target.value);
// };
return (
<div>
<div>
<span>{("0" + Math.floor((time / 60000) % 60)).slice(-2)}:</span>
<span>{("0" + Math.floor((time / 1000) % 60)).slice(-2)}:</span>
<span>{("0" + ((time / 10) % 100)).slice(-2)}</span>
</div>
<div>
{!timerOn && time === 0 && (
<button onClick={() => setTimeOn(true)}>Start</button>
)}
{timerOn && <button onClick={() => setTimeOn(false)}>Stop</button>}
{!timerOn && time !== 0 && (
<button onClick={() => setTimeOn(true)}>Resume</button>
)}
{!timerOn && time > 0 && (
<button onClick={() => setTime(0)}>Reset</button>
)}
</div>
</div>
);
};
export default StopWatch;
And here is my form component:
import {
Button,
Form,
Input,
GridContainer,
Label,
InputWrapper,
DateWrapper,
NotesWrapper,
StopWatchWrapper,
} from "./PracticeLog.styled";
import { useForm, Controller } from "react-hook-form";
import { useState } from "react";
import StopWatch from "./StopWatch";
const PracticeLogInput = (props) => {
const { register, handleSubmit, control } = useForm();
const [result, setResult] = useState("");
const onSubmit = (data) => console.log(data);
return (
<GridContainer>
<Form onSubmit={handleSubmit(onSubmit)}>
<DateWrapper>
<Label>Date</Label>
<Input type="date" {...register("Date")} placeholder="Date"></Input>
</DateWrapper>
<NotesWrapper>
<Label>Notes</Label>
<Input type="text" {...register("Notes")} placeholder="Notes"></Input>
</NotesWrapper>
<StopWatchWrapper>
<Controller
name="time"
control={control}
onChange={(e) => setInterval(e.target.value)}
value={} //<-- this is where I need to put the value I get from 'setTime' in '/.StopWatch'.
render={StopWatch}
/>
</StopWatchWrapper>
<Button type="Submit">Submit</Button>
</Form>
</GridContainer>
);
};
export default PracticeLogInput;
If you see anything I can improve on, please let me know.
Try using this code but your code is little bit complex try making it simpler bro
import {
Button,
Form,
Input,
GridContainer,
Label,
InputWrapper,
DateWrapper,
NotesWrapper,
StopWatchWrapper,
} from "./PracticeLog.styled";
import { useForm, Controller } from "react-hook-form";
import { useState } from "react";
import StopWatch from "./StopWatch";
const PracticeLogInput = (props) => {
const { register, handleSubmit, control } = useForm();
const [time, setTime] = useState(0)
const [result, setResult] = useState("");
const onSubmit = (data) => console.log(data);
return (
<GridContainer>
<Form onSubmit={handleSubmit(onSubmit)}>
<DateWrapper>
<Label>Date</Label>
<Input type="date" {...register("Date")} placeholder="Date"></Input>
</DateWrapper>
<NotesWrapper>
<Label>Notes</Label>
<Input type="text" {...register("Notes")} placeholder="Notes"></Input>
</NotesWrapper>
<StopWatchWrapper>
<Controller
name="time"
control={control}
onChange={(e) => setInterval(e.target.value)}
value={time} //<-- this is where I need to put the value I get from 'setTime' in '/.StopWatch'.
render={<StopWatch
time={time}
setTime={setTime}
/>}
/>
</StopWatchWrapper>
<Button type="Submit">Submit</Button>
</Form>
</GridContainer>
);
};
export default PracticeLogInput;
And in StopWatch component change the code like this
const StopWatch = (props) => {
const {time,setTime}=props
const [timerOn, setTimeOn] = useState(false
);
//rest
Note : The flow is like parent to child you can't pull a state from child to parent directly instead define the state in parent and pass it as props to child. And change the state from child using the props itself.

how to update dropdown list by clicking the update button in react

I want to give some text in input field and after clicking the update button it should be update in the dropdown list but its not working
import React from "react";
export default function DropDown() {
const [input, setInput] = React.useState('');
const selectproject=({e})=>{
setInput(e.target.value);
}
return (
<>
<select>
<option>{input?input:'null'}</option>
</select>
<br/><br/>
<input value={input} /><button onClick={selectproject}>Update</button>
</>
);
}
remove curly braces on selectproject props.
selectproject=(e)=>{
setInput(e.target.value);
}
you are destructuring e in <Event> which is not available. if you want to destructuring it, try this instead
selectproject=({target: {value}})=>{
setInput(value);
}
if you want dropdown list to be updated after clicking button, you'll need another state
import React from "react";
export default function DropDown() {
const [input, setInput] = React.useState("");
const [project, setProject] = React.useState("");
const selectproject = () => {
setProject(input);
};
const handleOnChange = (e) => {
setInput(e.value.target);
};
return (
<>
<select>
<option>{project ? project : "null"}</option>
</select>
<br />
<br />
<input value={input} onChange={handleOnChange} />
<button onClick={selectproject}>Update</button>
</>
);
}
Need to remove curly brace around e and set onchange to input:
import React from "react";
export default function DropDown() {
const [input, setInput] = React.useState("");
const selectproject = (e) => {
setInput(e.target.value);
};
return (
<>
<select>
<option>{input ? input : "null"}</option>
</select>
<br />
<br />
<input value={input} onChange={selectproject} />
<button onClick= {selectproject}>Update</button>
</>
);
}
You can use from two useState:
import React from "react";
function DropDown() {
const [input, setInput] = React.useState('');
const [select,setSelect] = React.useState('');
const selectproject = () => {
setSelect(input);
}
const handleChange = (e) => {
setInput(e.target.value);
}
return (
<>
<select>
<option>{select?select:'null'}</option>
</select>
<br/><br/>
<input onChange={handleChange} value={input} /><button onClick={selectproject}>Update</button>
</>
);
}

Different value in browser and in console

Can someone please explain to me why in this code when I type a character the character that appears in the label tag is different from the character that appears on the console? Is the code correct?
import React, { useState } from "react";
const App = () => {
const [text, settext] = useState("");
const update = (e) => {
settext(e.target.value);
console.log(text);
};
return (
<div>
<input type="text" value={text} onChange={update} />
<label>{text}</label>
</div>
);
};
export default App;
The settext doesn't update the text state instantly. So calling console.log(text) right after it will give interesting results.
What you want to use is useEffect to log out the value of text.
import React, { useState, useEffect } from "react";
const App = () => {
const [text, settext] = useState("");
const update = (e) => {
settext(e.target.value);
};
useEffect(() => console.log(text), [text]);
return (
<div>
<input type="text" value={text} onChange={update} />{" "}
<label> {text} </label>{" "}
</div>
);
};
export default App;

How to debounce Formik Field ReactJS

I want to debounce Formik <Field/> but when I type in the field seems debounce does not work. Also I have tried lodash.debounce, throttle-debounce and the same result. How to solve this?
CodeSandbox - https://codesandbox.io/s/priceless-nobel-7p6nt
Snippet:
import ReactDOM from "react-dom";
import { withFormik, Field, Form } from "formik";
const App = ({ setFieldValue }) => {
let timeout;
const [text, setText] = useState("");
const onChange = text => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => setText(text), 750);
};
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={e => {
onChange(e.target.value);
setFieldValue("textField", e.target.value);
}}
style={{ width: "100%" }}
/>
<br />
<br />
<div>output: {text}</div>
</Form>
);
};
const Enhanced = withFormik({
mapPropsToValues: () => ({
textField: ""
}),
handleSubmit: (values, { setSubmitting }) => {
setSubmitting(false);
return false;
}
})(App);
ReactDOM.render(<Enhanced />, document.getElementById("root"));
const [text, setText] = useState("");
const [t, setT] = useState(null);
const onChange = text => {
if (t) clearTimeout(t);
setT(setTimeout(() => setText(text), 750));
};
I would like to suggest to move the call inside of timeout function.
const App = ({ setFieldValue }) => {
let timeout;
const [text, setText] = useState("");
const onChange = text => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
setText(text);
//changing value in container
setFieldValue("textField", text);
}, 750);
};
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={e => {
onChange(e.target.value);
}}
style={{ width: "100%" }}
/>
<br />
<br />
<div>output: {text}</div>
</Form>
);
};
Using Custom Hooks
This is abstracted from the answer provided by #Skyrocker
If you find yourself using this pattern a lot you can abstract it out to a custom hook.
hooks/useDebouncedInput.js
const useDebouncedInput = ({ defaultText = '', debounceTime = 750 }) => {
const [text, setText] = useState(defaultText)
const [t, setT] = useState(null)
const onChange = (text) => {
if (t) clearTimeout(t)
setT(setTimeout(() => setText(text), debounceTime))
}
return [text, onChange]
}
export default useDebouncedInput
components/my-component.js
const MyComponent = () => {
const [text, setTextDebounced] = useDebouncedInput({ debounceTime: 200 })
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={(e) => setTextDebounced(e.target.value)}
/>
<div>output: {text}</div>
</Form>
)
}
An Example Using Redux, Fetching, and Validation
Here's a partial example of using a custom hook for a debounced field validator.
Note: I did notice that Field validation seems to not validate onChange but you can expect it onBlur when you leave the field after your debounced update has executed (I did not try racing it or with a long debounce to see what happens). This is likely a bug that should be opened (I'm in the process of opening a ticket).
hooks/use-debounced-validate-access-code.js
const useDebouncedValidateAccessCode = () => {
const [accessCodeLookUpValidation, setAccessCodeLookUpValidation] = useState()
const [debounceAccessCodeLookup, setDebounceAccessCodeLookup] = useState()
const dispatch = useDispatch()
const debouncedValidateAccessCode = (accessCodeKey, debounceTime = 500) => {
if (debounceAccessCodeLookup) clearTimeout(debounceAccessCodeLookup)
setDebounceAccessCodeLookup(
setTimeout(
() =>
setAccessCodeLookUpValidation(
dispatch(getAccessCode(accessCodeKey)) // fetch
.then(() => undefined) // async validation requires undefined for no errors
.catch(() => 'Invalid Access Code'), // async validation expects a string for an error
),
debounceTime,
),
)
return accessCodeLookUpValidation || Promise.resolve(undefined)
}
return debouncedValidateAccessCode
}
some-component.js
const SomeComponent = () => {
const debouncedValidateAccessCode = useDebouncedValidateAccessCode()
return (
<Field
type="text"
name="accessCode"
validate={debouncedValidateAccessCode}
/>
)
}

Categories