I am having issues with rendering a hook that I use to determine if my components are inside the screens view so that I can animate them. I run this hook in _app.tsx but the function does not run when routing to another page. If I put a console.log() in my _app.tsx it always runs but not mu custom hook.
If I refresh the page my hook runs as it should but I have to manually refresh the page in order for it to actually run and animate my animations.
What I have tried:
Is to run my hook in a component that I use on all of my pages it works but it takes a split second for it to load and the animations does not run smoothly. When my custom hook is in _app.tsx it does actually run smoothly when I refresh the page manually. That's why I feel like this is the place to have my hook or am I wrong? Maybe this isn't the most optimal way to do this.
The final goal is to have all may animations run smoothly. I want them to run when the component is in View and I want the animations to run again when I navigate to another page.
My _app.tsx looks like this:
import type { AppProps } from 'next/app'
import { ThemeProvider } from 'styled-components'
import GlobalStyle from '../components/GlobalStyles'
import { useInView } from '../hooks/useInView'
import { theme } from '../theme/theme'
export default function App({ Component, pageProps }: AppProps) {
useInView()
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Component {...pageProps} />
</ThemeProvider>
)
}
My custom hook (useInView()) looks like this:
import { useEffect } from 'react'
export const useInView = () => {
useEffect(() => {
function inView() {
const observer = new IntersectionObserver(
(intersections) => {
intersections.forEach(({ target, isIntersecting }) => {
if (isIntersecting) {
target.classList.add('in-view')
} else {
target.classList.remove('in-view')
}
})
},
{
threshold: 0,
}
)
document.querySelectorAll('.scroll-spy').forEach((element) => {
observer.observe(element)
})
}
inView()
}, [])
}
When you need to tell a hook to re-render when the path changes, implement a useEffect with the pathname as a dependency. Implementation is slightly different depending on if you use Next.js or React.
next.js
Take a look at the usePathname hook. Simply applied as such:
import { usePathname } from 'next/navigation';
Then inside your component you have access to the pathname:
const pathname = usePathname();
Then check when it changes before doing something:
useEffect(() => {
inView();
}, [pathname])
react.js
Take a look at the useLocation hook. It provides the pathname property.
import useLocation from 'react-router-dom'
Then inside your bring the location in:
const location = useLocation();
Then check when it changes before doing something:
useEffect(() => {
inView();
}, [location.pathname])
You'll need to slightly re-adjust where you define your inView() function but this is essentially how you do something when the view changes.
I also created a working sandbox that demonstrates IntersectionObserver observer and how to implement it using React components in case you're struggling with that. It shows an alert when the green box's visibility changes.
Related
`
import './App.css';
import ArrayState from './components/ArrayState';
import FetchApi from './components/FetchAPI/FetchApi';
import Login from './components/Login';
import Useeffect2 from './components/Useeffect2';
import UseEffects from './components/UseEffects';
import React,{useEffect} from 'react'
function App() {
const getUsers = async() => {
console.log("function")
}
useEffect(() => {
console.log("use")
getUsers();
});
return (
// <ArrayState/>
// <Login/>
// <UseEffects/>
// <Useeffect2/>
<FetchApi/>
);
}
export default App;
the function "getUsers" is called once in "UseState" but its runs 2 times. i want to run function once. i have define body of function into useEffect?
add empty array to the useEffect, so that it only make calls on page first load
useEffect(() => {
const getUsers = async() => {
console.log("function")
}
console.log("use")
getUsers();
}, []);
You can pass an empty array as the second argument to the useEffect hook :
useEffect(() => {
// Side Effect
}, []);
In this case, the side effect runs only once after the initial render of the component.
Already answered
You need to provide an dependency array
useEffect(() => {
}, []) // dependency array with props, variable that trigger the side effect
even this will run twice in development due to stritct mode, which helps is catching bugs related to cleanup
but if you want to disable it (not recommended)
// generally in index.js
ReactDOM.render(
<React.StrictMode> // remove this to disable stritct mode
<App />
</React.StrictMode>,
document.getElementById('root')
If you're using React 18 and your app is wrapped in the <StrictMode> tag, then this is expected behavior added on purpose in the hopes to help devs catch bugs relevant to the lifecycle of their components, such as abusing/misusing the useEffect hook.
What the new StrictMode actually does is unmount and then remount every component once it gets rendered.
Resulting in an initial lifecycle that looks like this:
* React mounts the component.
* Layout effects are created.
* Effects are created.
* React simulates unmounting the component.
* Layout effects are destroyed.
* Effects are destroyed.
* React simulates mounting the component with the previous state.
* Layout effects are created.
* Effects are created.
Note that it only behaves this way in dev environment, and not in the production build.
ref: https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors
P.S. you should also add the empty dependency array as mentioned in other comments, otherwise it will also run every time the component re-renders.
I know about the navigate hook but I need to control the route from window object that means it has to be a global function. In vuejs, it was easy like,
window.pushpath = function(path) { router.push("/"+path) }
how can i achieve this kind of behaviour in react? I cant use react hooks in js as it is forbidden to use react hooks without using it inside component.
A trivial solution could be to use a useEffect hook to set the window.pushpath function using the navigate function returned from the useNavigate hook.
Example:
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
...
const navigate = useNavigate();
useEffect(() => {
window.pushpath = function(path, options) {
navigate(path, options);
}
}, []);
This logic can only work so long as there is a router component higher in the ReactTree than the component running this effect, i.e. the Router must be in the parent component or higher.
Any linters may also complain about a missing dependency on the navigate function. It is, AFAIK, a stable reference, but should be ok to add.
useEffect(() => {
window.pushpath = function(path, options) {
navigate(path, options);
}
}, [navigate]);
Now that window.pushpath is updated, it can be called and passed the same arguments as the navigate function.
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Trying next with layout pattern:
https://github.com/zeit/next.js/tree/canary/examples/layout-component
And the problem is that Layout component get remounted on every page change. I need to use layout component as a Container so it'll fetch data from server on every mount. How can I prevent layout to get re-mounted? Or am I missing something there?
This helped me for persistent layouts. The author puts together a function that wraps your page components in your Layout component and then passes that fetch function to your _app.js. This way the _app.js is actually the components that renders the Layout but you get to specify which pages use which layout (in case you have multiple layouts).
So you have the flexibility of having multiple layouts throughout your site but those pages that share the same layout will actually share the same layout component and it will not have to be remounted on navigation.
Here is the link to the full article
Persistent Layout Patterns in Next.js
Here are the important code snippets. A page and then _app.js
// /pages/account-settings/basic-information.js
import SiteLayout from '../../components/SiteLayout'
import AccountSettingsLayout from '../../components/AccountSettingsLayout'
const AccountSettingsBasicInformation = () => (
<div>{/* ... */}</div>
)
AccountSettingsBasicInformation.getLayout = page => (
<SiteLayout>
<AccountSettingsLayout>{page}</AccountSettingsLayout>
</SiteLayout>
)
export default AccountSettingsBasicInformation
// /pages/_app.js
import React from 'react'
import App from 'next/app'
class MyApp extends App {
render() {
const { Component, pageProps, router } = this.props
const getLayout = Component.getLayout || (page => page)
return getLayout(<Component {...pageProps}></Component>)
}
}
export default MyApp
If you put your Layout component inside page component it will be re-remounted on page navigation (page switch).
You can wrap your page component with your Layout component inside _app.js, it should prevent it from re-mounting.
Something like this:
// _app.js
import Layout from '../components/Layout';
class MyApp extends App {
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext);
return {
...appProps,
};
}
render() {
const { Component, pageProps } = this.props;
return (
<Layout>
<Component {...pageProps} />
<Layout />
);
}
}
export default MyApp;
Also, make sure you replace all the to <Link href=""></Link>, notice that only have change the Html tag to link.
I struggled because with this for many days, although I was doing everything else correctly, these <a> tags were the culprit that was causing the _app.js remount on page change
Even though this is the topic Layout being mounted again and again, the root cause of this problem is that you have some data loaded in some child component which is getting fetched again and again.
After some fooling around, I found none of these problem is actually what Next.Js or SWR solves. The question, back to square one, is how to streamline a single copy of data to some child component.
Context
Use context as a example.
Config.js
import { createContext } from 'react'
export default createContext({})
_App.js
import Config from '../Config'
export default function App({ Component, pageProps }) {
return (
<Config.Provider value={{ user: { name: 'John' }}}>
<Component {...pageProps} />
</Config.Provider>
)
}
Avatar.js
import { useContext } from 'react'
import Config from '../Config'
function Avatar() {
const { user } = useContext(Config)
return (
<span>
{user.name}
</span>
)
}
export default Avatar
No matter how you mount and dismount, you won't end up with re-render, as long as the _app doesn't.
Writable
The above example is only dealing with readable. If it's writable, you can try to pass a state into context. setUser will take care the set in consumer.
<Provider value={useState({})} />
const [user, setUser] = useContext(Config)
setUser is "cached" and won't be updated. So we can use this function to reset the user anytime in child consumer.
There're other ways, ex. React Recoil. But more or less you are dealing with a state management system to send a copy (either value or function) to somewhere else without touching other nodes. I'll leave this as an answer, since even we solved Layout issue, this problem won't disappear. And if we solve this problem, we don't need to deal with Layout at all.
I'm trying to use responsive javascript media queries using useMediaQuery however I can't get it to work, I get: -
Error message:
"useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
Playground
https://stackblitz.com/edit/react-ts-5vseqr?file=media-query.ts
I think it's erroring on line 4 of media-query.ts
import { useState, useEffect } from 'react'
const useMediaQuery = (query: string) => {
const [match, setMatch] = useState(false)
useEffect(() => {
const updateMatch = () => setMatch(window.matchMedia(query).matches)
updateMatch()
window.matchMedia(query).addEventListener('change', updateMatch)
return () => {
window.matchMedia(query).removeEventListener('change', updateMatch)
}
}, [query])
return match
}
export default useMediaQuery
What you've done here is writing a custom hook(useMediaQuery). You've done that properly so no issues there. Above code snipped is fine.
The problem is in the index.tsx file when you try to use the above custom hook that you've written. As the error suggests your custom hook is called outside the react component there in line 7 of index.tsx.
You have to move the useMediaQuery call inside the App component. Also currently your App component is a class component which you have to convert to a functional component to use hooks inside it.
here's the adjusted code:
https://stackblitz.com/edit/react-ts-m6rwpd?file=index.tsx
My React Native app has several conditions in which it needs to handle deep links when the user clicks on a link for an invite. My intent was to setup the Link listeners in the main app.js file and when a deep link is detected I need to pass the data into the Splash view to determine several factors before adding the user to the group they are invited to.
I am using React Navigation. I initially tried to use redux by updating the store with the url data but ran into timing issues as the Splash page was loading before redux was updated. So I have attempted to create an HOC wrapper for the Navigator as follows:
HOC Linker:
import * as React from "react";
const Linker = (Component) => {
return ({ children, ...props }) => {
return (
<Component { ...props }>
{ children }
</Component>
);
}
};
export default Linker;
In my App JS file I import the Linker and wrap the main navigator with the HOC component. I then add a prop that is updated when React Native Linking method detects a deep link. It updates state with the parameters.
App JS:
import Navigator from './ui/navigation/navigator';
const LinkerWrapper = Linker(Navigator);
render() {
return (
<LinkerWrapper linking={this.state.url} />
);
}
My Navigator file looks as follows:
Navigator:
import { createSwitchNavigator } from 'react-navigation';
import Splash from '../screens/splash/splash';
import AuthStack from './auth';
import AppStack from './app';
const Navigator = createSwitchNavigator({
Loading: Splash,
Auth: AuthStack,
App: AppStack
});
export default Navigator;
When I click on a deep link the app opens as expected, I parse the url and prep the data object, state is updated and the splash page is re rendered as expected, the props are passed through the HOC, but when the componentWillReceiveProps method is ran in the Splash page I do not see any props from the linking prop.
I have tried many different things. I am really new to react so I know I am probably doing something fundamentally wrong. I am guessing I am missing something with React Navigation. One of the things I tried was to change the screen value to a function passing in the props. I was hoping that would magically make the linking prop appear in the Splash page props. Below was my attempt:
import React from 'react';
import { createSwitchNavigator } from 'react-navigation';
import Splash from '../screens/splash/splash';
import AuthStack from './auth';
import AppStack from './app';
const Navigator = createSwitchNavigator({
Loading: {
screen: props => <Splash { ...props }/>
},
Auth: AuthStack,
App: AppStack
});
export default Navigator;
Unfortunately that didn't work. Any assistance would be appreciated.
Thanks.