gards
gards

Reputation: 555

React redux separation of concerns

I'm trying to build a simple app to view photos posted from nasa's picture of the day service (https://api.nasa.gov/api.html#apod). Currently watching for keypresses, and then changing the date (and asynchronously the picture) based on the keypress being an arrow left, up, right, or down. These would correspondingly change the date represented by a week or a day (imagine moving across a calendar one square at a time).

What I'm having trouble with is this: I've created an async action creator to fetch the next potential date - however I need to know the current state of the application and the keypress to retrieve the new date. Is there a way to encapsulate this into the action creator? Or should I put the application state where the exported action creator is called in the application so I can keep my action creator unaware of the state of the application? I've tried to do this by binding the keydown function in componentDidMount for the top level Component, but the binding to the application store doesn't seem to reflect the changes that happen in the reducer.

The async logic relying on redux-thunk middleware and q:

// This function needs to know the current state of the application
// I don't seem to be able to pass in a valid representation of the current state
function goGetAPIUrl(date) {
   ...
}

function getAsync(date) {
  return function (dispatch) {
    return goGetAPIUrl(date).then(
      val => dispatch(gotURL(val)),
      error => dispatch(apologize(error))
    );
  };
}

export default function eventuallyGetAsync(event, date) {
  if(event.which == 37...) {
    return getAsync(date);
  } else {
    return {
      type: "NOACTION"
    }
  }
}

Here's the top level binding to the gridAppState, and other stuff that happens at top level that may be relevant that I don't quite understand.

class App extends React.Component {
  componentDidMount() {
    const { gridAppState, actions } = this.props;
    document.addEventListener("keydown", function() {
      actions.eventuallyGetAsync(event, gridAppState.date);
    });
  }
  render() {
    const { gridAppState, actions } = this.props;
    return (
        <GridApp gridAppState={gridAppState} actions={actions} />
    );
  }
}

App.propTypes = {
  actions: PropTypes.object.isRequired,
  gridAppState: PropTypes.object.isRequired
};

function mapStateToProps(state) {
  return {
    gridAppState: state.gridAppState
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(GridActions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

I've validated that the correctly modified date object is getting to the reducer - however the gridAppState seems stuck at my initial date that is loaded.

What is the right way to approach async logic in redux that relies on attaching event handlers and current application state? Is there a right way to do all three?

Upvotes: 1

Views: 537

Answers (1)

Gaston Sanchez
Gaston Sanchez

Reputation: 1211

You should handle the event in your component and call the correct action depending on the key pressed.

So when you dispatch an async action you can do something like

export default function getNextPhoto(currentDate) {
  return (dispatch) => {
    const newDate = calculateNewDate(currentDate);

    dispatch(requestNewPhoto(newDate));

    return photosService.getPhotoOfDate(newDate)
    .then((response) => {
      dispatch(newPhotoReceived(response.photoURL);
    });
  };
}

You should handle the keypress event on the component and just dispatch your action when you know you need to fetch a new photo.

Your App would look like

class App extends React.Component {
  componentDidMount() {
    const { gridAppState, actions } = this.props;
    document.addEventListener("keydown", function() {
      if (event.which == 37) {
        actions.getNextPhoto(gridAppState.date);
      } else if (...) {
        actions.getPrevPhoto(gridAppState.date);
      }

      // etc
    });
  }
}

By the way you re still missing your reducers that update your state in the Redux Store.

Upvotes: 2

Related Questions