Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gatsby.js: Navigating with URL Parameters and the Browser Back / Forward Buttons

I have an Articles component which displays a list of posts. The list is paginated so that a maximum of 10 posts can display per page. There is a "Next Page" button that when clicked will update the component state and the corresponding url parameter like so:

page 1: /news

page 2: /news?page=2

page 3: /news?page=3

...and so on. The way the constructor() and render() methods are set up, it will read this URL parameter and display the correct posts if the user navigates directly to /news?page=3, for instance.

The issue I'm having is that the browser back and forward buttons don't seem to rerender the page. So if a user hits the "Next Page" button a few times and then hits the back button, the URL will update, but the page won't rerender. Is there a way to force it to do so?

I'm guessing there's a way to accomplish this by adding a window.history listener, but I wasn't sure if there was a recommended practice to go along with gatsby-link.

Here is a stripped down version of the component for reference:

import React, { Component } from 'react';
import { navigateTo } from 'gatsby-link';
import getUrlParameter from '../functions/getUrlParameter';

export default class extends Component {
  constructor(props) {
    super(props);

    /* external function, will grab value
    * of ?page= url parameter if it exists */
    const urlParamPage = getUrlParameter('page');
    const currentPage = urlParamPage ? urlParamPage : 1;

    this.state = {
      currentPage
    };
  }

  nextPage() {
    const { props, state } = this;

    const urlParam = state.currentPage > 1
      ? `?page=${state.currentPage}`
      : '';

    navigateTo(props.pathname + urlParam);
    this.setState({currentPage: this.state.currentPage + 1});
  }

  render() {
    const { props, state } = this;

    const articles = props.articles
      .slice(state.currentPage * 10, state.currentPage * 10 + 10);

    return (
      <div>
        <ul>
          {articles.map((article) => <li>{article.title}</li>)}
        </ul>

        <button onClick={() => this.nextPage()}>Next Page</button>
      </div>
    );
  }
}
like image 705
dougmacklin Avatar asked Feb 04 '23 01:02

dougmacklin


1 Answers

Your page doesn't rerender because it is actually the same page - the component is already mounted. As you are fetching your data in constructor(), your page won't update because the constructor for a React component is called before it is mounted (source).

What you call urlParam are just a new prop that componentWillReceiveProps(nextProps) should receive in nextProps.location.search.

Edit:

You have to lift state up because only the root component will receive props.location on browser back and forward buttons. The pathname prop of your Articles component nerver changes, that is why componentWillReceiveProps never fires here.

The code:

/src/pages/root.js

import React, { Component } from 'react';
import { navigateTo } from 'gatsby-link';

import Articles from '../components/Articles';

export default class Test extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentPage: 1,
      data: {}, // your ext data
    };

    this.nextPage = this.nextPage.bind(this);
  }

  nextPage() {
    const { currentPage } = this.state;
    const { pathname } = this.props.location;
    const url = `${pathname}?page=${currentPage + 1}`;

    this.setState({ currentPage: currentPage + 1 });
    navigateTo(url);
  }

  componentWillReceiveProps(nextProps) {
    const { pathname, search } = nextProps.location;
    const getParam = /(\d+)(?!.*\d)/;

    const currentPage = search !== '' ? Number(search.match(getParam)[0]) : 1;

    /* get your ext. data here */
    const data = {};

    this.setState({ currentPage, data });
  }

  render() {
    const { currentPage, data } = this.state;
    return (
      <div>
        {/* other contents here */}
        <Articles
          nextPage={this.nextPage}
          currentPage={currentPage}
          data={data}
        />
      </div>
    );
  }
}

/src/components/Articles.js

import React from 'react';

const Articles = ({ nextPage, currentPage }) => {
  return (
    <div>
      <div>Page: {currentPage}</div>
      <button onClick={() => nextPage()}>Next Page</button>
    </div>
  );
};

export default Articles;
like image 158
Nenu Avatar answered Feb 07 '23 03:02

Nenu