React Redux with Router Location, Pagination / Sor

2019-05-19 01:19发布

问题:

I'm writing some generic Todo-List component, with the following features

  • Pagination / Sorting
  • Keep a part of the state within the URL (page, sort)
  • Fetching the Data asynchronously using HTTP Requests (using redux-saga)

Now I'm trying to find a pattern for the "best" sequence of redux actions to get this working without problems.

I want the following behavior:

  • When Todo Component loads: Read URL Query Parameters and use this to fetch the data.

  • When the user clicks sort by name (or next page or filer by xyz) the Query Parameters within the URL should be updated and the next data should be fetched.

What my current code does:

I'm creating the props todos, pagination and sort within react-redux's mapStateToProps:

state => {
    todos: state.venues.page,
    pagination: parsePagination(state.router.location.search),
    sorting: parseSorting(state.router.location.search)
}

Within the component I have lifecycle and methods like:

componentDidMount() {
    // these props come from redux's "mapStateToProps"
    dipatch(executeFetch(this.props.pagination, this.props.sorting));
}

onSortingChange = (sorting) => {
    // this will use connected-react-router's "push"
    // to update the browser's URL
    dispatch(dispatchSetSortingQueryParam(sorting));
}

onPaginationChange = (pagination) => {
    // same as "onSortingChange"
    dispatch(setPaginationQueryParam(sorting));
}

Now the big questionmark:

Where / How should I run executeFetch when sorting or pagination changes?

  • componentDidUpdate ? This looked promising, but: it creates an endless loop, because each executeFetch will trigger a component update, which then will run componentDidUpdate again and again. I could check here if pagination and / or sorting has changed, and then run executeFetch, but I get a new object instance for those two props every time any prop changes, which requires then a deep equality check. Not nice.

  • adding executeFetch at the end of each onSortingChange and onPaginationChange. This seems to work, but feels a bit redundant. Another problem is, that I can't use this.props.sorting and this.props.pagination to run executeFetch, because those props will aren't updated yet.

How do you solve this within your applications?

回答1:

I think this is the scenario you're describing:

The loop requires that you do equality checking to only update the redux state under certain conditions. This is a problem created by having a component that re-renders from both the url state and redux state. Separating that logic into two components should work:

In practice that might look like:

let App = () => (
  <Router>
    <>
      <Route component={class extends Component {
        componentDidUpdate() {
          let { pagination, sorting } = parse(window.location.search)
          dipatch(executeFetch(pagination, sorting));
        }
        render() { return null }
      }} />
      <Route path="/todo_list" component={props => (
        <div>
          {/* change url via link */}
          <Link to={...}>Change Page</Link>
          {/* change url programatically */}
          <input
            onChange={e => props.history.push(...)}
          />
        </div>
      )} />
    </>
  </Router>
)

Where the todolist is not dispatching actions to modify the url, but just using <Link /> components or history.push to add the pagination and sorting information.