NEXT JS - How to prevent layout get re-mounted? - reactjs

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.

Related

How to wrap ALL components of react in a HOC?

ReactJS is a great library, However, it misses some features which I found in Vue and Angular. These features can be implemented of course in React, however, they require extra code to be written.
Every react component, or every JSX element I should say has the following properties shared, which are given by React to us to consume:
ref
key
I wanted to add extra props:
renderIf
fallback
These props help in a way I can't describe when it comes to conditional rendering and filtering the views based on the logged-in user permissions and roles (and other conditional rendering use cases, of course).
In react, if we wanted to apply these props to our components, we would use a HOC as follows:
// 🍎 Disclaimer: you don't have to understand any of the code written bellow, the general idea is that this is a HOC.
import React from 'react'
import getVal from './getVal'
export default function EnhancedComponent(OriginalComponent) {
return ({ renderIf: renderIf_ = true, override: override_, fallback: fallback_ = undefined, ...props }) => {
const renderIf = getVal(renderIf_)
const override = getVal(override_)
const fallback = getVal(fallback_)
const consumersComponent = <OriginalComponent {...props} />
let render = fallback
if (renderIf) render = consumersComponent
if (override_ !== undefined) render = override
return render
}
}
Where every time you want to apply these props to your components, you would have to wrap every new component you create with EnhancedComponent as follows:
export default EnhancedComponent(function Sidenav(){
return <div> side nav </div>
})
Now, you can use your Sidenav component within your App component as follows:
import Sidenav from './Sidenav'
export default function App(){
return (
<div>
<Sidenav renderIf={(5 + 5 === 10)}/>
<div>etc</div>
</div>
)
}
This API is great, but it has a drawback, which is, every time you want to apply these cool props (renderIf and fallback) you'll have to repeat these steps:
import Enhanced component to your file.
wrap your export with Enhanced component.
What I am looking for, is a method, or a way to inherit, or to add some props to the original react component class, somehow?
In react class components, I can imagine doing this on the React.Component class which we used to extend from in the past
class Car extends React.Component{
constructor(){}
render(){
return <div>I miss you 🌹</div>
}
}
But in react functional component, how can we do that?
I want to apply these props by default everytime I create a new component, without wrapping my components in a HOC everytime.
Does React have a way to do that? To change its defaults ?

Calling Stitches createStitches for every component in React

I'm using Stitches to write my CSS in a React application, mainly for the theming and variables. I'm calling createStitches in every component file, like so:
ComponentA.tsx
import { createStitches, styled } from "#stitches/react";
const { createTheme } = createStitches({ theme: {...} });
...
export default ComponentA;
ComponentB.tsx
import { createStitches, styled } from "#stitches/react";
const { createTheme } = createStitches({ theme: {...} });
...
export default ComponentB;
And then I'm importing these components and using them in App.tsx:
import ComponentA from "./components/ComponentA";
import ComponentB from "./components/ComponentB";
function App() {
return (
<div className='App'>
<ComponentA />
<ComponentB />
</div>
)
}
I know I can just call the createStitches in the App.tsx file once, but I like keeping my components self-sufficient, as in they should work without any other extra work. Is this a bad approach, or is this something that is fine to do? Does React call the function many times, or just once? Thanks for any help!
You do not need to call createStitches for every component. This function is to create configuration for your application styling. I don't that you are going to have separate configuration for every component.
This will just cause extra work. Just keep the createStitches in a common file and import it from there

Keep state while navigating between component in react-router

Let's say I want to have a reusable react component in my project. I also want that component to have its state under different locations without losing it during component unmount. What is the correct way to deal with this kind of architecture in React? In other words, when the user navigates between these two routes react unmounts the previous component, therefore it loads remote data on every navigation between /user and /groups routes.
I also know that there is something called Redux. I don't see a clear way how to do it using reduct. Do I need two reducers? one for Users and the other for Groups? If so it's quite inconvenient creating a new reducer and write new logic each time when I need to use ReusableComponent.
Here is a similar skeleton to describe what I am trying to do. Any hint would be helpful.
//Router example
<Router>
<Switch>
<Route exact path=”/users” >
<UserComponent>
<ReusableComponent url=”http://apidomain.com/users” />
</UserComponent>
</Route>
<Route exact path=”/groups” >
<GroupComponent>
<ReusableComponent url=”http://apidomain.com/groups” />
</GroupComponent>
</Route>
</Switch>
</Router>
//ReusableComponent Example
<ReusableComponent>
--->use url, that passed from parent component tree(users or groups) to load data and keep in state
<ReusableComponentContext>
<Head />
<Body />
<Footer />
</ReusableComponentContext>
</ReusableComponent>
EDIT
So to describe my problem better is I need to have the same component with two or more parallel state on the different locations without overriding each other. If it's possible
I would use the "React Context" api. The context wrappes your app so if one component updates/ rerenderes the state which is stored inside of the context stayes untouched. To use Context you need three files:
"UserContext" = Example => rename!
Context Component (UserContext)
import { createContext } from "react"
export const UserContext = createContext(initValue)
Parent Component (Provider)
//filename: UserContext.js
//* import React, { useState } from "react"
//* import UserContext from "./UserContext"
const [state, setState] = useState("initState")
//* return(
<UserContext.Provider value={{state, setState}}> //value="props"
<ChildComponent/>
</UserContext.Provider>
Child Component (Consumer)
//*import React, { use Context } from "react";
//*import {UserContext} from "./UserContext"
const data = useContext(UserContext) //here "UserContext"
src: short explenation of usage
Edit: consuming with a custom hook
To avoid one import-statement you can create a custom Hook like this
import React, { use Context } from "react";
import {UserContext} from "./UserContext";
const useUserContext = (()=>{
const {state, setState} = useContext(UserContext)
//use effect if you want to set the context? with the hook...
return[state, setState]
})
in your remounting component
import useUserContext from "./useUserContext"
//rfce{
const {state, setState} = useUserContext()
//}
you can connect ReusableComponent to a piece of your redux store (see connect for more details).
import { connect } from "react-redux";
const ReusableComponent = (props) => {
// some logic before return
return <div>{props.magicProperty}</div>
}
const mapStateToProps = (state) => ({ magicProperty: state.magicProperty });
return connect(mapStateToProps)(ReusableComponent);
So every time you use ReusableComponent in you app, the magicProperty is shared, You can also connect some actions to the component in order to manage that part of state in the classical redux flow.
I think I found the solution. In my case, I had some misunderstanding on what level put context provider tag in the router component tree. So in React, it's very important to put the context provider wrapper in the right location. It holds a dedicated state only for those child components that are wrapped by that context provider.
In my case, I had ReusableComponentContext inside ReusableComponent and that was the wrong approach Because everywhere I used ReusableComponent it had individual context(Therefore individual state). I moved ReusableComponentContext on the top of a couple of components to solve my problem.

