React UseEffect with History Location Key - reactjs

I am using useEffect to render a modal if a query string shows up that contains "newcc"
useEffect(() => {
if (qs === "newcc") {
setShowPaymentModal(true)
}
}, [qs]);
Any time I click on a button for a previous modal that directs to the account page with newCC if I am already on that page it will not rerender that modal (makes sense that useEffect isn't called again):
<Button text="Update Payment Info"
onClick={() => {
history.push({
pathname: '/account',
search: 'alert=newcc',
})
}}
/>
A workaround I have found is to simply include the following in the dependency array:
history.location.key
This forces a re-render since the key changes every push. The question I have is that this brings up the lint error of:
React Hook useEffect has an unnecessary dependency:
'history.location.key'. Either exclude it or remove the dependency
array. Outer scope values like 'history.location.key' aren't valid
dependencies because mutating them doesn't re-render the component
Is it safe to simply ignore this lint error or am I approaching this situation the wrong way? It does re-render the component. I hate to ignore lint errors if I don't have to.

It sounds like the issue is that your trying to capture too much logic in one place and it's simpler to manage this by separating things out.
state should contain
isModalOpen: Boolean
&& the render of a component should contain the logic check for pathname.
in render / functional component's return
{qs === "newcc" ? return newCC : null}
{...rest of component}

Related

React useState reruns the component function on no change

in this very simple demo
import { useState } from 'react';
function App() {
const [check, setCheck] = useState(false);
console.log('App component Init');
return (
<div>
<h2>Let's get started! </h2>
<button
onClick={() => {
setCheck(true);
}}
>
ClickMe
</button>
</div>
);
}
export default App;
i get one log on app init,
upon the first click (state changes from false to true) i get another log as expected.
But on the second click i also get a log , although the state remains the same.(interstingly the ReactDevTools doesn't produce the highlight effect around the component as when it is rerendered)
For every following clicks no log is displayed.
Why is this extra log happening.
Here is a stackblitz demo:
https://stackblitz.com/edit/react-ts-wvaymj?file=index.tsx
Thanks in advance
Given
i get one log on app init,
upon the first click (state changes from false to true) i get another
log as expected.
But on the second click i also get a log , although the state remains
the same.(interstingly the ReactDevTools doesn't produce the highlight
effect around the component as when it is rerendered)
For every following clicks no log is displayed.
And your question being
Why is this extra log happening?
Check the useState Bailing Out of a State Update section (emphasis mine):
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects.
(React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again
before bailing out. That shouldn’t be a concern because React won’t
unnecessarily go “deeper” into the tree. If you’re doing expensive
calculations while rendering, you can optimize them with useMemo.
The answer to your question is essentially, "It's just the way React and the useState hook work." I'm guessing the second additional render is to check that no children components need to be updated, and once confirmed, all further state updates of the same value are ignored.
If you console log check you can see...
The first click you will get check is true -> check state change from false (init) => true (click) => state change => view change => log expected.
The second click => check state is true (from frist click) => true => state not change => view not render.
So. you can try
setCheck(!check);

React unnecessary render of element

I've just started learning React and was putting together a small app which makes calls to a quotes API. The API has an endpoint that returns a random quote. When the app initially loads it makes a call to the API and shows a quote, and there's a button that can be clicked to get a new random quote (new call to the API).
I have a root component named App. This component has a QuoteWrap component as a child. The QuoteWrap component has two children: the button that is used to get a new random quote and a Quote component which shows the author of the quote and the quote itself. This is the code inside of the QuoteWrap component:
export default function QuoteWrap() {
const { quoteData, isLoading, fireNewCall } = useQuote();
const handleClick = () => {
fireNewCall();
};
return(
<>
<button onClick={handleClick}>Get random quote</button>
{ isLoading ?
<h2>Loading...</h2>
:
<Quote author={quoteData.author} quote={quoteData.quote} />
}
</>
);
}
useQuote() is a custom hook that manages the calls to the API and returns 3 values: 1- the data, 2- if a call is in process and 3- a function to make a call to the API.
Obviously, every time the button is clicked, the whole QuoteWrap component is re-rendered (as quoteData and isLoading change). But really, the button doesn't need to be re-rendered as it never changes.
So I thought: ok, I can move the button up to the App component. But then I don't have access to the fireNewCall function in the useQuote hook.
How can I prevent the button from being re-rendered? Is it even important in this case or am I getting too obsessed with React re-renders?
Thanks!
Your component will re-render every time the handleClick function changes, which is every time that QuoteWrap is rendered.
The solution is the useCallback hook. useCallback will return the same function to handleClick, every time QuoteWrap is rendered, so long as the dependencies haven't changed.
https://reactjs.org/docs/hooks-reference.html#usecallback
You would use it like this:
const handleClick = useCallback(() => {
fireNewCall();
},[fireNewCall]);
fireNewCall is the dependency, so as long as useQuote returns a stable fireNewCall function, then your button will not re-render, since the handleClick property hasn't changed.
I think you might get too obsessed with React re-renders. The button should be re-rendered because, handleClick should be changed when fireNewCall changed for some case. Even if, handleClick will never be changed. It's no need to think about an element re-render.
Pretty much what Benjamin and Viet said - in your original code, a new function is assigned to handleClick on each render. You can use React.useCallback to maintain the original function reference and only update it when something in the dependency array changes - in this case, just fireNewCall needs to go into the dependency array.
But as Viet says, don't get too obsessed with it. Using React.useCallback might even slow down your code. Check out Kent C. Dodds When to useMemo and useCallback post for more insight.

Trying to automatically run a method without having the user click the button

{!this.props.account ? (
<button onClick={this.props.onSignIn}>Sign In</button>
) : (
// Otherwise show the homepage
Replacing the onClick with a way to just have the this.props.onSignIn happen just once.
The user should not have to click on a button to log in, but once the page loads, it should just check if the user is authenticated, and if not then run the onSignIn method just once
Any thoughts?
componentDidMount is the lifecycle hook to run code once after mounting:
componentDidMount() {
if (!this.props.account) {
this.props.onSignIn()
}
}
If you are using Hooks, useEffect is what you are looking for.
useEffect(() => {
if(!this.props.account) {
this.props.onSignIn();
}
}, [])
Notice the second argument of useEffect, it accepts a set of dependencies, upon the changes on those dependencies, the useEffect hook will run again.
Since we are passing nothing, (an empty array), the hook will run only once when the component mounts.
Read more about hooks: https://reactjs.org/docs/hooks-effect.html

React.useEffect: Component re-renders with window.location.pathname in dependency array despite warning from ESLint(react-hooks/exhaustive-deps)

I have a left-side menu that has sub-options which need to be selected/unselected based on the current URL path. In the process of finding a solution for this, I found that the following code gives the ESLint error as shown below:
useEffect(() => {
if (window.location.pathname === "/events/list") {
setManageEventsOpen(true);
setSelectedListItem(LIST_SELECTED);
}
if (window.location.pathname === "/events/map") {
setManageEventsOpen(true);
setSelectedListItem(MAP_SELECTED);
}
if (window.location.pathname === "/events/calendar") {
setManageEventsOpen(true);
setSelectedListItem(CALENDAR_SELECTED);
}
}, [window.location.pathname]);
React Hook useEffect has an unnecessary dependency: 'window.location.pathname'.
Either exclude it or remove the dependency array.
Outer scope values like 'window.location.pathname' aren't valid
dependencies because mutating them doesn't re-render
the component.eslint(react-hooks/exhaustive-deps)
However, when I run this code, I notice that the component DOES re-render when the path changes. I thought this was just because the whole app is reloading when manually changing the URL and refreshing the page, but even using history.push('/events/list') from another component will cause a re-render.
I'm wondering, is this warning incorrect? Anyone see different results?
Note: I've got a better solution for this in my app, but I'm still curious about the warning.
The fact that your app re-renders when the path changes doesn't mean that the warning is incorrect. It means that your useEffect logic is relying on unrelated logic, which is confusing and prone to bugs.
Instead, you should explicitly listen/subscribe to changes to location.pathname - so that your component re-renders with the updated value. Given that you use React Router, check out their useLocation hook:
const location = useLocation();
useEffect(() => {
if(location.pathname === "/events/list") {
...

Is there a way to know if a specific prop was updated without using state in React?

I'm using React, Redux, and some antd components
I'm using this modal to alert users of errors, but if you look at it, its not exactly a component, its a function, so right now I'm using componentDidUpdate like this:
componentDidUpdate () {
if (this.props.states.ErrorReducer.displayError) {
error(() => {
this.props.dispatch(ErrorActionCreators.acceptError())
}, this.props.states.ErrorReducer.errorMessage )
}
}
Problem is, if I make multiple changes to state at once, for example make multiple calls to the API and they alter the state at different times, this modal open multiple times.
I could use state to do something like
if (this.state.displayError !== this.props.displayError {
updateState();
error();
}
But I'm avoiding using React state.
Is there anyway I can check if one specific prop was changed on the Component?
You can use the lifecycle method, componentWillReceiveProps. This gets called every time props are updated. Here's a link for help componentWillreceiveProps and a code snippet:
componentWillReceiveProps(nextProps){
if(this.props !== nextProps){
// your code and conditions go here
}
}
You can actually compare the old props (this.props) with the new or updated props (nextProps).

Resources