I'm trying to make a custom cursor component in react, based on this article: https://dev.to/andrewchmr/awesome-animated-cursor-with-react-hooks-5ec3
I'm using react router dom, and the problem is that the hover events are only working on the content within the router components on initial page load, or after refreshing the pages.
The hover is however always working on the nav component links. I've set up a basic CodeSandbox where you can see how the hover works on the nav links, but not on the page content links (after initial page load and when navigating between pages).
https://codesandbox.io/s/dazzling-newton-u9hk5
I'm a beginner with react, so I'm sure I'm going about this the wrong away. Can someone please help?
The issue here is that when the route changes, your Cursor Component does not need to be updated, therefore the useEffect hook is not called. And that's why your listeners are not attached to new anchor tags that appear when you change the route.
However, React Router Dom has introduced a new hook called useLocation that you can use to respond to the route change. This hook returns current url, so when you pass it to the array as below, useEffect will be called everytime when the url changes, therefore it will attach your listeners to the recently rendered anchor tags
Add this to your Cursor component:
import { useLocation } from "react-router-dom";
// then in your functional component
const location = useLocation();
useEffect(() => {
addEventListeners();
handleLinkHoverEvents();
return () => removeEventListeners();
// eslint-disable-next-line
}, [location]);
Related
I'm working on an app where the app is wrapped in App.js which changes the page based on a button click, using the following hook:
const [page, setPage] = useState(Accounts);
This setPage is sent as a parameter to Sidebar.js which triggers it when an element in sidebar is clicked. Every page follows this structure:
const ErrorsPage = () => {. However, when I try to use useState inside these pages, i get the following error: Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.. I suspect it's because the page is inside useState and is calling another useState which break the Hook rules. I was wondering how I can fix this, because I need to call useState in my lower pages.
I have tried setting up the useState in parent components(App.js) and keep passing it down to sidebar and then down to the page itself, but it seems like a lot of code.
I want to update the state of my application when a router component is rendered. For example, if I go home "/" I will render homepage, but I want to trigger a state change so that other components like my footer and header are aware we are on the home page vs internal page. Currently, I am parsing the url manually when site is loaded to detect this, however, as the site grows this is unfeasible. I have also seen onEnter as a solution, however, this is not available for reach router.
<Router basepath={process.env.PUBLIC_URL} className="app" >
<LandingPageBody path="/" default/>
//update redux state to show we are on homepage
</Router>
You can use componentDidMount in LandingPageBody component. It is a react lifecycle method it will be triggered when component will mount. You can update state from inside of this method.
If you are using functional components, make use of useEffect function with dependency on props.match.location. Check for condition in that and dispatch action if the condition is true.
(React.useEffect(() => { if(props.match.location === 'desired path') dispatch(action) }, [props.match.location]))
This way when ever your route component loads, it will check for route on every route change and let your redux store know about the current path.
For Stateful component, you can do the same in componentDidUpdate lifecycle.
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
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
I have a app which has the Following Components
|__Base - /home/Base
|__Try - /home/Base/:try
|__Report - /home/Base/:try/report
Base is the Starting screen where the user hits a button and clicks on Try and after trying some things he hits submits which generates reports which has some back end interactions and when the data is fetched it loads the Reports.
So what i want is when the user hits the back button from the Reports Page he should not land on the Try page but on the Base page .
For that to work i went through the react router documentation and was trying to use history.replace on componentWillUnmount for Reports Page
this.props.history.replace(`/home/Base`, {
pathname: `/home/Base`,
search: null,
state: {
isActive: true
}
}, null);
In case the Report Page is FullyLoaded and i press the back button it works but calls the Try Render Method too and then takes me to the Base Page , But in case of Reports Not fully Loaded and i press the back button while the loading spinner is in progress it goes to base page still but also mounts and unmounts the TRY component.
What am i missing here , what causes it to mount/unmount or render the previous component and then load the base component even though i replace the history stack ?
Reason
Related with this issue
React v16, changing routes, componentWillMount of the new route is called before componentWillUnmount of the old route
Update:
Solution (checked, update online demo later)
Use react-router-last-location to get previous pathname
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom';
import { LastLocationProvider } from 'react-router-last-location';
<BrowserRouter>
<LastLocationProvider>
<Switch>
...
</Switch>
</LastLocationProvider>
</BrowserRouter>
Check previous pathname in componentWillMount, if it's from certain page, push a new pathname to route.
componentWillMount() {
const { history, lastLocation } = this.props;
if (lastLocation?.pathname === '/home/Base/:try/report') {
history.push({pathname: '/home/Base'});
}
}
You can use the HOC they provide or write it yourself refer to the lib's source to reduce the dependencies
import { withLastLocation } from 'react-router-last-location';
interface Props {
lastLocation: any,
history: any,
}
export const YourComponent = withLastLocation(connect(
...
))
In this way you can redirect all the routing process from certain pages without mount current page, no matter you clicked a back button or clicked the back in your browser.