5

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>
  );
}

3 Answers 3

4

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. 👍

Sign up to request clarification or add additional context in comments.

3 Comments

This seems like the right approach, but I run your code and the console.log inside <Layout> still runs twice (renders twice?) just as my example does?
@TaylorA.Leach I edited my covde, I forgot the remove the old code in the Home component
Ah right so removing the state from the page and passing it down to the HOC prevents the page from re-rendering the entire <Layout> and instead now correctly only renders the <ThirdPartyComponent>. I knew that was the answer I just couldn't quite get there. Thank you!
1

The Layout component re-renders because its children prop changed. First it was <div> ...loading</div> (when ssrDone = false) then <ThirdPartyComponent /> (when ssrDone = true)

1 Comment

But my question was is there a way for me to prevent the unnecessarily re-render since the only thing that will be changing is the ThirdPartyComponent.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.