Nextjs: useEffect only once (if I use Link component) - javascript

I use Link component for open pages without reload:
<Link href="/home"><a>Home</a></Link>
<Link href="/page"><a>Page</a></Link>
This in my home:
const loadedRef = useRef(false)
useEffect(() => {
if(!loadedRef.current){
console.log("run")
loadedRef.current = true;
}
}, []);
This work fine for first load.
If I click on page and click on home, useEffect run again!
I want only and only once load useEffect even click another pages and return to home

useRef creates a value for this specific instance of the home component. If the instance unmounts, then that value goes away. If you want to make a global variable that persists even after the component has unmounted, do something like this:
import React, { useEffect } from 'react';
// Deliberately making this *outside* the component
let loaded = false;
const Home = () => {
useEffect(() => {
loaded = true;
}, []);
// ...
}
export default Home;

It occurs because the feature "shallow" of next.js
https://nextjs.org/docs/routing/shallow-routing
The official documentation asks to listen to the variable in the query string that receives the request. Generally, this variable is the name of your page like [slug].js. You can inspect the Network tab (F12) to see what is variable used in the query string also.
The below example inspects the slug variable.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Current URL is '/'
function Page() {
const router = useRouter()
useEffect(() => {
// in my case the page is [slug].jsx
}, [router.query.slug])
}
export default Page

Related

Redirect in an exported function with react-router-dom without passing anything?

