How to debounce Formik Field ReactJS - javascript

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}
/>
)
}

Related

How to debounce a function?

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);
}

TODOLIST Update in REACT

So I was trying to update the value I got by the Addlist and I tried this but this isn;t working. Also when I click on the '+' button without writing anything, an empty list is created. How should I stop it. I've attached a code below.
import React from "react";
import "./App.css";
import { useState } from "react";
import TodoList from "./components/TodoList";
function App() {
const [input, setInput] = useState("");
const [list, setList] = useState([]);
const updateList = (e) => {
setInput(e.target.value);
};
const AddList = () => {
console.log("value added")
setList((addValue) => {
return [...addValue, input];
});
setInput("");
};
const updateItems=(id)=>{
const newValue=[...list].map((newVal)=>{
if(input.id===id){
input.text='';
}
return newVal;
})
setList(newValue);
}
const deleteItems = (id) => {
console.log("deleted");
setList((addValue) => {
return addValue.filter((element, index) => {
return index !== id;
});
});
};
return (
<div className="todo-app">
<h1> Enter Anything</h1>
<input
type="text"
placeholder="Add anything"
value={input}
onChange={updateList}
/>
<button onClick={AddList}>+</button>
<ul>
{list.map((itemsvalue, id) => {
return (
<TodoList
itemsValue={itemsvalue}
key={id}
onSelect={deleteItems}
id={id}
onUpdate={updateItems}
/>
);
})}
</ul>
</div>
);
}
export default App;
Any kind of help would be appreciated. Also if I want to split this into multiple components is there a way to do.
When user clicks on the add button there is the check for empty String AddList method
for ex:- User updates second index value, second position value will get updated.
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [index, setIndex] = useState(null);
const updateList = (e) => {
setInput(e.target.value);
};
useEffect(() => {
setList(list);
console.log(list, '<>?');
}, [index]);
const AddList = () => {
if (input.trim() !== '') {
setList([...list, input]);
}
setInput('');
};
const updateValue = (index) => {
console.log(list[index]);
setIndex(index);
if (list[index].trim() !== '') {
setInput(list[index]);
}
};
const UpdateList = () => {
list[index] = input;
console.log(list, 'before <>?');
setIndex(null);
setInput('');
};
return (
<div>
<input type="text" placeholder="Add anything" value={input} onChange={updateList} />
<button disabled={!index && !list.length === 0} onClick={AddList}>
Add
</button>
<button disabled={input.trim() === ''} onClick={UpdateList}>
Update
</button>
{list.map((m, index) => (
<h1 style={{ border: '1px solid black' }} onClick={() => updateValue(index)}>
{m}
</h1>
))}
</div>
);

React useEffect onClick Refetch Data - Change Params

