Trying to create an react autocomplete component and use debounce function to reduce frequent api call. The code is following:
const { useState, useMemo } = React;
function debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, ...args), delay);
}
}
const getApi = (terms) => `https://www.reddit.com/search.json?q=${terms}`;
const Search = () => {
const DELAY = 500;
const [text, setText] = useState("");
const [list, setList] = useState([]);
const fetchData = useMemo(debounce(async(evt) => {
const res = await fetch(getApi(evt.target.value));
const {data} = await res.json();
setList(data.children.map(d => d.data.title));
}, DELAY), []);
return (
<div>
<form className="bg-gray-200 p-5">
<input
type="text"
name="text"
placeholder="search users..."
value={text}
onChange={(e) => {
setText(e.target.value);
fetchData(e);
}
}
className="bg-white p-2 w-3/4 outline-none"
/>
</form>
<ul>
{list.map(title => <li>{title}</li>)}
</ul>
</div>
);
};
ReactDOM.render(<Search />, document.getElementById("app"));
The api call has not been made after I test it from console. Any idea where goes wrong?
The signature of useMemo is
const memoizedValue = useMemo(functionThatCreatesMemoizedValue, [dependencies]);
The first argument passed to it is executed immediately. But you have
const fetchData = useMemo(debounce(......), []);
debounce returns a debounced function; that debounced function is then called immediately by useMemo, and the return value is put into fetchData.
What you need to do is have a function that returns the debounced function.
const fetchData = useMemo(() => debounce(......), []);
function debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, ...args), delay);
}
}
const Search = () => {
const DELAY = 500;
const [text, setText] = React.useState("");
const [list, setList] = React.useState([]);
const fetchData = React.useMemo(() => debounce((evt) => {
console.log('api call');
}, DELAY), []);
return (
<div>
<form className="bg-gray-200 p-5">
<input
type="text"
name="text"
placeholder="search users..."
value={text}
onChange={(e) => {
setText(e.target.value);
fetchData(e);
}
}
className="bg-white p-2 w-3/4 outline-none"
/>
</form>
<ul>
{list.map(title => <li>{title}</li>)}
</ul>
</div>
);
};
ReactDOM.createRoot(document.querySelector('.react')).render(<Search />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div class='react'></div>
For a memoized value that's a callback, you can also consider using useCallback instead, which works nearly identically but is arguably a bit more semantically appropriate.
const fetchData = useCallback(debounce((evt) => {
console.log('api call');
}, DELAY), []);
function debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, ...args), delay);
}
}
const Search = () => {
const DELAY = 500;
const [text, setText] = React.useState("");
const [list, setList] = React.useState([]);
const fetchData = React.useCallback(debounce((evt) => {
console.log('api call');
}, DELAY), []);
return (
<div>
<form className="bg-gray-200 p-5">
<input
type="text"
name="text"
placeholder="search users..."
value={text}
onChange={(e) => {
setText(e.target.value);
fetchData(e);
}
}
className="bg-white p-2 w-3/4 outline-none"
/>
</form>
<ul>
{list.map(title => <li>{title}</li>)}
</ul>
</div>
);
};
ReactDOM.createRoot(document.querySelector('.react')).render(<Search />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div class='react'></div>
Related
I have such a functional component. When a user enters input, I send a message to the server and output to other users what someone writes. Here is the code:
const ChatInput = (props) => {
const [message, setMessage] = useState('');
const typingMessage = () =>{
socket.emit('typing',props.username);
}
return (
<div className>
<Input
value = {message}
onChange = {
(e) => typingMessage(e.target.value)
}
placeholder="Type a message here"
/>
<Button
onClick={sendMessage}
icon={<SendOutlined />
}/>
</div>
);
};
How do I track that the user has stopped writing? If he does not enter anything into the input for more than 10 seconds?
You need deboune function, that will count 10sec (it may be different time, depends on you) after last input (onChange trigger)
function debounce(func, timeout = 10000){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function saveInput(){
console.log('User has stopped writting 10 sec ago');
}
const processChange = debounce(() => saveInput());
const typingMessage = () =>{
socket.emit('typing',props.username);
processChange()
}
You can use a combination of onFocus, onBlur, and onMouseLeave, in such a way that when onFocus happens, he is writing, when onBlur happens, he is not writing anymore, and when onMouseLeave happens or when he clicks on send, you trigger onBlur yourself. Like below:
I assumed that your Input component can forward ref. If not make it so by following Forwarding Refs.
const ChatInput = (props) => {
const [message, setMessage] = useState("");
const inputRef = useRef();
const typingMessage = () => {
socket.emit("typing", props.username);
};
const notTypingMessage = () => {
socket.emit("typing", "");
};
return (
<div className>
<Input
ref={inputRef}
value={message}
onChange={(e) => setMessage(e.target.value)}
onFocus={() => typingMessage()}
onBlur={() => notTypingMessage()}
onMouseLeave={() => inputRef?.current.blur()}
placeholder="Type a message here"
/>
<Button onClick={()=>{sendMessage(); inputRef?.current.blur()}} icon={<SendOutlined />} />
</div>
);
};
export default ChatInput;
This is a one solution waiting for a second after finish typing and emits stoppedTyping event.
you may still want to optimize it according to the your applications needs
const emit = (action, data) => {
console.log(action, data)
}
function App() {
const props = { username: 'me' }
const [message, setMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
useEffect(() => {
if (message && !isTyping) {
emit('stoppedTyping', props.username)
}
}, [isTyping])
const typingMessage = (v) => {
emit('typing', props.username);
setMessage(v)
}
const sendMessage = (e) => {
console.log(e)
}
return (
<div className=''>
<input
value={message}
onKeyDown={() => !isTyping && setIsTyping(true)}
onKeyUp={() => {
setTimeout(() => {
setIsTyping((t) => t ? false : t)
}, 1000)
}}
onChange={
(e) => typingMessage(e.target.value)
}
placeholder="Type a message here"
/>
<button
onClick={sendMessage}
>send</button>
<pre>{
JSON.stringify({ message, isTyping })
}</pre>
</div>
);
}
This is a functional component. Here I am using useEffect hook to hit API on the dependency of search. The place where I am doing console.log(${search} is displaying the current search in the console. How can I take the last five searches in an array and then display them?
const [city, setCity] = useState(null);
const [search, setSearch] = useState("Dehradun");
useEffect ( () => {
const fetchApi = async () => {
const url = `https://api.openweathermap.org/data/2.5/weather?q=${search}&units=metric&appid=7938d9005e68d8b258a109c716436c91`
const response = await fetch(url);
fetch("https://api.openweathermap.org/data/2.5/weather?q=${search}&units=metric&appid=7938d9005e68d8b258a109c716436c91")
.then(result => console.log(`${search}`, result))
const resJson = await response.json();
setCity(resJson.main);
};
fetchApi();
},[search] )```
The following code is able to keep track of previous searches as you expect. It just stacks the previous response to an array along with the result and excludes items from the end when the size exceeds more than 5.
function App() {
const [search, setSearch] = React.useState("Dehradun");
const [searchHistory, setSearchHistory] = React.useState([]);
const doSearch = () => {
if (search.length > 0) {
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${search}&units=metric&appid=7938d9005e68d8b258a109c716436c91`
)
.then((res) => res.json())
.then((result) => {
setSearchHistory((prevState) => [
[search, result],
...prevState.slice(0, 4)
]);
setSearch("");
});
}
};
return (
<div>
<input onChange={(e) => setSearch(e.target.value)} value={search} />
<button onClick={doSearch}>search</button>
{searchHistory.map(([search, result], index) => (
<div key={index}>
<b>{search}</b> : {JSON.stringify(result)}
</div>
))}
</div>
);
}
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>
I'm trying to display a new, filtered array that hides the rest of the elements and leaves only the ones I type in the search bar. The const newFilter works in the console but doesn't read in the return. I tried placing the const in other places but it's beyond the scope..
import React, { useState } from "react";
function SearchBar({ placeholder }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const [pokemonData, setPokemonData] = React.useState({});
React.useEffect(() => {
fetch(
"https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json"
)
.then((res) => res.json())
.then((data) => setPokemonData(data.pokemon));
}, []);
const allPokes = pokemonData;
const pokemons = Object.values(allPokes);
const handleFilter = (event) => {
const searchWord = event.target.value;
setWordEntered(searchWord);
const newFilter = pokemons.filter((value) => {
return value.name.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
setFilteredData([]);
} else {
setFilteredData(newFilter);
}
console.log(newFilter);
};
let checkConsole = () => alert("Check the console :)");
return (
<div className="search-div">
<p className="search-text">Name or Number</p>
<div className="search">
<div className="searchInputs">
<input
type="text"
placeholder={placeholder}
value={wordEntered}
onChange={handleFilter}
/>
</div>
</div>
</div>
);
}
export default SearchBar;
In the given snippet, there is no filtered data displaying logic inside the return
import React, { useState } from "react";
export default function SearchBar({ placeholder }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const [pokemonData, setPokemonData] = React.useState({});
React.useEffect(() => {
fetch(
"https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json"
)
.then((res) => res.json())
.then((data) => setPokemonData(data.pokemon));
}, []);
React.useEffect(() => {
console.log(filteredData);
});
const allPokes = pokemonData;
const pokemons = Object.values(allPokes);
const handleFilter = (event) => {
const searchWord = event.target.value;
setWordEntered(searchWord);
const newFilter = pokemons.filter((value) => {
return value.name.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
setFilteredData([]);
} else {
setFilteredData(newFilter);
}
console.log(newFilter);
};
let checkConsole = () => alert("Check the console :)");
return (
<div className="search-div">
<p className="search-text">Name or Number</p>
<div className="search">
<div className="searchInputs">
<input
type="text"
placeholder={placeholder}
value={wordEntered}
onChange={handleFilter}
/>
</div>
/* Add filteredData logic in the return */
{filteredData.map((each) => (
<p>{each.name}</p>
))}
</div>
</div>
);
}
I've to call useEffect / Fetch the data only when user click on Search Button otherwise not fetch the data..
Code:
const App = () => {
const[datas,setDatas] = useState([])
const [space,setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val){
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
return(
<div className="App">
{ //Displaying on search
print?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<p>{field.title}</p>
<p>{field.author}</p>
)}
</div>
</>
:null
}
<input type="text" onChange={getData} />
<button onClick={() => { setSpace(true); fetchPosts() }}>search</button>
</div>
)
}
};
export default App;
It's not working Error:
fetchPosts() is not defined...
I've also tried like this:
function trigger(){
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
}
<button onClick={() => { setSpace(true); trigger() }}>search</button>
It's not working Error:
React Hook useEffect has unnecessary dependencies:'space'
/PLZZ help to out...
make a separate function for api call and in your UseEffect function just call that function and on Button click function call the Api Function and it fetch data automatically
Use useCallback not useEffect
useCallback is similar to useEffect but is for when a function needs a callback, like what you're doing here onClick. useEffect is used in response to some prop changing not an action taken by a user.
You have to set your fetchPosts outside of the useEffect.
Then, you can use a new state search to track any click on the button.
const App = () => {
const [datas, setDatas] = useState([]);
const [space, setSpace] = useState(null);
const [print, setPrint] = useState(false);
const [search, setSearch] = useState(false);
const fetchPosts = async () => {
let initial_url = `http://localhost:4000/search`;
let url = initial_url + "?text=" + space;
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
};
function getData(val) {
setSpace(val.target.value);
setPrint(false);
}
useEffect(() => {
fetchPosts(); // fecthPosts is called each time space changed
}, [search]);
return (
<div className="App">
{
//Displaying on search
print ? (
<>
<h2>{space}</h2>
<div>
{datas.map((field) => (
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
))}
</div>
</>
) : null
}
<input type="text" onChange={getData} />
<button onClick={() => setSearch(!search)}>search</button>
</div>
);
};
export default App;
I initialized shouldFetchData = false. Once the button is clicked, I changed it's value; shouldFetchData = true. Inside useEffect I called fetchPosts() only when shouldFetchData is true.
import React, { useState } from "react";
const App = () => {
const [datas, setDatas] = useState([])
const [shouldFetchData, setShouldFetchData] = useState(false);
const [space, setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val) {
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
}
useEffect(() => {
if(shouldFetchData) {
fecthPosts();
}
}, [space]);
return (
<div className="App">
{
print ?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
)}
</div>
</>
: null
}
<input type="text" onChange={getData} />
<button onClick={() => {
setSpace(true);
setShouldFetchData(true);
}}>search</button>
</div>
)
};
export default App;
I found a few syntax errors in your code, so I hope I did what you intended.
If This is not the proper way to do this or if there exists a better way, please let me know. I'd be happy to learnš¤.
So, i have this code that retrieves movies from api and im trying to implement live searching. I created an if statement to check the input but every time i put the first character in the input field I get the filter undefined error. How can I fix this?
import React, { useState, useEffect } from "react";
const SearchMovie = () => {
const [state, setState] = useState([]);
const [movie, setmovie] = useState("");
const [search, setSearch] = useState("");
const key = "xxxxxxxx";
const url = `http://www.omdbapi.com/?s=${movie}&apikey=${key}`;
useEffect(() => {
getData();
}, [movie]);
const getData = async () => {
const data = await fetch(url);
const response = await data.json();
setState(response.Search);
console.log(response.Search);
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setmovie(search);
setSearch("");
}
if(search.length > 0) {
setState(state.filter((i)=>{
return i.Title.match(search)
}))
}
return (
<div>
<form onSubmit={getSearch} className="search-form">
<input
type="text"
className="search-bar"
value={search}
onChange={updateSearch}
/>
<button className="search-button" type="submit">
Search
</button>
</form>
{(state || []).map((details) => (
<>
<p>{details.Title}</p>
<p>{details.Year}</p>
<img src={details.Poster} alt=""/>
</>
))}
</div>
);
};
export default SearchMovie;
You have call filter on undefined. Because you have setState(response.Search). I think your result search is undefined. Let's check.
const getData = async () => {
const data = await fetch(url);
const response = await data.json();
setState(response.Search ? response.Search : []);
//or
//if (response.Search) setState(response.Search)
console.log(response.Search);
};