Om3ga
Om3ga

Reputation: 32823

Replace search current page contents with search contents

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

Answers (1)

Drew Reese
Drew Reese

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>
    </>
  );
};

Demo

Edit replace-search-current-page-contents-with-search-contents

Update

If you want to add a loading indicator then it's likely the same as you've done previously.

  1. Add an isLoading state to the context.
  2. Set isLoading true when fetching and
  3. Set isLoading false when fetching complete.
  4. Consume the 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

Related Questions