createContext hook not working for react components inside JSP

I am rendering my react component inside an existing JSP page using
ReactDOM.render(
React.createElement(MyReactComponents.myReactComponent, {
props
}),
document.querySelector("#id")
);
and the react component is as follows:
import MyStore from "./MyStore";
const MyReactComponent: React.FC<any> = (props: any) => {
const store = useContext(MyStore);
store.myFunction();
---code---
}
and MyStore is as follows:
export class MyStore{
---Code---
}
export default createContext(new MyStore());
But i'm getting this error:
And one more importing thing to notice is that when I'm trying to render this react component on top of another existing react component, i'm not getting any error and everything is working fine.
Can someone please explain me what might be causing the issue?
I'm not sure, but maybe you are misusing the useContext hook?
Whenever you use it inside a component Child, then at least one of its parent component must call the <Context>.Provider, so that it is initialized down the tree.
In your example, you render MyReactComponent using ReactDOM.render: due this, I suppose MyReactComponent is the first component in your tree. If that is the case, when you use useContext inside it, it cannot find any MyStore context.
So, probably, you just need to wrap your MyReactComponent with a context provider.
export class MyStore { ... }
export const MyStoreContext = createContext(new MyStore());
---
ReactDOM.render(
<MyStoreContext.Provider>
<MyReactComponent {...props />
</MyStoreContext.Provider>
, document.querySelector("#id"));
And then, inside MyReactComponent, you can use const store = useContext(MyStoreContext);.

How to detect route changes with react-router v4?

I need to detect if a route change has occurred so that I can change a variable to true.
I've looked through these questions:
1. https://github.com/ReactTraining/react-router/issues/3554
2. How to listen to route changes in react router v4?
3. Detect Route Change with react-router
None of them have worked for me. Is there a clear way to call a function when a route change occurs.
One way is to use the withRouter higher-order component.
Live demo (click the hyperlinks to change routes and view the results in the displayed console)
You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
import {withRouter} from 'react-router-dom';
class App extends Component {
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
console.log('Route change!');
}
}
render() {
return (
<div className="App">
...routes
</div>
);
}
}
export default withRouter(props => <App {...props}/>);
Another example that uses url params:
If you were changing profile routes from /profile/20 to /profile/32
And your route was defined as /profile/:userId
componentDidUpdate(prevProps) {
if (this.props.match.params.userId !== prevProps.match.params.userId) {
console.log('Route change!');
}
}
With React Hooks, it should be as simple as:
useEffect(() => {
const { pathname } = location;
console.log('New path:', pathname);
}, [location.pathname]);
By passing location.pathname in the second array argument, means you are saying to useEffect to only re-run if location.pathname changes.
Live example with code source: https://codesandbox.io/s/detect-route-path-changes-with-react-hooks-dt16i
React Router v5 now detects the route changes automatically thanks to hooks. Here's the example from the team behind it:
import { Switch, useLocation } from 'react-router'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
This example sends a "page view" to Google Analytics (ga) every time the URL changes.
When component is specified as <Route>'s component property, React Router 4 (RR4) passes to it few additional properties: match, location and history.
Then u should use componentDidUpdate lifecycle method to compare location objects before and after update (remember ES object comparison rules). Since location objects are immutable, they will never match. Even if u navigate to the same location.
componentDidUpdate(newProps) {
if (this.props.location !== newProps.location) {
this.handleNavigation();
}
}
withRouter should be used when you need to access these properties within an arbitrary component that is not specified as a component property of any Route. Make sure to wrap your app in <BrowserRouter> since it provides all the necessary API, otherwise these methods will only work in components contained within <BrowserRouter>.
There are cases when user decides to reload the page via navigation buttons instead of dedicated interface in browsers. But comparisons like this:
this.props.location.pathname !== prevProps.location.pathname
will make it impossible.
How about tracking the length of the history object in your application state? The history object provided by react-router increases in length each time a new route is traversed. See image below.
ComponentDidMount and ComponentWillUnMount check:
React use Component-Based Architecture. So, why don't we obey this rule?
You can see DEMO.
Each page must be wrapped by an HOC, this will detect changing of page automatically.
Home
import React from "react";
import { NavLink } from "react-router-dom";
import withBase from "./withBase";
const Home = () => (
<div>
<p>Welcome Home!!!</p>
<NavLink to="/login">Go to login page</NavLink>
</div>
);
export default withBase(Home);
withBase HOC
import React from "react";
export default WrappedComponent =>
class extends React.Component {
componentDidMount() {
this.props.handleChangePage();
}
render() {
return <WrappedComponent />;
}
};

Resources