In my React application, at the layout level and in addition to the main content, I have a sidebar and a header with a profile section and a search bar - all different components.
The header goes with every page - routes - and I want to do searches on any route; after doing the search (sent with enter) I go to the "/search" route to display and filter the results.
This is a snippet of code related to the search bar component - form:
const SearchSection = () => {
const [value, setValue] = useState("");
const navigate = useNavigate();
const location = useLocation();
const handleSearch = (event) => {
event.preventDefault();
if (location.pathname !== "/search") {
navigate("/search", { state: value });
}
};
return (
<>
<Box
component="form"
onSubmit={handleSearch}
sx={{ display: { xs: "none", md: "block" } }}
>
<OutlineInputStyle
id="input-search-header"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Search"
/>
</Box>
</>
);
};
export default SearchSection;
After the user submits the search (with enter) I navigate to the search page and display the results:
const Search = () => {
const [results, setResults] = useState([]);
const [error, setError] = useState(null);
const location = useLocation();
function showResults() {
const headers = { Accept: "application/ld json" };
const response = fetch(
`http://localhost:1026/ngsi-ld/v1/entities?type=Sensor&q=refRoom~=.*${location.state}`,
{
headers,
}
)
.then((response) => {
if (response.ok) {
return response.json();
}
throw response;
})
.then((data) => {
setResults(data);
})
.catch((error) => {
console.error("Error fetching data: ", error);
setError(error);
});
}
useEffect(() => {
showResults();
}, []);
return (
<Grid container spacing={gridSpacing}>
<Grid item xs={12}>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>Type</TableCell>
</TableRow>
</TableHead>
<TableBody>
{results.map((result) => (
<TableRow>
<TableCell>{result.id}</TableCell>
<TableCell>{result.type}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
);
};
export default Search;
routes/MainRoutes.js file:
import { lazy } from 'react';
import MainLayout from 'layout/MainLayout';
import Loadable from 'ui-component/Loadable';
const DashboardHome = Loadable(lazy(() => import('views/dashboard/Home')));
const SearchPage = Loadable(lazy(() => import('views/search')));
const MainRoutes = {
path: '/',
element: <MainLayout />,
children: [
{
path: '/',
element: <DashboardHome />
},
{
path: '/search',
element: <SearchPage />
}
]
};
export default MainRoutes;
routes/index.js file:
import { useRoutes } from 'react-router-dom';
import MainRoutes from './MainRoutes';
import AuthenticationRoutes from './AuthenticationRoutes';
export default function ThemeRoutes() {
return useRoutes([MainRoutes, AuthenticationRoutes], config.basename);
}
App.js file:
import { FirebaseProvider } from 'contexts/Firebase';
import Routes from 'routes';
const App = () => {
return (
<FirebaseProvider>
<>
<Routes />
</>
</FirebaseProvider>
);
};
export default App;
The user can start the search on any route and, regardless of where he is, he is redirected to /search
. The search bar is a subcomponent of the MainLayout
component. The MainLayout
component has the sidebar, header (which has the search bar) and main content, with the latter being the SearchPage
component, DashboardHome
, etc.
So far it works fine, the problem appears when I want to re-use the search bar already inside the /search route - it doesn't refresh the page.
How do I solve this problem and/or what is the best approach?
CodePudding user response:
Issues
- The
SearchSection
doesn't navigate to"/search"
with the new search value when the URL path is already on `"/search". - The
SearchPage
component doesn't "listen" to changes in the route state (from thelocation
object) to invokeshowResults
with the new search parameters.
Solution
Just navigate to "/search"
with new search state unconditionally. Or perhaps, only issue the navigation request if there is a search value. When the route state value changes, the useEffect
hook callback will be called and issue the fetch request.
const SearchSection = () => {
const [value, setValue] = useState("");
const navigate = useNavigate();
const handleSearch = (event) => {
event.preventDefault();
if (value) {
navigate("/search", { state: value, replace: true });
}
};
return (
<>
<Box
component="form"
onSubmit={handleSearch}
sx={{ display: { xs: "none", md: "block" } }}
>
<OutlineInputStyle
id="input-search-header"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Search"
/>
</Box>
</>
);
};
Unpack the route state and use as a dependency for the useEffect
hook in SearchPage
.
const Search = () => {
const [results, setResults] = useState([]);
const [error, setError] = useState(null);
const { state } = useLocation(); // <-- unpack route state
useEffect(() => {
const headers = { Accept: "application/ld json" };
// pass in queryString
const url = `http://localhost:1026/ngsi-ld/v1/entities?type=Sensor&q=refRoom~=.*${state || ''}`;
fetch(url, { headers })
.then((response) => {
if (response.ok) {
return response.json();
}
throw response;
})
.then((data) => {
setResults(data);
})
.catch((error) => {
console.error("Error fetching data: ", error);
setError(error);
});
}, [state]); // <-- use as effect dependency
return (
...
);
};