Simon H
Simon H

Reputation: 21005

Redux: trigger async data fetch on React view event

Completely new to React/Redux, and pretty confused about all the different ways to create components etc. I have read the async docs for Redux and managed to get async data loaded as the page loads, but now I want to use a button to trigger data download.

So far I have a components with this in the render function.

<button onClick={() => dispatch(getEntry('dummy'))}> Get some data< /button>

This is reaching my action creator

export function getEntry(apiroute) {
  return {
    type: GET_ENTRY,
    apiroute
  };
}

and redux is passing this to my reducer

export function entry(state = initialState, action) {
  switch (action.type) {
  case 'GET_ENTRY':
    return Object.assign({}, state, {
      fetching : true
    });

I can see in my logs that the state is being changed by the action.

In my actions file I have this code, which I know works if I wire it into to bootstrapping of my app.

export function fetchEntry() {
  return function(dispatch) {
    return window.fetch('/google.json')
      .then(response => response.json())
      .then(json => dispatch(recEntry(json)));    
  }
}

But where and how should I call fetchEntry in the sequence above following the button click?

Upvotes: 2

Views: 4192

Answers (2)

Nathan Hagen
Nathan Hagen

Reputation: 12780

The idiomatic way to do async is to use something like redux-thunk or redux-promise. redux-thunk I think is more common though. You used the thunk pattern in your fetchEntry function, but I don't think idiomatically. From the top:

Your button code looks good.

Your getEntry action creator is a bit of a misnomer. You're using it more as initiateGetEntry, right?

Using redux-thunk, you'd combine the two action creators into a thunk action creator:

export function getEntry() {
  return function(dispatch) {
    dispatch({ type: 'GET_ENTRY_INITIATE' });
    fetch('google.json)
      .then(function(res) { dispatch({ type: 'GET_ENTRY_SUCCESS, payload: res }) }
      .catch(function(res) { dispatch({ type: 'GET_ENTRY_FAIL' });
  } 
}

The reducer would be similar to what you had:

export function entry(state = initialState, action) {
  switch (action.type) {
  case 'GET_ENTRY_INITIATE':
    return Object.assign({}, state, { fetching : true });
  case 'GET_ENTRY_SUCCESS':
    return Object.assign({}, state, action.payload, { fetching: false });
  case 'GET_ENTRY_FAIL':
    return Object.assign({}, state, { fetching: false, error: 'Some error' });
  default:
    return state;
}

Then, your UI code works fine:

<button onClick={() => dispatch(getEntry('dummy'))}> Get some data< /button>

Upvotes: 3

Simon H
Simon H

Reputation: 21005

OK, not sure whether this is the only way, but this worked in my Component

constructor(props) {
    super(props);
    this.getdata = this.getdata.bind(this);
}

getdata(evt) {
    const { dispatch } = this.props;
    dispatch(getEntry('getdata'));
    fetchEntry()(dispatch);
}

render() {
    return (
        <div>
            <button onClick={this.getdata}> Get some data2</button>

Basically I dispatch a message to the store that an update is starting, and then start the update.

Would welcome confirmation that this is an idiomatic pattern.

Upvotes: 0

Related Questions