如何将 useSearchParams Hook 与 React Router v6 一起使用

hah*_*oto 9 query-string reactjs react-router react-hooks urlsearchparams

我正在尝试为我的 React 图像搜索应用程序实现搜索参数功能。而且,我了解到我需要(可以)使用 useSearchParams Hook,但我不确定如何进行这些更改。

所以,基本上我希望 URL 类似于 localhost:3000/ input&page=1,这意味着斜杠后面的任何内容都将是input页码的值和键/值对。

正如您在 app.js 中看到的,我有这 3 个主要路由,而主路由(呈现 Main.js)是我主要处理的路由。此外,Main.js 渲染 Header.js(渲染表单和其他)。

我想我应该在 app.js 中创建一个新的路线,但我不知道该怎么做。

import './App.css';
import Home from './components/pages/Home';
import Favorites from './components/pages/Favorites';
import Error from './components/pages/Error';
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { SkeletonTheme } from 'react-loading-skeleton';
import { useDarkMode } from './components/Navbar';


function App() {
  const darkMode = useDarkMode(state => state.darkMode)
  let style
  if (darkMode === 'light') {
    style = 'wrapper'
  } else {
    style = 'wrapper-dark'
  }

  return (
    <div className={style}>
      <SkeletonTheme baseColor="#808080" highlightColor="#b1b1b1">
        <BrowserRouter>
          <Routes>
            <Route path='/' element={<Home />} />
            <Route path='favorites' element={<Favorites />} />
            <Route path='*' element={<Error />} />
          </Routes>
        </BrowserRouter>
      </SkeletonTheme>
    </div>
  );
}

export default App;
Run Code Online (Sandbox Code Playgroud)
import React from 'react'
import Header from './Header'
import Image from './Image'
import { useState, useEffect, useRef } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'
// import InfiniteScroll from 'react-infinite-scroll-component'


