I'm new to React 18 and Suspense. Nearly all of my previous web development was done in asp.net mvc. I want to click a button on a form, pass the form input values to a web api HttpGet method with the [FromQuery] attribute, and render the return into a div.
If I were doing this in asp.net mvc, I would wire up a button click event like so in javascript:
const btnSearch = document.getElementById('btnSearch');
btnSearch.addEventListener("click", function() {
executeMySearch();
return false;
});
And in the executeMySearch() method I'd grab the form input values, send them to server, fetch some html from the server and plunk it into a div like so:
const searchresults = document.getElementById('searchresults');
let formData = new FormData(document.forms[0]);
fetch('/Index?handler=MySearchMethod', {
method: 'post',
body: new URLSearchParams(formData),
}).then(function (response) {
return response.text();
}).then(function (html) {
searchresults.innerHTML = html;
Of course in React the approach is completely different, I showed the code above only to demonstrate what I want to happen. I want the search to execute only when the search button is clicked. My problem is, I cannot figure out how to manage React state to make that happen. Currently, after the search button is clicked once, my search is executing every time the user changes the value of a form input. I understand why that is happening, but I can't figure out how to structure my components so that the search executes only when the search button is clicked.
Server-side, my web api receives a form and returns a generic list, like so. This works fine:
[HttpGet("MySearchMethod")]
public async Task<List<MySearchResult>> MySearchMethod([FromQuery]MySearchForm mySearchForm)
{
return await _myRepository.GetMySearchResults(mySearchForm);
}
In my React app I have a search component. The component renders a form with the following elements:
- four selects, which contain the search criteria. These selects are wrapped in React components.
- a search button
- a component that renders the search results
Each select input is a React component that contains a list of enums fetched from the web api. Each select is defined in the search component like so:
const MyEnums = lazy(() => import('./MyEnums'));
Each of these React components is tied to the React state when the search component is defined, like so:
const MySearchComponent = () => {
const [myEnum, setMyEnum] = useState(0);
function onChangeMyEnum(myEnumId : number){
setMyEnum(myEnumId);
}...
and I tie my search button to React state like so:
const [isSearch, setIsSearch] = useState(false);
My search component returns a form with the search criteria and search button, and a div to contain the search results:
return (
<>
<form>
<div>
<ErrorBoundary FallbackComponent={MyErrorHandler}>
<h2>My Search Criteria Select</h2>
<Suspense fallback={<Spinner/>}>
<MyEnums onChange={onChangeMyEnum} />
</Suspense>
</ErrorBoundary>
</div>
<button className='btn btn-blue' onClick={(e) => {
e.preventDefault();
setIsSearch(true);
}
}>Search</button>
</form>
<div>
{
isSearch === true ?
<ErrorBoundary FallbackComponent={MyErrorHandler}>
<Suspense fallback={<Spinner/>}>
<MySearchResults myEnum={myEnum} [..other search criteria] />
</Suspense>
</ErrorBoundary>
: <span> </span>
}
</div>
Everything works fine. The problem is, after the first time the search button is clicked (which executes "setIsSearch(true)"), every time a user alters a selection in one of the form inputs, the search executes. I understand why. My "isSearch" variable remains true, so when the state is altered by the form input changing, and the component is re-rendered, the search happens again.
I tried passing the "setIsSearch" method into the MySearchResults component, and calling setIsSearch(false) after the component rendered, but that of course does exactly what it is supposed to to. The React state changes, the component re-renders, it sees that "isSearch" is false, and it makes the search results disappear. When I click my search button I see the search results flicker briefly and then disappear, which is exactly what should happen.
I also tried calling setIsSearch(false) every time a select changes, but of course this causes my search results to disappear, which is not desired.
What am I missing? How do I structure this so that the search only occurs when I click the Search button?
P.S. the web api call is made inside of the MySearchResults component when it renders. The MySearchResults component looks like this:
import React from 'react';
import { useQuery } from 'react-query';
import MySearchResult from './MySearchResult';
const fetchMySearchResults = async (myEnumId : number [...other criteria]) => {
let url = `${process.env.REACT_APP_MY_API}/GetMySearchResults/?myEnumId=${myEnumId}&[...other criterial]`;
const response = await fetch(url);
return response.json();
}
const MySearchResults = (props : any) => {
const {data} = useQuery(['myquery', props.myEnum,...other search criteria...]
,() => fetchMySearchResults(props.myEnun [...other search criteria]),
{
suspense:true
});
return (
<>
<table>
<thead>
<tr>
<th>My Column Header</th>
<th>...and so on</th>
</tr>
</thead>
<tbody>
{data.map((mySearchResult: {
...and so on
</tbody>
</table>
</>
);
};
export default MySearchResults;