How to detect URL changes in UmiJS React Web application? - reactjs

There is BasicLayout.jsx in my application which pass down the props for main components and render the child components inside the layout. I want to perform some logic depends on URL changes inside the useEffect() of BasicLayout.jsx. Is there any efficient way to detect any URL changes with useLocation hook?

In this case that I want to re-render the BasicLayout.tsx, I used useHistoy() hook instead of useLocation() and passed the current URL pathname as dependency to the useEffect().
Here is the code snippet:
import { useHistory } from 'react-router-dom';
let url = useHistory();
useEffect(() => {
...
// perform some logic
...
}, [currentUser, declaration, url.location.pathname]);

Related

React JS page keeps refreshing when using the back button

I have 2 React JS pages (A & B), when I go from A->B and back to A, page A is refreshed every time. I was under the impression that page is not destroyed. All related questions on StackOverflow seems to be about the opposite problem.
The reason the page refreshes is because useEffect() is called when the back button is pressed despite using useState() to prevent this. I even tried replacing 'refresh' with a 'props.id' parameter (that never changes). See code below:
Here's my code to page A:
import { useHistory, useParams } from "react-router-dom";
import React, { useState, useEffect, useRef } from "react";
import { Link } from "react-router-dom";
export default function Test(props) {
const [refresh, setRefresh] = useState(false);
useEffect(() => {
console.log("useEffect called: "+refresh);
setRefresh(true);
},[refresh]);
return (
<>
Hello from Test
<Link to="/test2">Test me</Link>
</>
);
}
I'm using react-router-dom: "^5.1.2", and import { BrowserRouter as Router } from "react-router-dom"; in App.js and specified:
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/test">
<Test id="1"/>
</Route>
<Route exact path="/test2">
<Test2 />
</Route>
.....
Does anyone know how to prevent useEffect() from being triggered when returning to page? The actual page A fetches using a REST call and display a long list of items and I do not want the page to refresh every time the user load page B to view item and then returns to the page.
You need to add a condition to useEffect.
If you only want to setRefresh to true if its false, then do something like:
useEffect(() => {
if(!refresh) setRefresh(true)
}, [refresh])
Since you are starting with const [refresh, setRefresh] = useState(false) and are not changing refresh anywhere else in the component, this will run once everytime the component loads (not renders).
If you want to run this once in the lifetime of the app and not the component, you need to persist the information outside the component, by either lifting the state up to a parent component and persisting the information is something like localstorage/sessionstorage.
You could then extract this information whenever your component loads and set the refresh state variable accordingly.
Let's say you just want to setRefresh to true once. Add this useEffect:
useEffect(() => {
let persistedRefresh
try {
persistedRefresh = !!JSON.parse(window.localstorage.getItem('THE_KEY_TO_REFRESH_VALUE'))
} catch(error) {
persistedRefresh = false
}
setRefresh(persistedRefresh)
}, [])
This useEffect will run whenever the component loads, and update the state variable, triggering the previous useEffect.
We also need to modify the previous useEffect:
useEffect(() => {
if(!refresh) {
setRefresh(true)
window.localstorage.setItem('THE_KEY_TO_REFRESH_VALUE', JSON.stringify(true))
}
}, [refresh])
In this useEffect we are updating the persisted value so that whenever the component loads,
it will check the persisted value,
refresh if needed, and
update the persisted value for the next loads.
This is how you do it without any extra dependencies.
I can see that you're importing the very useful useHistory prop, but not doing much with it. It can actually be used to check if a user is navigating to the page by using the back button. useHistory()'s action properly will tell you everything you need. If the back button was used, action will be "POP". So you can put some logic into your useEffect to check for that:
const history = useHistory();
React.useEffect(() => {
if (history.action === "POP")
console.log("Back button used. Not running stuff");
else console.log("useEffect called in home");
}, []);
Here is a Sanbox. And here you can actually test the sandbox code in a dedicate browser window: https://okqj3.csb.app/
Click the "About" link and then use the back button to go back to "Home", in the console you will see how the Home element's useEffect function catches it.
Solution 1 (Correct way)
Use Stateless components and have a common super state (Redux will be of great assistance), and bind you page/data to common state so even if the state changes, the page will always render the current state creating an illusion of page retaining the state (I used it to run large queries and store progress/result in redux so even if I open another page and come back then also I see query in progress or result).
However I am not really sure what your use case is.
Solution 2 (slightly wrong way)
Use React.memo,You can use it when you don't want to update a component that you think is static
For function Components:
const Mycomponents = React.memo(props => {
return <div>
No updates on this component when rendering, use useEffect to verify too
</div>;
});
You shouldn't be defining any method/functionality/dynamic calculation inside this kind of method just to avoid getting irregular data

ComponentDidMount did not fire after using Redirect in react-router

so i was using redirect in react-router-dom, i have 2 pages, home and create when the form in create is done and has been submitted, it will execute the <Redirect> function, and it works, but the ComponentDidMount was not being fired again, i need to reload the page to make the ComponentDidMount to make it start again, here is my code
this is how i redirect in the Create file :
if(this.state.createNew){
return <Redirect to='/home'/>
}
and this is my ComponentDidMount in my Home file :
componentDidMount() {
console.log("hi")
}
the console print hi only on the initial render, when i redirect back to the page it does not fire again, i tried use setState inside the ComponentDidMount but it still not being re rendered.
when i tried using Link to method, it works, the ComponentDidMount is being fired again, but Link to is not what i want, because it does not automatically redirect like the <Redirect> do
i got an error when i try to use the useHistory function :
React Hook "useHistory" is called in function "simpan" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
here is how i use my useHistory :
function simpan(event){
event.preventDefault()
const email = event.target.elements.email.value
const name = event.target.elements.name.value
const admin = event.target.elements.admin.value
const active = event.target.elements.active.value
const history = useHistory()
console.log(email)
history.push('/home')
}
thanks before, anyhelp will be appriciated
instead of <Redirect /> why don't you use history.push('/home'). this will take you to the new route once state is true and call componentDidMount
how to use history:
import { useHistory } from 'react-router-dom'
then inside your component: const history = useHistory()
then whether you need to change the route:
history.push('/home')
If your create is class component just use this.props.history.push("/home") instead of <Redirect> tag.
As create component is your route it will automatically get history object in props.
why to use history instead of redirect tag in your case
Simple example of programatic navigation with react routing Please check console of browser while checking this example

