Reputation: 32823
I have few components i.e. about
, home
, contact
, header
, footer
, search
.
I am using react-router-dom
for routing between all the pages. In App.js
I am loading all the components based on routes however, for footer
and header
I do not need route, they are loaded in all pages. Header
consists of navigation and a search
component. search
component is where the user enter text to search for things. Now this header
with search
component is rendered in all pages because I am using it in App.js
file. Now what I want is when user is searching something, I want the contents of current page to be replaced with search contents. How can I do this?
Here is my App.js
import { BrowserRouter, Route, Switch } from "react-router-dom";
import About from "./about";
import Contact from "./contact";
import Home from "./home";
import Header from "./header";
import Footer from "./footer";
export default function App() {
return (
<BrowserRouter>
<Header />
<div>
<Switch>
<Route path="/" exact={true}>
<Home />
</Route>
<Route path="/home" exact={true}>
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/contact">
<Contact />
</Route>
</Switch>
</div>
<Footer />
</BrowserRouter>
);
}
here is my header
import { Link } from "react-router-dom";
import Search from "./search";
const Header = () => {
return (
<>
<ul>
<li>
<Link to="home">Home</Link>
</li>
<li>
<Link to="about">About</Link>
</li>
<li>
<Link to="contact">Contact</Link>
</li>
</ul>
<Search />
</>
);
};
export default Header;
here is my search component
import { useState } from "react";
const Search = () => {
const apiKey = "test key";
const baseUrl = "https://content.guardianapis.com/";
const [items, setItems] = useState();
const searchNews = async (event) => {
const query = event.target.value;
if (query === "") {
const news = await fetchNews("news", 15, 1);
console.log();
setItems(news.response.results);
return;
}
console.log(query);
const news = await fetchNews(query, 15, 1);
setItems(news.response.results);
};
const fetchNews = async (section, pageSize, page) => {
const url = `${baseUrl}${section}?page-size=${pageSize}&page=${page}&api-key=${apiKey}`;
const news = await fetch(url);
return await news.json();
};
return (
<>
<input
type="text"
name="search"
placeholder="Enter search term"
onChange={searchNews}
/>
</>
);
};
export default Search;
Here is the link to codesandbox https://codesandbox.io/s/muddy-forest-4vbgs?file=/src/search.js:0-970
You can find the rest of the components on that link above. Please help me how can I achieve this scenario?
Upvotes: 1
Views: 61
Reputation: 202658
I would abstract the search fetching logic from the Search
component into a React context that various other components of your app can consume.
SearchContext.js
import { createContext, useEffect, useContext, useState } from "react";
export const SearchContext = createContext({
search: "",
searchResult: [],
setSearch: () => {}
});
export const useSearch = () => useContext(SearchContext);
const SearchProvider = ({ children }) => {
const apiKey = "test key";
const baseUrl = "https://content.guardianapis.com/";
const [search, setSearch] = useState('');
const [items, setItems] = useState([]);
useEffect(() => {
const searchNews = async () => {
if (search.length < 3) {
const news = await fetchNews("news", 15, 1);
console.log();
setItems(news.response.results);
return;
}
console.log(search);
const news = await fetchNews(search, 15, 1);
setItems(news.response.results);
};
const fetchNews = async (section, pageSize, page) => {
const url = `${baseUrl}${section}?page-size=${pageSize}&page=${page}&api-key=${apiKey}`;
const news = await fetch(url);
return await news.json();
};
searchNews();
}, [search]);
return (
<SearchContext.Provider
value={{
search,
searchResult: items,
setSearch: (e) => setSearch(e.target.value)
}}
>
{children}
</SearchContext.Provider>
);
};
export default SearchProvider;
Provide the context in index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import SearchProvider from "./SearchContext";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<SearchProvider>
<App />
</SearchProvider>
</StrictMode>,
rootElement
);
search.js - set the search value
import { useSearch } from "./SearchContext";
const Search = () => {
const { setSearch } = useSearch();
return (
<>
<input
type="text"
name="search"
placeholder="Enter search term"
onChange={setSearch}
/>
</>
);
};
In any of the route components display the search value and/or results.
home.js
import { useSearch } from './SearchContext';
const Home = () => {
const { search } = useSearch();
return (
<>
<h3>Home page</h3>
<div>Search value: {search}</div>
</>
);
};
If you want to add a loading indicator then it's likely the same as you've done previously.
isLoading
state to the context.isLoading
true when fetching andisLoading
false when fetching complete.isLoading
state from the context.SearchProvider:
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const searchNews = async () => {
console.log(search);
setIsLoading(true); // <-- start loading
const news = await fetchNews(search.length ? search : "news", 15, 1);
setItems(news);
setIsLoading(false); // <-- clear loading
};
const fetchNews = async (section, pageSize, page) => {
const url = `${baseUrl}${section}?page-size=${pageSize}&page=${page}&api-key=${apiKey}`;
const news = await fetch(url);
return await news.json();
};
searchNews();
}, [search]);
UI example:
const Home = () => {
const { isLoading, search, searchResult } = useSearch();
return (
<>
<h3>Home page</h3>
{isLoading ? (
<h1>LOADING...</h1>
) : (
<div>
<div>Search value: {search}</div>
{searchResult..map( ..... )}
</div>
)}
</>
);
};
Upvotes: 1