I am dealing with search parameters and redirecting of URLs in a not so pretty way (only way I could come up with). And, because of the first useEffect right under handleSubmit(), there are way too many unnecessary renders of Search component. For instance, when the page is refreshed on the search page, the Search component gets rendered 7 times (5 renders of allImages being empty, 2 renders of allImages filled with fetched images).
So, I am thinking of adding a conditional for Search component to render Search component only when allImages is not empty (when it is filled with fetched images). Let me know if this is doable.
import React from 'react'
import Navbar from './Navbar'
import create from 'zustand'
import ErrorMsg, { useError } from './ErrorMsg'
import { useEffect } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
// Zustand
let store = (set) => ({
input: '',
setInput: (value) => set({ input: value }),
allImages: [],
setAllImages: (images) => set({ allImages: images}),
totalResults: null,
setTotalResults: (num) => set({ totalResults: num}),
})
export const useHeader = create(store)
function Header() {
// global state and search params
let navigate = useNavigate()
const location = useLocation()
const [searchParams] = useSearchParams()
const query = searchParams.get('query')
const page = Number(searchParams.get('page') || 1)
const input = useHeader(state => state.input)
const setInput = useHeader(state => state.setInput)
const allImages = useHeader(state => state.allImages)
const setAllImages = useHeader(state => state.setAllImages)
const setTotalResults = useHeader(state => state.setTotalResults)
const error = useError(state => state.error)
const setError = useError(state => state.setError)
const showError = useError(state => state.showError)
const setShowError = useError(state => state.setShowError)
const setFadeOut = useError(state => state.setFadeOut)
function handleChange(event) {
setInput(event.target.value)
}
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)
} else {
setAllImages([])
setTotalResults(0)
}
} catch(error) {
setError(error)
}
}
const handleSubmit = async (event) => {
event.preventDefault()
navigate(`/search?query=${input}&page=1`)
}
// this useEffect causes Search.js to render too many times
// especially the second conditional need improvement
useEffect(() => {
if (location.pathname === '/search' && allImages.length === 0) {
if (query) {
setInput(query)
}
navigate(`/search?query=${input}&page=${page}`)
fetchImages()
}
// need this to deal with page not refreshing when submitting or changing pages
if (location.pathname === '/search' && allImages.length !== 0) {
fetchImages()
}
// eslint-disable-next-line
}, [searchParams])
// error
useEffect(() => {
if (error) {
setShowError(true)
setTimeout(() => {
setFadeOut(true)
setTimeout(() => {
setShowError(false)
}, 1000)
}, 5000)
}
}, [error, setFadeOut, setShowError])
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
value={input}
/>
</form>
</div>
{showError && <ErrorMsg />}
</div>
)
}
export default Header
import React from 'react'
import Header from '../Header'
import Image from '../Image'
import { useHeader } from '../Header';
import { useSearchParams } from 'react-router-dom';
function Search() {
const [searchParams, setSearchParams] = useSearchParams()
const page = Number(searchParams.get('page') || 1)
const allImages = useHeader(state => state.allImages)
const totalResults = useHeader(state => state.totalResults)
console.log(allImages)
console.log('Search.js rendered')
// pages
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 (
<div>
<Header />
{/* {totalResults === 0 && <p>Nothing Found</p>} */}
<div className='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>
{allImages.length !== 0 && <div className='pagination'>
<button disabled={page === 1} onClick={handlePrev}>
Prev
</button>
<h5 className='pagination--h5'>{page}</h5>
<button disabled={totalResults < 31} onClick={handleNext}>
Next
</button>
</div>}
</div>
)
}
export default Search
useEffecthook as an intentional side-effect. 1useEffecthook call == 1 render cycle. Doing this will give you a more accurate measure when the component is rendered to the DOM. Second, this is completely normal React behavior. Unless you've a measured/audited performance issue you really shouldn't go out of your way much to "optimize" React, it is already optimized to run efficiently enough right out-of-the-box.