Search code examples
reactjsreduxflux

Where to put chained actions in React/Redux


Fairly simple use case: I have actions/events that will cause an ajax request to be executed and then update a list.

The problem is I'm not sure how (specifically, where to kick off a request for a new list when the page is changed.

redux store

const defaultStore = {
  list: [],
  page: 1    
};

Wrapper Component

const wrapper = props => (
  <div>
    <List {...props}> {/* This list should have page controls */}
      <PageControls {...props} />
    </List>
    <List /> {/* This list should not have page controls */}
  </div>
);

List component

const List = props => (
  <div>
    {props.children} {/* render page controls if present */}
    {props.items.map((item, k) => <div key={k}>item</div>
  </div>
);

Pager Controls component

const PageControls = props => (
  <div>
    <span onClick={props.changePage(-1)}>Backward</span>
    <span onClick={props.changePage(1)}>Forward</span>
  </div>
);

actionCreator

function changePage(delta) {
  return {
    type: 'CHANGE_PAGE',
    delta
  };
}

// utilizes react-thunk middleware
function getList(page = 1) {
  return dispatch => 
    axios.get(`/path/to/api?page=${page}`)
      .then(res => dispatch(updateList(res.data));
}

function updateList(newList) {
  return { 
    type: 'UPDATE_LIST',
    newList
  };
}

reducer

function reducer(state = {}, action) {
  switch(action.type) {
    case 'CHANGE_PAGE':
      return {...state, page: state.page + action.delta};
    case 'UPDATE_LIST':
      return {...state, list: action.newList};
    default:
      return state;
  }
}

At this point I could do a couple of things -- I could make every actionCreator that should trigger a list update dispatch that action:

function changePage(delta) {
  return dispatch => {
    dispatch({
      type: 'CHANGE_PAGE',
      delta
    });

    return dispatch(getList(store.getState() + delta));
  }
}

But this seems messy. Now not only do I have to get my store but I also have to turn every actionCreator that affects the list into a thunk.

The only other thing I can think of is to have my <List> component use store.subscribe somewhere to watch for changes to page and then kick off another getList action, but this also seems like I'm moving the understanding of what does and does not trigger state changes out of Redux and into my React components.

Any ideas?


Solution

  • Well, maybe you should change your approach. I don't see a reason to make two actions for changing page and retrieving the list. You can just dispatch getPage() action on button click, passing next page number. This should retrieve list of items and refresh your page.

    In your store you should keep track on current page, so each time page refreshes the value of getPage() argument will also update.

    For example (assuming that current page is not retrieved from API):

    function getPage(page = 1) {
      return dispatch => 
        axios.get(`/path/to/api?page=${page}`)
          .then(res => dispatch(updatePage(res.data, page));
    }
    
    function updatePage(newList, currentPage) {
      return { 
        type: 'UPDATE_PAGE',
        newList,
        currentPage,
      };
    }
    

    and connect required components to the store, in your case it would be List and PageControls components.

    const PageControls = props => (
      <div>
        <span onClick={props.getPage(props.currentPage - 1)}>Backward</span>
        <span onClick={props.getPage(props.currentPage + 1)}>Forward</span>
      </div>
    );
    

    This will allow you to maintain simple and clean code. Also you can trigger it from multiple, not related components.