I have a very simple logout function that looks like this:
export const logout = () => {
localStorage.removeItem('_id')
localStorage.removeItem('token')
localStorage.removeItem('refresh_token')
return {}
}
I'd like this function to redirect using react-router-dom but I'd also like to avoid passing anything to it. This would allow me to call this function outside the scope of a React Component and would mean I don't have to clutter my application by passing the history object everywhere. I can't use redirect as this function must return an empty object.
In a perfect world I'd be able to import something from react-router-dom at the top of the document that would allow me to programatically redirect from within this function.
If you can import the history object in your react component:
import { useHistory } from 'react-router-dom';
const ReactComponent = () => {
const history = useHistory();
// ... React component to be continued
You can pass it into your function:
// ... React component continued
<button onClick={() => logout(history)}> log out </button>
And push a new route to it:
export const logout = (history) => {
localStorage.removeItem('_id')
localStorage.removeItem('token')
localStorage.removeItem('refresh_token')
history.push('/desiredRoute')
return {}
}
Then if you have a Route component with its path set up as /desiredRoute it should render its children.

Pass object one page to another page using Link in React Js

I need to pass object one page to another page. According to requirement, I need to do it using "Link" tag. this is my code.
1st page is as below
<Link
to={{
pathname: "/2ndPage",
state: { foo: 'bar'}
}}
> Action</Link>
When I click this button, I need to pass object to my second page. this is my second page code that I tried
function 2ndPage(props) {
console.log(props)
return <h1>Hello</h1>;
}
correctly project build successfully. When I click my first page button, page is redirect correctly. But object did not pass. I think this is wrong approach. can u help me to do this.
First of all, start the function name with a number it's just a little unconventional.
The foo value should be located in location.state
Try this
import { useParams} from "react-router-dom";
function TwoNdPage(props){
let { foo } = useParams();
//const {foo} = props.location.state // or you can try this
console.log(foo) // it should return bar
return <h2>About</h2>
}
export default TwoNdPage;
Your state will be on location object location.state. You can use hook for location useLocation inside SecondPage component. Attention location.state can be undefined if you open link directly but it has to be present after click on link.
import React from 'react';
import { useLocation } from 'react-router-dom';
const SecondPage = () => {
const location = useLocation();
return (
<p>{location.state}</p>
);
};

Duplicate componentDidMount logic with useEffect() to load external JavaScript on client side

I’m implementing a rich text editor into a NextJS project. There are no React components for it, and it runs only on the client side, so I have to load the JavaScript and CSS files from an external source and work around SSR. Please don't recommend to use another tool, as that is not an option.
The tool works fine as a class component, but I’d like to port it into a functional component. When I test the functional component, it works occasionally — namely, after I change my file and save (even if it's just adding a space). But as soon as I refresh the page, I lose the editor. I thought it was because the component hadn’t mounted, but now I check for that, yet the issue persists.
I’ve tried various approaches, including Next’s Dynamic import with SSR disabled, but so far only the class method below has worked (the editor works by binding to the <textarea> element):
import React from "react";
import Layout from "../components/Layout";
class Page extends React.Component {
state = { isServer: true };
componentDidMount() {
this.MyEditor = require("../public/static/cool-editor.js");
this.setState({ isServer: false }); // Trigger rerender.
var app = MyEditor("entry"); // Create instance of editr.
}
render(props) {
return (
<Layout>
<textarea id="entry"></textarea>
</Layout>
);
}
}
export default Page;
Last attempt at functional component:
import React, { useEffect } from "react";
import Layout from "../components/Layout";
function hasWindow() {
const [isWindow, setIsWindow] = React.useState(false);
React.useEffect(() => {
setIsWindow(true);
return () => setIsWindow(false);
}, []);
return isWindow;
}
const Editor = () => {
useEffect(() => {
const script = document.createElement("script");
script.src =
"http://localhost:3000/static/article-editor/cool-editor.js";
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);
var app = MyEditor("entry");
return (
<Layout>
<textarea id="entry"></textarea>
</Layout>
);
};
const Page = () => {
const isWindow = hasWindow();
if (isWindow) return <Editor />;
return null;
};
export default Page;
You can use useRef hook in <textarea> tag:
const refContainer = useRef(null);
return <textarea ref={refContainer}>
then useEffect to check if the the element has been mounted.
useEffect(() => {
if (refContainer.current) {
refContainer.current.innerHTML = "ref has been mounted";
console.log("hello");
}
}, []);
Check the code here: https://codesandbox.io/s/modest-dubinsky-7r3oz
Some of the things I could suggest changing:
var app = MyEditor("entry"); is being created on every render. Consider using useRef as a way to keep instance variable: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
In Editor, the MyEditor variable is not defined.
hasWindow includes a useEffect that runs once (with empty dependency array), I don't think it needs the clean up function. To check staying at browser or server, you could simply use const isServer = type of window === 'undefined'
Custom hook should be named with prefix use

React Rendered page is 1 page behind requested page

I have a state in Redux called page and is changed whenever I click a URL to another page. This is to make my website just have one URL. Whenever I request for a new page, it only gets updated on the next click. For example, when I click Login, nothing happens. Then when I click Register, the Login page renders. Then when I click About, the Register page renders. Why is the page rendering 1 page slow? Is there any way to fix this? I want to make a fade animation to the page and then make the new page appear. Is this something to do with having to use nextProps? I've heard about it somewhere but I neither know what it is nor know how to use it.
import React, { Component } from 'react'
// Redux
import { connect } from "react-redux"
// Components
import Login from './routes/Login'
import About from './routes/About'
import Register from './routes/Register'
import Chat from './routes/Chat'
// PropTypes
import PropTypes from 'prop-types'
class Renderer extends Component {
state = {
rendered: <About /> // Default page to render
}
static propTypes = {
page: PropTypes.string.isRequired
}
componentDidUpdate(prevProps) {
const main = document.getElementsByTagName('main')[0] // Where the rendered page should be
if (this.props.page !== prevProps.page) { // If page state changes
main.style.animation = "fadeOut 0.5s forwards" // Fade old page
setTimeout(() => { // After faded
let returned
switch (this.props.page) {
case 'About':
returned = <About />
break
case 'Login':
returned = <Login />
break
case 'Register':
returned = <Register />
break
case 'Chat':
returned = <Chat />
break
default:
returned = <About />
}
this.state.rendered = returned
main.style.animation = "fadeIn 0.5s forwards" // Show new page
}, 500)
}
}
render() {
return this.state.rendered
}
}
const mapStateToProps = state => ({
page: state.page
})
export default connect(mapStateToProps)(Renderer)
Is there a way to render data in the componentDidUpdate() instead of putting it in the state then updating it? Because I think that is the main cause of the problem
I'm guessing that you should call this.setState and not this.state.rendered = returned. Furthermore the animation isn't going to be right this way. I think it works if you call the animation inside the setState callback, which should happen after the component is re-rendered ( see reactjs.org/docs/react-component.html#setstate )

Reach router navigate updates URL but not component

I'm trying to get Reach Router to navigate programmatically from one of my components. The URL is updated as expected however the route is not rendered and if I look at the React developer tools I can see the original component is listed as being displayed.
If I refresh the page once at the new URL then it renders correctly.
How can I get it to render the new route?
A simplified example is shown below and I'm using #reach/router#1.2.1 (it may also be salient that I'm using Redux).
import React from 'react';
import { navigate } from '#reach/router';
const ExampleComponent = props => {
navigate('/a/different/url');
return <div />;
};
export default ExampleComponent;
I was running into the same issue with a <NotFound defualt /> route component.
This would change the URL, but React itself didn't change:
import React from "react";
import { RouteComponentProps, navigate } from "#reach/router";
interface INotFoundProps extends RouteComponentProps {}
export const NotFound: React.FC<INotFoundProps> = props => {
// For that it's worth, neither of these worked
// as I would have expected
if (props.navigate !== undefined) {
props.navigate("/");
}
// ...or...
navigate("/", { replace: true });
return null;
};
This changes the URL and renders the new route as I would expect:
...
export const NotFound: React.FC<INotFoundProps> = props => {
React.useEffect(() => {
navigate("/", { replace: true });
}, []);
return null;
};
Could it be that you use #reach/router in combination with redux-first-history? Because I had the same issue and could solve it with the following configuration of my historyContext:
import { globalHistory } from "#reach/router";
// other imports
const historyContext = createReduxHistoryContext({
// your options...
reachGlobalHistory: globalHistory // <-- this option is the important one that fixed my issue
}
More on this in the README of redux-first-history
The same issue happens to me when I'm just starting to play around with Reach Router. Luckily, found the solution not long after.
Inside Reach Router documentation for navigate, it is stated that:
Navigate returns a promise so you can await it. It resolves after React is completely finished rendering the next screen, even with React Suspense.
Hence, use await navigate() work it for me.
import React, {useEffect} from 'react';
import {useStoreState} from "easy-peasy";
import {useNavigate} from "#reach/router";
export default function Home() {
const {isAuthenticated} = useStoreState(state => state.auth)
const navigate = useNavigate()
useEffect(()=> {
async function navigateToLogin() {
await navigate('login')
}
if (!isAuthenticated) {
navigateToLogin()
}
},[navigate,isAuthenticated])
return <div>Home page</div>
}
Try and use gatsby navigate. It uses reach-router. It solved my problem
import { navigate } from 'gatsby'

Categories

Resources