0

In my case here, I want to avoid the component <Checkout/> from re-rendering once I replace the url props.history.replace(props.match.path + '/contact-data' to load the component <ContactData/>.

As it can be seen on useEffect when <Checkout/> loads it gets a query tail on the url, something like this: http://localhost:3000/checkout?cheese=0&salad=0&special=0&veggie=1. Then I redirect to the url, having it like this: http://localhost:3000/checkout/contact-data, which renders <ContactData/> below , having both <Checkout/> & <ContactData/> on the screen.

My question is regarding useEffect. If I add a props.location.search as a dependency of useEffect, useEffect will cause a re-render of <Checkout/>, acting somehow like ComponentDidUpdate every time props.location.search changes.

On this use case, when I replace the url in order to load <ContactData/>, props.location.search will change and <Checkout/> would re-render if I had the dependency added, as explained above.

Now, I solved this behaviour by not adding the dependency to useEffect and still leaving the array of deps empty, forcing useEffect to run just once. Is this a bad practice? Would there be a proper way of doing this without cheating on useEffect?

useEffect(() => {
        const query = new URLSearchParams(props.location.search)
        const ingredients = {};
        for (let param of query.entries()) {
            ingredients[param[0]] = +param[1]
        }
            setNewOrder(ingredients)
    },[props.location.search])

Even using useEffect with an empty array of dependencies I notice that the <Checkout/> still re-renders, but off course, not running the code inside useEffect on this new render cycle.

Would it be possible to really avoid <Checkout/> from re-rendering here?

import React, {useEffect, useState, Fragment, Suspense} from 'react'
import {withRouter, Route} from "react-router-dom";
import CheckoutSummary from "../../components/CheckoutSummary/CheckoutSummary";
const ContactData = React.lazy(() => import("./ContactData/ContactData"))

function Checkout(props) {
    const [newOrder, setNewOrder] = useState(null);
    const returnToOrderCreationHandler = () => {
        props.history.goBack()
    }

    const confirmOrderHandler = () => {
        props.history.replace(props.match.path + '/contact-data')
    }
    useEffect(() => {
        const query = new URLSearchParams(props.location.search)
        const ingredients = {};
        for (let param of query.entries()) {
            ingredients[param[0]] = +param[1]
        }
            setNewOrder(ingredients)
    },[])
    return (
        <Fragment>
            <CheckoutSummary ingredients={newOrder}
                             cancelCheckout={returnToOrderCreationHandler}
                             confirmCheckout={confirmOrderHandler}/>
            <Route path={props.match.path +  '/contact-data'} render={() =>
                <Suspense fallback={<h1>Loading...</h1>}><ContactData/></Suspense>}/>
        </Fragment>
    )
}

export default withRouter(Checkout)
Arp
  • 979
  • 2
  • 13
  • 30
  • Please check this post: https://stackoverflow.com/questions/55840294/how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook/55854902#55854902 This should answer your query in detail – Shubham Khatri May 04 '20 at 15:33
  • @ShubhamKhatri thanks for your comment. Well, I am kind of aware of those examples. If I use a callBack it would still not work, as the component would have to re-render a second time when I redirect and props.location.search would trigger a change inside useCallback. I guess that the only clean way would be to set the dependency ```props.location.search``` inside useEffect and then add a ```if``` statement wrapping the whole block of code inside ```useEffect``` stating: ```if(props.location.search.length > 0) {...get the query and setState}...``` – Arp May 04 '20 at 18:10
  • As this component only gets this query once the order is made in the app, there would be no risk in getting other stuff inside ```props.location.search``` creating some further conflict in this code. What do you think? – Arp May 04 '20 at 18:11
  • Yes, the warning is there to notify the developer in case he/she is making an unknown error, However you must note that you might want to run the effect when search changes to reflect the update in data so think about it before you remove it from the dependency – Shubham Khatri May 04 '20 at 18:23
  • 1
    Yep I see what you are saying. I actually will look for updates in whatever is received in ```props.location.search``` in this case, as no more ```queries``` will be passed to this component before I do not need it anymore. So if were to be a ```class``` based ```component``` I would not use ```componentDidUpdate``` for this ```prop```. I would have this code set in ```componentDidMount``` to run it once and that is it. You see what I am saying? – Arp May 04 '20 at 18:27
  • So I am really aware of the behaviour that I want it to have, regarding the ```state``` updates. My big question here is about syntax, best practices, in a specific case like this one. – Arp May 04 '20 at 18:28
  • In such a case yes, you can disable the warning and allow the hook to execute when you want it to – Shubham Khatri May 04 '20 at 18:29
  • 1
    Oh I see. Its cleaner than running an ```if``` statement to preserve the logic from re-running. I was just worried that the syntax where I would leave the dependency array empty could be seen as a bad practice or something of this sort – Arp May 04 '20 at 18:30

1 Answers1

0

Is the solution below a hack or a good practice in this case? Considering the idea behind the code, regarding the apps desired behaviour. Or could I keep the dependency array empty in useEffect as I do not want it to re-render, and then ignore the eslint error?

 useEffect(() => {
        if(props.location.search.length > 0) {
            const query = new URLSearchParams(props.location.search)
            const ingredients = {};
            for (let param of query.entries()) {
                ingredients[param[0]] = +param[1]
            }
            setNewOrder(ingredients)
        }
    },[props.location.search])

<Checkout/> cycle:

  • 1st Render - newOrder is initialized as null

  • 2nd Render - useEffect is executed - newOrder is set to whatever is received through props.location.search

  • 3rd Render - once executing the function confirmOrderHandler the url changes props.match.path + '/contact-data'. useEffect recognizes a change through its dependency props.location.search, but before executing the code in its block again it checks if there is still a string at props.location.search, which in this case it will not be true, as we have replaced the url, so the logic is not re-executed and state is preserved.

Arp
  • 979
  • 2
  • 13
  • 30