function Main() {
  const [input, setInput] = useState('')
  const [allImages, setAllImages] = useState([])
  // const [totalResults, setTotalResults] = useState(null)
  const [isVisible, setIsVisible] = useState(false)
  const [error, setError] = useState(null)
  const [showError, setShowError] = useState(false)
  const [fadeOut, setFadeOut] = useState(false)
  const [page, setPage] = useState(1)
  const paginationRef = useRef(false)

  // get
  useEffect(() => {
    if (localStorage.getItem('input')) {
      setInput(JSON.parse(localStorage.getItem('input')))
    }

    if (localStorage.getItem('allImages')) {
      setAllImages(JSON.parse(localStorage.getItem('allImages')))
      // setTotalResults(JSON.parse(localStorage.getItem('totalResults')))
      setIsVisible(JSON.parse(localStorage.getItem('isVisible')))
      setPage(JSON.parse(localStorage.getItem('page')))
      paginationRef.current = true
    }
  }, [])

  // set
  //* dryer?
  useEffect(() => {
    localStorage.setItem('input', JSON.stringify(input))
  }, [input])

  useEffect(() => {
    localStorage.setItem('allImages', JSON.stringify(allImages))
  }, [allImages])

  // useEffect(() => {
  //   localStorage.setItem('totalResults', JSON.stringify(totalResults))
  // }, [totalResults])

  useEffect(() => {
    localStorage.setItem('isVisible', JSON.stringify(isVisible))
  }, [isVisible])

  function handleChange(event) {
    setInput(event.target.value)
  }

  // display nothing by default
  // display image-list when user press search button

  // function handleSubmit(event) {
  //   event.preventDefault()
  //   // interpolate input state and .env variable to API
  //   fetch(`https://api.unsplash.com/search/photos?query=${input}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
  //     .then(res => res.json())
  //     .then(data => setAllImages(data.results))
  // }

  async function fetchImages() {
    try {
      const res = await fetch(`https://api.unsplash.com/search/photos?&page=${page}&per_page=30&query=${input}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
      const data = await res.json()
      if (data.total !== 0) {
        setAllImages(data.results)
        // setTotalResults(data.total)
        setIsVisible(true)
      }
    } catch(error) {
      setError(error)
    }
  }

  const handleSubmit = async (event) => {
    event.preventDefault();
    fetchImages()
    setPage(1)
    paginationRef.current = true
  }

  // error
  useEffect(() => {
    if (error) {
      setShowError(true)
      setTimeout(() => {
        setFadeOut(true)
        setTimeout(() => {
          setShowError(false)
        }, 1000)
      }, 5000)
    }
  }, [error])

  // total results
  // let results
  // if (totalResults >= 10000) {
  //   results = 'Total Results: ' + totalResults + '+'
  // } else if (totalResults > 0) {
  //   results = 'Total Results: ' + totalResults
  // } else if (totalResults === 0) {
  //   results = 'Nothing Found'
  // }

  // pagination
  useEffect(() => {
    if (paginationRef.current) {
      fetchImages()
    }
    localStorage.setItem('page', JSON.stringify(page))
  }, [page])

  function handlePrev() {
    setPage(prevState => prevState - 1)
    fetchImages()
  }
  function handleNext() {
    setPage(prevState => prevState + 1)
    fetchImages()
  }


  return (
    <main>
      <Header
        input={input}
        handleChange={handleChange}
        handleSubmit={handleSubmit}
      />

      {showError && <div className={`network-error ${fadeOut ? 'fade-out' : ''}`}>
        <i><FontAwesomeIcon icon={faTriangleExclamation} /></i>
        <div className='network-error--message'>
          <h5>Network Error</h5>
          <p>Please check your Internet connection and try again</p>
        </div>
      </div>}

      {/* <p className='main--results'>{results}</p> */}
      <div className='main--image-list mt-5 pb-5'>
        {allImages.map(el => (
          <Image
            key={el.id}
            // do need spread operator below for img's src to work in Image.js
            {...el}
            el={el}
          />
        ))}
      </div>

      {isVisible && <div className='main--pagination'>
        <button disabled={page === 1} onClick={handlePrev}>
          Prev
        </button>
        <h5 className='main--pagination--h5'>{page}</h5>
        <button onClick={handleNext}>
          Next
        </button>
      </div>}
    </main>
  )
}

export default Main
Run Code Online (Sandbox Code Playgroud)
import React from 'react'
import Navbar from './Navbar'


function Header(props) {
  return (
    <div className='header'>
      <Navbar />
      <h2 className='header--heading text-center text-light'>Find Images</h2>
      <div className='header--form'>
        <form onSubmit={props.handleSubmit}>
          <input
            className='header--form--input'
            autoComplete='off'
            type='text'
            placeholder='Search'
            onChange={props.handleChange}
            name='input'
            value={props.input}
          />
        </form>
      </div>
    </div>
  )
}

export default Header
Run Code Online (Sandbox Code Playgroud)

Dre*_*ese 6

如果您只想将page状态初始化为pagequeryParam,则可以使用以下方法。如果使用useSearchParams来访问 queryString 并返回一个构造URLSearchParams对象,然后该对象可以访问各个查询参数。传递“page”查询参数作为初始page状态值。

const [searchParams] = useSearchParams();

const [page, setPage] = useState(Number(searchParams.get("page")) || 1);
Run Code Online (Sandbox Code Playgroud)

不过,您很可能不希望当前页面的内容存在竞争“事实来源”。如果您希望 URL 查询字符串成为事实来源,则删除状态page并直接读取/更新“page”查询参数。

例子:

function Main() {
  const [searchParams, setSearchParams] = useSearchParams();

  ...
 
  const page = Number(searchParams.get("page"));

  // get
  useEffect(() => {
    ...

    if (localStorage.getItem('allImages')) {
      ...
      setSearchParams(params => {
        params.set("page", JSON.parse(localStorage.getItem('page')) || 1);
        return params;
      });
      ...
    }
  }, []);

  ...

  const handleSubmit = async (event) => {
    event.preventDefault();
    ...
    setSearchParams(params => {
      params.set("page", 1);
      return params;
    });
    ...
  }

  ...

  // pagination
  useEffect(() => {
    if (paginationRef.current) {
      fetchImages();
    }
    localStorage.setItem('page', JSON.stringify(page));
  }, [page])

  function handlePrev() {
    setSearchParams(params => {
      params.set("page", Math.max(1, page - 1));
      return params;
    });
    ...
  }

  function handleNext() {
    setSearchParams(params => {
      params.set("page", page + 1);
      return params;
    });
    ...
  }

  return (
    ...
  );
}
Run Code Online (Sandbox Code Playgroud)