How do I add a click handler to refetch data from my API based on my input ON CLICK?
In my console I'm getting back data if I input "Jon Snow" for instance because the onChange set to e.target.value but not sure how to fetch this on button click.
Code Sandbox: https://codesandbox.io/s/pedantic-lichterman-4ev6f?file=/src/game.jsx
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function Game() {
const [error, setError] = useState(null);
const [name, setName] = useState("");
const handleSubmit = e => {
e.preventDefault();
console.log( name );
}
const handleClick = e => {
// ??
}
useEffect(() => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then((data) => {
console.log(data[0].name); // the data I want back
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, [name]);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
);
}
When the Submit button is clicked it will trigger onSubmit event, no need for you to handle the onClick event separately.
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function Game() {
const [error, setError] = useState(null);
const [name, setName] = useState("");
const handleSubmit = e => {
e.preventDefault();
console.log( name );
fetchData(name);
}
const fetchData = (name) => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then((data) => {
console.log(data[0].name); // the data I want back
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}
useEffect(() => {
fetchData(name);
}, []);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
);
}
Add another stateful variable. You need not only a value and setter for the input value but also a value and setter for the API results you want to be able to use elsewhere. Maybe something like
const [searchText, setSearchText] = useState('');
const [result, setResult] = useState('');
// inside fetch callback:
setResult(data[0]?.name ?? ''); // use optional chaining to not throw an error
// if there is no result
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Name"
/>
And then you can use the result where you need.
Live demo:
const App = () => {
const [error, setError] = React.useState(null);
const [searchText, setSearchText] = React.useState('');
const [result, setResult] = React.useState('');
const handleSubmit = e => {
e.preventDefault();
console.log( name );
}
React.useEffect(() => {
fetch(`https://anapioficeandfire.com/api/characters?name=${searchText}`)
.then((res) => res.json())
.then((data) => {
setResult(data[0] ? data[0].name : '');
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, [searchText]);
console.log(result);
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" onClick={e => e.preventDefault()}/>
</form>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
You can use direct state variable [name] in handleClick function.
The other answers are all correct that you should trigger the fetch in your handleSubmit. I just wanted to chime in with some sample code for rendering results since you asked for help with that.
The API returns an array of characters. We want to map through that result and show each character. We also want to tell the user if there were no results (especially since this API seems to only work with an exact name and will return a result for "Arya Stark" but not for "Stark"). We don't want to show that "No Characters Found" message before they have submitted.
I am using a setState hook to store the array of character matches from the API. I am initializing the state to undefined instead of [] so that we only show the no results message if it gets set to [].
My code allows the user to submit multiple times. We keep displaying the previous results until they submit a new search. Once we have an array in our characters state, we display those results.
// an example component to render a result
const RenderCharacter = ({ name, aliases }) => {
return (
<div>
<h2>{name}</h2>
{aliases.length && (
<div>
<h3>Aliases</h3>
<ul>
{aliases.map((a) => (
<li key={a}>{a}</li>
))}
</ul>
</div>
)}
</div>
);
};
export default function Game() {
// current form input
const [name, setName] = useState("");
// save characters returned from the API
// start with undefined instead of empty array
// so we know when to show "no characters found" message
const [characters, setCharacters] = useState();
// store API errors
const [error, setError] = useState(null);
const fetchData = () => {
fetch(`https://anapioficeandfire.com/api/characters?name=${name}`)
.then((res) => res.json())
.then(setCharacters) // store data to state
.then(() => setError(null)) // clear previous errors
.catch((error) => {
console.log("Error", error);
setError(error);
setCharacters(undefined); // clear previous character matches
});
};
const handleSubmit = (e) => {
e.preventDefault();
fetchData();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input type="submit" value="Submit" />
</form>
{characters !== undefined &&
(characters.length === 0 ? (
<div>No Characters Found</div>
) : (
<div>
{characters.map((character) => (
<RenderCharacter key={character.name} {...character} />
))}
</div>
))}
{error !== null && <div>Error: {error.message}</div>}
</div>
);
}
Code Sandbox Demo (with typescript annotations)

stop character been inserted into input react

I want to detect which character base on user keyboard hence I use onKeyDown, but how do I stop ',' been inserted into the input element?
const [inputValue, setInputValue] = useState("");
const handleKeyDown = (e: any) => {
if (['188'].includes(e.keyCode)) {
console.log("do something");
}
};
const handleChange = (e: any) => {
setInputValue(e.target.value)
}
return (
<input
type="text"
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
);
const handleKeyDown = (e: any) => {
if (['188'].includes(e.keyCode)) {
return;
}
};
Your includes doesn't work since '188' (string) isn't equal to 188 (number)
You can check the last inputed char in your handleChange function like so:
const handleChange = (e: any) => {
const value = e.target.value
if (![','].includes(value[value.length-1])) {
setInputValue(e.target.value)
}
}
You just need to check the input in handleChange to decide to update new value or not
Try the code below:
Or Codesandbox
import React, { useState } from "react";
import "./styles.css";
const App = () => {
const [inputValue, setInputValue] = useState("");
const handleChange = e => {
if (!e.target.value.includes(",")) {
setInputValue(e.target.value);
}
};
return (
<input
value={inputValue}
type="text"
onChange={handleChange}
/>
);
};
export default App;

prevent useEffect to fire on initial render

I want to do a debounce for custom input, but my problem is I can't stop useEffect from trigger on initial render
import { useDebouncedCallback } from "use-debounce";
interface myInputProps {
getValue: any;
}
const MyInput = ({ getValue }: myInputProps) => {
const [value, setValue] = useState("");
React.useEffect(() => {
getValue(value);
}, [value]);
return (
<input type="text" value={value} onChange={e => setValue(e.target.value)} />
);
};
export default function App() {
const [debouncedCallback] = useDebouncedCallback(value => {
console.log(value);
}, 1000);
return (
<div className="App">
<MyInput getValue={debouncedCallback} />
</div>
);
}
https://codesandbox.io/s/upbeat-lamport-ukq70?file=/src/App.tsx
I've also tried useLayoutEffect but it doesn't solve the problem.
We could use useRef to keep track of if it's the first time the useEffect hook is being run.
https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
Sandbox link: https://codesandbox.io/s/confident-cerf-flkf2?file=/src/App.tsx
const MyInput = ({ getValue }: myInputProps) => {
const [value, setValue] = useState("");
const first = useRef(true);
React.useEffect(() => {
if (first.current) {
first.current = false;
return;
}
getValue(value);
}, [value]);
return (
<input type="text" value={value} onChange={e => setValue(e.target.value)} />
);
};
Set initial value to undefined and you can explicitly check for undefined. Once the user enter, it won't be undefined.
const MyInput = ({ getValue }: myInputProps) => {
const [value, setValue] = useState(undefined);
React.useEffect(() => {
if (value === undefined) {
return;
}
getValue(value);
}, [value]);
return (
<input type="text" value={value} onChange={e => setValue(e.target.value)} />
);
};

Categories