How to render a component with props derivative from NextJS router - reactjs

I'm trying to render a component that uses a dynamic router path prop. I want mysite.com/something to load the component with the something prop. If the route is mysite.com/somethingelse, I want to load the component with the somethingelse prop. Here's my code:
page.js:
import { useRouter } from "next/router";
import List from "./List";
function DefaultPage() {
const router = useRouter();
console.log(router.query.category); // Works correctly
return (
<div>
<List category={router.query.category} />
</div>
);
}
export default DefaultPage;
The component, list.js:
import React, { Component } from "react";
class List extends Component {
constructor(props) {
super(props);
console.log(this.props.category); // This is where I'm confused
}
static defaultProps = { category: "default" };
render() {
return <p>Hello</p>;
}
}
export default List;
The problem is, this.props.category always returns as default (my default prop), unless I recompile. It works perfectly after a fresh compile, but then breaks after every subsequent refresh in the browser.
I can visually see the router query returning the correct value in the log, but the component is rendering before everything else, thus returning a default value. Is there a way I can stop the List component from rendering before its own props are specified? Or is there a better way of doing this all together? Thanks.

I would do something like this in the DefaultPage component:
if(router.query.category === 'something') {
return <ListComponent/>
}
if(router.query.category === 'somethingElse') {
return <SomethingElseComponent/>
}
If you don't want to use two separate components, you could pass the prop to useEffect so it can re-render the component when that prop changes https://reactjs.org/docs/hooks-effect.html

Related

How to include the Match object into a Reacts component class?

I am using react router v5, and trying to get URL parameters by props match object into my react component class. However it is not working! What am I doing wrong here?
When I create my component as a JavaScript function it all works fine, but when I try to create my component as a JavaScript class it doesn't work.
Perhaps I am doing something wrong? How do I pass the Match object in to my class component and then use that to set my component's state?
here code:
import React, { Component } from 'react';
import { Link} from "react-router-dom";
export class JobDetail extends Component {
state = {
// jobsData: [],
}
async componentDidMount() {
const props = this.props.match;
console.log("---props data---",props); // it's showing undefined
}
render() {
return (
<>
test message
</>
)
}
}
export default JobDetail

Destructuring this.props into Component

