One solution is to "debounce" setting searchTerm to minimize the request to the API:
we're going to use lodash package particularly it's debounce
method (doc here), and useCallback
from Hooks API (doc here) :
import React, { useState, useCallback, useRef } from "react";
import _ from "lodash";
import axios from "axios";
import TextField from "@material-ui/core/TextField";
const SearchInputComponent = ({ label }) => {
const [value, setValue] = useState("");
const [data, setData] = useState([]);
const inputRef = useRef(null);
const debounceLoadData = useCallback(
_.debounce((value) => {
getData(value);
}, 500), // you can set a higher value if you want
[]
);
const getData = (name) => {
axios.get(`https://restcountries.eu/rest/v2/name/${name}`).then((res) => {
console.log(res);
setData(res.data);
});
};
const handleSearchFieldChange = (event) => {
const { value } = event.target;
setValue(value);
debounceLoadData(value);
};
return (
<>
<TextField
inputRef={inputRef}
id="searchField"
value={value}
label={"search"}
onChange={handleSearchFieldChange}
/>
{data &&
<ul>
{data.map(country=> (
<li key={country.alpha3Code}>{country.name}</li>
))
}
</ul>
}
</>
);
};
export default SearchInputComponent;
with this code the front end will wait 500 ms before fetching api with the search input value.
here a sandBox example.
Possible Feature: Make search field generic
If in the future you will need a search component you can make it generic with Context:
first create a context file named for example SearchInputContext.js and add:
SearchInputContext.js
import React, {
createContext,
useState
} from 'react';
export const SearchInputContext = createContext({});
export const SearchInputContextProvider = ({ children }) => {
const [value, setValue] = useState('');
return (
<SearchInputContext.Provider
value={{ searchValue: value, setSearchValue: setValue }}
>
{children}
</SearchInputContext.Provider>
);
};
Next create a generic searchField component named for example SearchInput.js and add in it :
SearchInput.js
import React, {
useState,
useCallback,
useRef,
useContext
} from 'react';
import _ from 'lodash';
import TextField from "@material-ui/core/TextField";
import { SearchInputContext } from './SearchInputContext';
const SearchInputComponent = () => {
const [value, setValue] = useState('');
const { setSearchValue } = useContext(SearchInputContext);
const inputRef = useRef(null);
const debounceLoadData = useCallback(
_.debounce((value) => {
setSearchValue(value);
}, 500),
[]
);
const handleSearchFieldChange = (event) => {
const { value } = event.target;
setValue(value);
debounceLoadData(value);
};
return (
<>
<TextField
inputRef={inputRef}
id="searchField"
value={value}
label={"search"}
onChange={handleSearchFieldChange}
/>
</>
);
};
export default SearchInputComponent;
After in your App.js (or other component page where you want a searchField) add your ContextProvider like this:
App.js
import {ListPage} from "./searchPage";
import {SearchInputContextProvider} from './SearchInputContext';
import "./styles.css";
export default function App() {
return (
<SearchInputContextProvider>
<ListPage/>
</SearchInputContextProvider>
);
}
And finally add your searchComponent where you need a search feature like in the ListPage component :
SearchPage.js:
import React, { useState,useContext, useEffect } from "react";
import axios from "axios";
import SearchInputComponent from './SearchInput';
import {SearchInputContext} from './SearchInputContext'
export const ListPage = () => {
const [data, setData] = useState([]);
const { searchValue } = useContext(SearchInputContext);
useEffect(() => {
if(searchValue){
const getData = (name) => {
axios.get(`https://restcountries.eu/rest/v2/name/${name}`).then((res) => {
console.log(res);
setData(res.data);
});
};
return getData(searchValue)
}
}, [ searchValue]);
return (
<>
<SearchInputComponent />
{data &&
<ul>
{data.map(country=> (
<li key={country.alpha3Code}>{country.name}</li>
))
}
</ul>
}
</>
);
};
here a sandbox link of this example