ctrlplusb / react-universally

A starter kit for universal react applications.
MIT License
1.7k stars 244 forks source link

Succeeding server side requests #545

Closed iwoork closed 6 years ago

iwoork commented 6 years ago

On the redux branch, this works perfectly fine on page load.

export default compose(
  connect(mapStateToProps, mapActionsToProps),
  withJob({
    work: ({ filters, fetchExperiments }) => {
      return fetchExperiments(filters);
    }
  }),
)(Experiments);

However, when I'm trying to make a succeeding request by changing the url parameters and calling

history.push({
  pathname: history.location.pathname,
  search: queryString.stringify(filters)
})

It only calls the API through the client side and of course throws a CORS error. Ideally, the succeeding request should also call the server side and render the response accordingly.

How do I achieve this with react universally?

unleashit commented 6 years ago

I think this is more of a React Router question than RU, but it looks like you need to wrap your export with withRouter (import it from react-router-dom) so history is available on this.props. This is assuming you're using RR v4.

Try:

export default withRouter(
  compose(
    connect(mapStateToProps, mapActionsToProps),
    withJob({
      work: ({ filters, fetchExperiments }) => {
        return fetchExperiments(filters);
      }
    }),
)(Experiments));

Also I'd double check that the search parameter if you pass an object to history.push object is still avail. It may be fine, I can't remember for sure.

iwoork commented 6 years ago

Thanks @unleashit but it didnt seem to do the trick. What happens on the succeeding call is that it looks like it re-renders the page but fails to render the part that is dynamic because the request made was XHR (browser console throws the CORS error) and the API doesn't allow any calls coming from client side.

Ideally, calling the history.push should change the url (e.g changing the page=1 value ) and at the same time calling the API through server side and return the response into the component itself. What works though is if I refresh the page with the new appended value (page=2)

unleashit commented 6 years ago

I think I'd need to see some more code to fully understand what you're trying to do. If the browser throws a CORS error, I would think that's because you're trying to use AJAX on an API from a different origin than the webpage (and that API's server doesn't have a CORS header).

Other than that, I've also had issues at first trying to get RR to refresh a current view, if that's what you're trying to do. If you're trying to update based on a query string, you could use componentWillReceiveProps() test for it, then send off your request again. I haven't used query strings in React, but I have used the "state" property of the history object to pass the previous url and other data. I usually use the id/slug in the url proper rather than query string.

iwoork commented 6 years ago

Here's my complete component:

import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withJob } from 'react-jobs';
import { Route, withRouter } from 'react-router-dom';
import { Grid, Col, Row, Breadcrumb } from 'react-bootstrap';
import Helmet from 'react-helmet';
import ReactPaginate from 'react-paginate';

import TableList from './TableList';
import Filters from '../Filters/Filters';

import * as ExperimentsActions from '../../../actions/experiments';

import '../../../styles/experiments.css';

const queryString = require('query-string');

class Experiments extends React.Component{
  constructor(props){
    super(props);
  }

  render(){
    const title = "Experiments";
    const { experiments, fetchExperiments, history, filters } = this.props;
    return (
     <div>
        <Helmet title={title} />
        <Grid className="experiments">
        <Row>
          <Col sm={12}>
            <Breadcrumb>
                <Breadcrumb.Item href="/">Home</Breadcrumb.Item>
                <Breadcrumb.Item active>Experiments</Breadcrumb.Item>
            </Breadcrumb>
          </Col>
        </Row>
        <Row>
            <Col sm={2}>
              <Filters facets={experiments.data.facets} />
            </Col>
            <Col sm={10}>
              <h1>{title}</h1>
              <TableList experiments={experiments.data.experiments} />
              <div className="text-center">
                <ReactPaginate previousLabel={"previous"}
                  nextLabel={"next"}
                  breakLabel={<a href="">...</a>}
                  pageCount={experiments.data.total / 10}
                  marginPagesDisplayed={2}
                  pageRangeDisplayed={5}
                  initialPage={parseInt(filters.page - 1)}
                  containerClassName={"pagination"}
                  subContainerClassName={"pages pagination"}
                  activeClassName={"active"}
                  onPageChange={
                    (page) => {
                      filters.page = page.selected + 1;
                      history.push({
                        pathname: history.location.pathname,
                        search: queryString.stringify(filters)
                      })
                    }
                  }
                />
              </div>
            </Col>
          </Row>
        </Grid>
      </div>
    );
  }
}

function mapStateToProps(state, ownProps) {
  const { filters, experiments } = state;
  return {
    experiments,
    filters
  };
}

const mapActionsToProps = {
  fetchExperiments: ExperimentsActions.getExperiments,
};

export default withRouter(compose(
  connect(mapStateToProps, mapActionsToProps),
  withJob({
    work: ({ filters, fetchExperiments }) => fetchExperiments(filters),
    shouldWorkAgain: function (prevProps, nextProps, jobStatus) {
      return prevProps.location.search !== nextProps.location.search;
    }
  }),
)(Experiments));
unleashit commented 6 years ago

Still not enough info to help easily, as you're using a 3rd party pagination component and there are other missing pieces. I would either try to get help through them or on Stack Overflow with some more code added (filters, actions). I think this is more of a general React/programming (and maybe React Pagination) issue than anything to do with React Universally.

My last best guess is maybe you shouldn't be adding the history call to the onPageChange callback for the pagination, because it may not be getting called at the right time. I'd remove shouldWorkAgain (react jobs) and do your after initial mount fetchExperiments() calls from componentWillReceiveProps. Then right after that call (assuming it returns a promise, in a .then), push the new location. Personally, I've built my own pagination component and I don't let it handle anything aside from how to render its data (page numbers, next/prev, etc.)

iwoork commented 6 years ago

Thanks @unleashit will check this out then. Closing this since it's most likely not RU issue.