-1

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
3
  • What exactly is the question? Is there an issue with the code you've shared? Commented Feb 15, 2023 at 9:59
  • Here, I have uploaded some screenshots to imgur. In the console, you can see how many times the search component and allImages state is being rendered. The app works as intended but it has too many unnecessary renders, so the question is how can you improve on its optimization? imgur.com/a/3ncRBUV Commented Feb 15, 2023 at 12:05
  • 1
    First, try not console logging as an unintentional side-effect. You should log in the useEffect hook as an intentional side-effect. 1 useEffect hook 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. Commented Feb 16, 2023 at 9:33

1 Answer 1

0

Finally figured it out and all I had to do was to improve the fetchImages function and simplify the useEffect.

import React from 'react'
import Navbar from './Navbar'
import create from 'zustand'
import ErrorMsg, { useError } from './ErrorMsg'
import { useEffect, useRef } from 'react'
import { 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, and some others
  let navigate = useNavigate()
  const inputRef = useRef(null)
  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 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)
  }

  const handleSubmit = async (event) => {
    event.preventDefault()
    navigate(`/search?query=${input}&page=1`)
  }

  let realShit
  if (input === '') {
    realShit = query
  } else {
    realShit = input
  }

  useEffect(() => {
    async function fetchImages() {
      try {
        const res = await fetch(`https://api.unsplash.com/search/photos?&page=${page}&per_page=30&query=${realShit}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
        const data = await res.json()
        if (data.total === 0) {
          setTotalResults(0)
        } else {
          setAllImages(data.results)
          setTotalResults(data.total)
        }
      } catch(error) {
        setError(error)
      }
    }
    fetchImages()
    // eslint-disable-next-line
  }, [searchParams])

  // input
  useEffect(() => {
    inputRef.current.focus()
  }, [])

  // 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}
            ref={inputRef}
          />
        </form>
      </div>

      {showError && <ErrorMsg />}
    </div>
  )
}

export default Header

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.