Should we change class component to functional component when we put state into Redux?

I was learning React and created two class components having respective states. Then, I learned about Redux and decided to transfer states into redux store. The question is "Is it best practice to change class componenents into functional components since we get state via props from redux store?"
Functional components with react hooks is the new standard of coding on React. For store management(f.e. redux) you may use as classes as functional components, but most of the libs moved to functional components and you may not use all benefits of last versions of them.
Why I prefer functional components and hooks over classes:
Cleaner render tree. No wrapper components
More flexible code. You
can use useEffect on different state changes, in classes you have
only componentDidUpdate for ANY state/props change
You can define your custom hooks to keep your code clean and shiny
IMHO, yes, I suggest that you should switch from class-based component to functional component as soon as possible.You might not want to know how the class-based components have bugged me so hurt before I decided to go with Hooks. The number of components in my large project is now over 400 (including both smart and dumb components) and keep increasing. Hooks keep my life easier to continue developing and maintaining.
Have a look at this useful article: https://blog.bitsrc.io/why-we-switched-to-react-hooks-48798c42c7f
Basically, this is how we manage state with class-based:
It can be simplified to half the lines of code, achieving the same results with functional component and useState, useEffect:
Please also take a look at this super useful site: https://usehooks.com/
There are many useful custom hooks from the community that you can utilize. Below are the ones that I have been using all the time:
useRouter: Make your life easier with react-router. For example:
import { useRouter } from "./myCustomHooks";
const ShowMeTheLocation = () => {
const router = useRouter();
return <div>Show me my param: {router.match.params.myDesiredParam}</div>;
}
useEventListener: simplify your event handler without using componentDidMount and componentWillUnmount to subscribe/unsubscribe. For example, I have a button that needs to bind a keypress event:
import { useEventListener } from "./myCustomHooks";
const FunctionButton = () => {
const keydownHandler = event => { // handle some keydown actions };
const keyupHandler = event => { // handle some keyup actions };
// just simple like this
useEventListener("keydown", keydownHandler);
useEventListener("keyup", keyupHandler);
}
useAuth: authenticate your user.
import { useAuth } from "./use-auth.js";
const Navbar = (props) => {
// Get auth state and re-render anytime it changes
const auth = useAuth();
// if user is authenticated, then show user email, else show Login
return <div>{auth.user? auth.user.email: "Login"}</div>;
}
useRequireAuth: handle redirect your user if they are signed out and trying to view a page that should require them to be authenticated. This is composed by useRouter and useAuth above.
import { useRequireAuth } from "./myCustomHooks";
// Dashboard is a page that need authentication to view
const Dashboard = () => {
const isAuth = useRequireAuth();
// If isAuth is null (still fetching data)
// or false (logged out, above hook will redirect)
// then show loading indicator.
if (isAuth) {
return <div>Fetching data, please wait!</div>
}
// {...{ isAuth }} is similar to:
// isAuth={isAuth}
return <Dashboard {...{ isAuth }} />
}
Hope this helps!
First of All, States can be used only in Class Component. In React's latest version there's a huge update that allows functional components to declare and use state using React-Hooks. So, the best practice I would personally suggest you is to use Class Component when you use the Redux Store. As you're a beginner, Please use a functional component where you don't use any state or props and just render DOM elements (Note: Functional components can accept props). Once you learn the differences properly, go with React-Hooks.
I hope it helps!! Happy Coding!!

React - the preferred way to perform an action when leaving page / changing route

I'm writing a react app which uses react-router-dom for routing. For one of the routes I have to perform an action when the user leaves (perform api request & stop the timer).
I can do this either
In the cleanup function returned by useEffect hook inside the component rendered for that route
or
Attach to the router and detect the route changes.
Which way is better? Or perhaps there's some other way?
You can use componentWillUnmount. However, useEffect works just as well!
That is likely the better way to handle this event.
I will try to use react router tools to make effects when your are navigating.
Unmount is kind of side effect of navigation, but maybe could not unmount, or you can unmount that component for other reasons.
if you are using hooks maybe you can use useLocation and store what is the current page and do something like this:
function usePageViewTracker() {
const location = useLocation()
[lastLocation, setLastLocation] = useState('')
useEffect(() => {
if (lastLocation === 'specialLocation') {
// call your API
}
setLastLocation(location)
}, [location])
}

Dispatch action on route change with BrowserRouter

I need to dispatch a Redux action every time there is a route change in my app. Unfortunately, these answers don't apply to my case, since I have to use BrowserRouter instead Router, and BrowserRouter does not take a history prop. Is there another way to do this? I am using V4.
Dennis' comment gave a working solution, using React hooks. Make a new component as follows:
const RouteChange = withRouter(({ location }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch({ type: "AN_ACTION" });
}, [dispatch, location.pathname]);
return null;
});
and just include it in the root of your application. The specified action will be dispatched whenever the route changes.
You can use componentwillunmount in each of your react class and it will work fine

Resources