21

In my component I use this.props.history.push(pathname:.. search:..) to rerender the component and fetch new data form a third party service. When I first call the page it renders. But when I call history push inside the component the URL updates correctly BUT the component doesn't rerender. I read a lot but couldn't get it working. Any ideas?

I'm using react router v4

//index.js

  <Provider store={store}>
    <BrowserRouter>
      <Switch>
        <Route path="/login" component={Login}/>
        <Route path="/" component={Main}/>
      </Switch>
    </BrowserRouter>
  </Provider>


//Main.js
//PropsRoute is used to push props to logs component so I can use them when fetching new data
const PropsRoute = ({ component: Component, ...rest }) => {
  return (
    <Route {...rest} render={props => <Component {...props} />}/>
  );
};

class Main extends Component {
  render() {
    return (
        <div className="app">
          <NavigationBar/>
          <div className="app-body">
            <SideBar/>
            <Switch>
              <PropsRoute path="/logs" component={Log}/> //this component is not rerendering
              <Route path="/reports" component={Reports}/>
              <Route path="/gen" component={Dashboard}/>
              <Redirect from="/" to="/gen"/>
            </Switch>
          </div>
        </div>
    )
  }
}

export default Main;


//inside 'Log' component I call
import React, {Component} from 'react';
import {getSystemLogs} from "../api";
import {Link} from 'react-router-dom';
import _ from "lodash";
import queryString from 'query-string';

let _isMounted;

class Log extends Component {

  constructor(props) {
    super(props);

    //check if query params are defined. If not re render component with query params
    let queryParams = queryString.parse(props.location.search);
    if (!(queryParams.page && queryParams.type && queryParams.pageSize && queryParams.application)) {
      this.props.history.push({
        pathname: '/logs',
        search: `?page=1&pageSize=25&type=3&application=fdce4427fc9b49e0bbde1f9dc090cfb9`
      });
    }

    this.state = {
      logs: {},
      pageCount: 0,
      application: [
        {
          name: 'internal',
          id: '...'
        }
      ],
      types: [
        {
          name: 'Info',
          id: 3
        }
      ],
      paginationPage: queryParams.page - 1,
      request: {
        page: queryParams.page === undefined ? 1 : queryParams.page,
        type: queryParams.type === undefined ? 3 : queryParams.type,
        pageSize: queryParams.pageSize === undefined ? 25 : queryParams.pageSize,
        application: queryParams.application === undefined ? 'fdce4427fc9b49e0bbde1f9dc090cfb9' : queryParams.application      
      }
    };

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

  componentDidMount() {
    _isMounted = true;
    this.getLogs(this.state.request);
  }

  componentWillUnmount() {
    _isMounted = false;
  }

  getLogs(request) {
    getSystemLogs(request)
      .then((response) => {
        if (_isMounted) {
          this.setState({
            logs: response.data.Data,
            pageCount: (response.data.TotalCount / this.state.request.pageSize)
          });
        }
      });
  }

  applyFilter = () => {
    //reset page to 1 when filter changes
    console.log('apply filter');
    this.setState({
      request: {
        ...this.state.request,
        page: 1
      }
    }, () => {
      this.props.history.push({
      pathname: '/logs',
        search: `?page=${this.state.request.page}&pageSize=${this.state.request.pageSize}&type=${this.state.request.type}&application=${this.state.request.application}`
      });
    });
  };

  onInputChange = () => (event) => {
    const {request} = this.state; //create copy of current object
    request[event.target.name] = event.target.value; //update object
    this.setState({request}); //set object to new object
  };

  render() {
    let logs = _.map(this.state.logs, log => {
      return (
          <div className="bg-white rounded shadow mb-2" key={log.id}>
           ...
          </div>
      );
    });

    return (
      <main className="main">
        ...
      </main>
    );
  }
}

export default Log;
shotofcode
  • 447
  • 1
  • 5
  • 12
  • 1
    Can you should your Log component – Shubham Khatri Jul 19 '18 at 08:14
  • I added the log component. the function applyFilter calls history.push. As mentioned, URL gets updated but component is not re-rendering.. I expect a re-render so that constructor is called again and I can get new data. Is that the right approach? Thanks. – shotofcode Jul 19 '18 at 08:29
  • 1
    A re-render is trigger on the query params change, but not a remount so constructor is not called again. If you want to detect a change in query params, check this https://stackoverflow.com/questions/48993247/detect-change-in-query-param-react-router-dom-v4-x-and-re-render-component/48993736#48993736 – Shubham Khatri Jul 19 '18 at 09:33

3 Answers3

11

Reactjs don't re-run the constructor method when just props or state change, he call the constructor when you first call your component.

You should use componentDidUpdate and do your fetch if your nextProps.location.pathname is different than your this.props.location.pathname (react-router location)

Darkilen
  • 439
  • 3
  • 11
7

I had this same issue with a functional component and I solved it using the hook useEffect with the props.location as a dependency.

  import React, { useEffect } from 'react';
  const myComponent = () => {
    useEffect(() => {
      // fetch your data when the props.location changes
    }, [props.location]);
  } 


This will call useEffect every time that props.location changes so you can fetch your data. It acts like a componentDidMountand componentDidUpdate.

0

what about create a container component/provider with getderivedstatefromprops lifecycle method, its more react-look:

class ContainerComp extends Component {
  state = { needRerender: false };

  static getderivedstatefromprops(nextProps, nextState) {
    let queryParams = queryString.parse(nextProps.location.search);

    if (!(queryParams.page && queryParams.type && queryParams.pageSize && queryParams.application)) {
      return { needRefresh: true };
    } else {
      return { needRefresh: false };
    }

  }

  render() {
    return (
      <div>
        {this.state.needRefresh ? <Redirect params={} /> : <Log />}
      </div>
    );
  }
}
dvvtms
  • 524
  • 2
  • 10