In this sample code below: It is React and Next.js
import App, { Container} from "next/app";
import React from "react";
class MyApp extends App{
render() {
const { Component } = this.props;
return (
<Container>
<p>Hey I am on every page</p>
<Component/>
</Container>
)
}
}
export default MyApp;
My question is about this line:
const { Component } = this.props;
My questions:
How is this working? This is the highest page level I have. So who is passing this.props to it?
Also what kind of syntax is that? Why it called Components? Could he call it something else?
Your MyApp component extends a component named App, which is an internal Next react component. The reason you'd want to extend App is if you're calling getInitialProps before each page is loaded. You can think of App as a HOC (higher order component) that calls getInitialProps in MyApp first, then other Next methods within a page, like: getInitialProps, getServerSideProps, getStaticProps, getStaticPaths second.
This creates a render-blocking scenario where MyApp.getInitialProps can block page-level methods from being executed until it has been resolved.
In your case, you don't even need to extend App since you're not calling getInitialProps, and instead, you can just use a pure function (or remove _app.js completely if you don't need to include metadata or wrap each page with some sort of layout component):
function MyApp({ Component, pageProps }) {
return (
<Component {...pageProps} />
);
}
export default MyApp;
All subsequent pages (like /pages/example.js) will be passed to this MyApp component as the named Component prop while any props returned from one the methods mentioned above will be passed to MyApp as the named pageProps prop.
You can kind of think of App (next/app) like this:
import React, { Component } from "react";
import MyApp from "../_app.js"; // custom _app page
import Example from "../example.js"; // a page component
class App extends Component {
static async getInitialProps(ctx) {
const { pageProps } = await MyApp.getInitialProps(ctx);
return { pageProps }; // this.props.pageProps
};
render() {
return <MyApp Component={Example} pageProps={this.props.pageProps} />
}
}
--
On a side note, the Container component has been deprecated since v9.0.4
The Component prop is the active page, so whenever you navigate between routes, Component will change to the new page. Therefore, any props you send to Component will be received by the page.

Reactjs redux store changes but in component's props doesn't change

I'm tryin to show navigation depends on changes of categoryURL from
redux store and changing the state in other components. Redux changes the store and it works fine. But in my
component "this.props.categoryUrl" doesn't reflect on value. Can't
find out why?
import React, {Component} from 'react';
import NavigationItems from './NavigationItems/NavigationItems';
import classes from './Navigation.module.css';
import {connect} from 'react-redux';
const mapStateToProps = state => {
console.log(state)
return {
categoryURL: state.categoryUrl
};
};
class navigation extends Component {
componentDidMount() {
console.log(this.props.categoryUrl);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('NAVIGATION!', this.props.categoryUrl);
}
render() {
let nav = null;
if (this.props.categoryUrl) {
nav = (
<div className={classes.Navigation}>
<NavigationItems/>
</div>
)
}
return (
<>
{nav}
</>
)
}
}
export default connect(mapStateToProps, null)(navigation);
In "normal" React it is needed to use <Navigation/> (Capital letter at the beginning) instead of <navigation/>. Also, If <Navigation/> is being used by another React component then it might be needed to add code that will be executed inside <Navigation/> to refresh the component where you are using <Navigation/> (some kind of callback passed to <Navigation/>). It is this the way or move all the <Navigation/>'s code to the component where you are using <Navigation/>. This is how I solved this kind of problem.

How to prevent parent component from re-rendering with React (next.js) SSR two-pass rendering?

So I have a SSR app using Next.js. I am using a 3rd party component that utilizes WEB API so it needs to be loaded on the client and not the server. I am doing this with 'two-pass' rendering which I read about here: https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57
I'm trying to figure out why when 'ssrDone' state changes in the next.js page state the entire <Layout> component unnecessarily re-renders which includes the page's Header, Footer, etc.
I've read about React.memo() as well as leveraging shouldComponentUpdate() but I can't seem to prevent it from re-rendering the <Layout> component.
My console.log message for the <Layout> fires twice but the <ThirdPartyComponent> console message fires once as expected. Is this an issue or is React smart enough to not actually update the DOM so I shouldn't even worry about this. It seems silly to have it re-render my page header and footer for no reason.
In the console, the output is:
Layout rendered
Layout rendered
3rd party component rendered
index.js (next.js page)
import React from "react";
import Layout from "../components/Layout";
import ThirdPartyComponent from "../components/ThirdPartyComponent";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
ssrDone: false
};
}
componentDidMount() {
this.setState({ ssrDone: true });
}
render() {
return (
<Layout>
{this.state.ssrDone ? <ThirdPartyComponent /> : <div> ...loading</div>}
</Layout>
);
}
}
export default Home;
ThirdPartyComponent.jsx
import React from "react";
export default function ThirdPartyComponent() {
console.log("3rd party component rendered");
return <div>3rd Party Component</div>;
}
Layout.jsx
import React from "react";
export default function Layout({ children }) {
return (
<div>
{console.log("Layout rendered")}
NavBar here
<div>Header</div>
{children}
<div>Footer</div>
</div>
);
}
What you could do, is define a new <ClientSideOnlyRenderer /> component, that would look like this:
const ClientSideOnlyRenderer = memo(function ClientSideOnlyRenderer({
initialSsrDone = false,
renderDone,
renderLoading,
}) {
const [ssrDone, setSsrDone] = useState(initialSsrDone);
useEffect(
function afterMount() {
setSsrDone(true);
},
[],
);
if (!ssrDone) {
return renderLoading();
}
return renderDone();
});
And you could use it like this:
class Home extends React.Component {
static async getInitialProps({ req }) {
return {
isServer: !!req,
};
};
renderDone() {
return (
<ThirdPartyComponent />
);
}
renderLoading() {
return (<div>Loading...</div>);
}
render() {
const { isServer } = this.props;
return (
<Layout>
<ClientSideOnlyRenderer
initialSsrDone={!isServer}
renderDone={this.renderDone}
renderLoading={this.renderLoading}
/>
</Layout>
);
}
}
This way, only the ClientSideOnlyRenderer component gets re-rendered after initial mount. 👍
The Layout component re-renders because its children prop changed. First it was <div> ...loading</div> (when ssrDone = false) then <ThirdPartyComponent /> (when ssrDone = true)
I had a similar issue recently, what you can do is to use redux to store the state that is causing the re-render of the component.
Then with useSelector and shallowEqual you can use it and change its value without having to re-render the component.
Here is an example
import styles from "./HamburgerButton.module.css";
import { useSelector, shallowEqual } from "react-redux";
const selectLayouts = (state) => state.allLayouts.layouts[1];
export default function HamburgerButton({ toggleNav }) {
let state = useSelector(selectLayouts, shallowEqual);
let navIsActive = state.active;
console.log("navIsActive", navIsActive); // true or false
const getBtnStyle = () => {
if (navIsActive) return styles["hamBtn-active"];
else return styles["hamBtn"];
};
return (
<div
id={styles["hamBtn"]}
className={getBtnStyle()}
onClick={toggleNav}
>
<div className={styles["stick"]}></div>
</div>
);
}
This is an animated button component that toggles a sidebar, all wrapped inside a header component (parent)
Before i was storing the sidebar state in the header, and on its change all the header has to re-render causing problems in the button animation.
Instead i needed all my header, the button state and the sidebar to stay persistent during the navigation, and to be able to interact with them without any re-render.
I guess now the state is not in the component anymore but "above" it, so next doesn't start a re-render. (i can be wrong about this part but it looks like it)
Note that toggleNav is defined in header and passed as prop because i needed to use it in other components as well. Here is what it looks like:
const toggleNav = () => {
dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action
I'm using an id and fn because all my layouts are stored inside an array in redux, but you can use any logic or solution for this part.

Page does not reload when changing query params through 'next/router' Router.push()

I am using nextjs for my application. On updating query parameters of a page, the page should get re-rendered. For some reason that is not happning. Some online article suggests that we should get the new query props in componentWillReceiveProps, but that too isn't happening. Need help.
/----- modules/my-component.js ------/
import { Component } from 'react'
import Router from 'next/router'
class MyComponent extends Component {
someEvent = () => {
Router.push({
pathname: '/logs',
query: { keyword: 'blah' }
})
}
render(){
return (
<div onClick={this.someEvent}>Click Me</div>
)
}
}
export default MyComponent
/----- pages/logs.js ------/
import { Component } from 'react'
import { withRouter } from 'next/router'
import MyComponent from '~/modules/my-component'
import Router from 'next/router'
class Logs extends Component {
componentWillUpdate(props){
//does not get printed
console.log("================componentWillUpdate", this.props.router.query)
}
componentWillReceiveProps(props){
//does not get printed
console.log("================componentWillReceiveProps", this.props.router.query)
}
render(){
return (
<div>
<div>My Logs page</div>
<MyComponent />
</div>
)
}
}
export default withRouter(Logs)
first of all I see something strange with your someEvent = keyword => { function. Is that the actual code, or an example you created for SO?
Because if you call it like this: <div onClick={this.someEvent}>Click Me</div>, then keyword will actually be an event, not a keyword.
It also seems like you aren't referencing props anywhere in your JSX for the Logs component. You should try to reference it.
Third, what is the call to withRouter(Logs)? That isn't standard. By default in Next.js you should have the component exported by default.
Are you using a custom router? If so, then it's likely not going to work with functions in 'next/router'. Next.js which comes with its own router.
Try to export the component by default and to make sure that keyword is an actual keyword, not an event.

Resources