user12856847
user12856847

Reputation:

Unable to pass props in Next

I am making a Server Side Rendering application using Next Js (React SSR).

Index.js (Simply calling another component Layout in index)

import Layout from "./layout";
import React from "react";

class Home extends React.Component {
  render() {
    return (
      <div>
        <Layout />
      </div>
    );
  }
}

export default Home;

Layout.js

import React from "react";
import Product from "./product";

class Layout extends React.Component {
  static async getInitialProps() {
    const res = await fetch("https://api.github.com/repos/developit/preact");
    console.log(res);
    const json = await res.json();
    return { stars: json.stargazers_count };
  }

  componentDidMount() {
    if (localStorage.getItem("userLoggedIn")) {
      //Get products details for logged in user api
      //For now let us consider the same api
      // const res = fetch("https://api.github.com/repos/developit/preact");
      // const json = res.json(); // better use it inside try .. catch
      // return { stars: json.stargazers_count };
    } else {
      // Get product details for guest user api
      //For now let us consider the same api
      // const res = fetch("https://api.github.com/repos/developit/preact");
      // const json = res.json(); 
      // return { stars: json.stargazers_count };
    }
  }

  render() {
    return (
      <div>
        This is layout page
        <Product stars={this.props.stars} />
      </div>
    );
  }
}

export default Layout;

Complete simple application example is here: https://codesandbox.io/s/nextjs-getinitialprops-748d5

My problem here is, I am trying to pass props to product page from layout page and props are received from getInitialProps (SSR).. But you could see in the example provided, the props didn't works and it still gives undefined for this.props.stars .

If I move this code into componentDidMount and using setState and passing state as props would work but that will not display the data fetched from api in view page source.

Note: Please don't move this logic into index.js file where it works but in my real application It is dynamic routing page and I will fetch api as per the query param fetched on that page.

If you get into this link https://748d5.sse.codesandbox.io/ and click ctrl + u then you would see the source where I am also need the dynamic content fetched from api.

To achieve this dynamic content(stars count here in this example) display in view source only I am doing all these which is meant for SEO purpose.

Upvotes: 4

Views: 6044

Answers (1)

Matt Carlotta
Matt Carlotta

Reputation: 19772

In short, you can't call getInitialProps from a child component: getInitialProps caveats.

getInitialProps can not be used in children components, only in the default export of every page

Your Layout page is a child component of Home.

If you want to call getInitialProps, then you'll either need to define getInitialProps from within the Home page OR create a wrapper component that calls its own getInitialProps that will wrap the page's default export PageComponet with this wrapper component: export default withStarsData(PageComponent); as a result, this wrapper component will then pass PageComponent some props.

If you want to make this function flexible/dynamic, then use ctx's query parameter with a dynamic route.

This is a rather complex solution to understand, but in brief, it allows the home (/) and layout (/layout) pages to fetch the same data. If you don't want to fetch the data multiple times, but instead, fetch ONCE and then share between pages, then you'll want to use a higher-order state provider like redux.

Working example:

Edit GetInitialProps Wrapper


components/withStars/index.js

import React from "react";
import fetch from "isomorphic-unfetch";

// 1.) this function accepts a page Component and...
const withStars = WrappedComponent => {

  // 7.) if stars data is present, returns <WrappedComponent {...props} /> with props
  // else if there's an error, returns the error
  // else returns a "Loading..." indicator
  const FetchStarsData = props =>
    props.stars ? (
      <WrappedComponent {...props} />
    ) : props.error ? (
      <div>Fetch error: {props.error} </div>
    ) : (
      <div>Loading...</div>
    );

  // 3.) this first calls the above function's getInitialProps
  FetchStarsData.getInitialProps = async ctx => {

    // 4.) here's where we fetch "stars" data
    let stars;
    let error;
    try {
      const res = await fetch("https://api.github.com/repos/developit/preact");
      const json = await res.json();
      stars = json.stargazers_count;
    } catch (e) {
      error = e.toString();
    }

    // 5.) optionally this will call the <WrappedComponent/> 
    // getInitialProps if it has been defined
    if (WrappedComponent.getInitialProps)
      await WrappedComponent.getInitialProps(ctx);

    // 6.) this returns stars/error data to the FetchStarsData function above
    return { stars, error };
  };

  // 2.) ...returns the result of the "FetchStarsData" function
  return FetchStarsData;
};

export default withStars;

Upvotes: 6

Related Questions