4

I'm having this issue when I'm trying to invoke a callback after the promise resolves (using .then) It turns out that this gives my const request some kind of different promise that reducer returns as undefined:

action:

   export function lookup(company, callback) {
      const id = company.textfield;
      const url = `${ROOT_URL}${id}`;

      const request = axios.get(url)
       .then(() => callback())

      return {
        type: LOOK_UP,
        payload: request,
        meta: id
     };
 }

reducer:

import { LOOK_UP } from '../actions/index';

export default function(state = {}, action) {
    switch (action.type) {
        case LOOK_UP:
            const data = action.payload.data;
            const id = action.meta;

            if (data.Success === true) {
                return { ...state, [id]: data.CompanyInformation };
            } else {
                return state;
            }
    }
    return state;
}

As you can see I pass the data from API that axios get to my reducer. After this I set the state and I need to invoke that callback that is in 'actions' (it creates then another action call inside component). Unfortunatelly, I got error that in reducer const data = action.payload.data is undefined.

When I don't use this callback everything works fine, but the case is that I neet to invoke that callback only after this reducer returns new state.

  • 1
    Any progress? Have you had time to try my suggestions? – jonahe Sep 13 '17 at 08:58
  • 1
    I see now, this time the callback is invoked, but I think that the problem here is that it's fired too soon. I mean, this callback relays on the new state returned from reducer with this LOOK_UP action. I have to make another function that will fire up after this state is changed and not only when the promise is resolved. – Mateusz Szymajda Sep 13 '17 at 10:19
  • 1
    Does the callback really rely on the new state, or just the return from `request = axios.get(url)`? If it's only that the callback need the response as an argument, then you can simply use my first suggestion but with `callback(dataFromRequest);` Or `callback(dataFromRequest.data);` or what ever info you are interested in. You can also include the if-statement. etc. and do `Promise.reject( )` if `dataFromRequest.data.SUCESS !== true` . – jonahe Sep 13 '17 at 10:40
  • 1
    What library are you using for the promises? You have the tag `redux-promise`. Is is this one? https://github.com/acdlite/redux-promise The examples are so few there that it's hard to see how it's supposed to be used. Is it `redux-promise` that adds the `.Success` ? – jonahe Sep 13 '17 at 10:47
  • 1
    Yes, I'm using redux-promise, but the .Success comes form API (this api returns object with data/null and Success true/false). So I think the most reasonable thing to do is to use that callback with dataFromRequest that comes back from request in this case. But I guess for my reducer I still need to return the part with type: LOOK_UP, payload: request etc? – Mateusz Szymajda Sep 13 '17 at 11:05
  • 1
    Yeah, if you want to have the data in the Redux state (maybe because some component needs the data) then you would still need to return the `dataFromRequest`, like in the first example. Not sure what the callback does, so I can't say if the callback itself maybe should be a new action dispatch. – jonahe Sep 13 '17 at 11:10
  • 1
    Exactly, this callback fires another action from component level and I think it should take the dataFromRequest as an argument, I will give this a try. And thank you for an explanation on those promises. – Mateusz Szymajda Sep 13 '17 at 12:02
  • 1
    Ok, yeah you can at least try it. No problem! – jonahe Sep 13 '17 at 12:10
  • 1
    You seem to be pretty new here so I just want to add something. (And please don't read this as a complaint. I'm not mad. Promise!) In the future, it would be good if you could formulate the question so that it is **clear** what the right answer could look like. In this case, your question can be read as: "I get undefined. Why?". Since I did explain why, it could be argued that the question is solved. The fact that the code also has other problems isn't really relevant. (Although new problems can still be the subject of a new question, or discussion in comments) The question is still solved. – jonahe Sep 13 '17 at 12:41
  • It is, of course, ultimately up to you if you think an answer is worthy of being accepted and/or up-voted (or neither), but it isn't fair (to the people responding) if the criteria for a correct answer is changed from "The answer needs to solve A" to "The answer needs to solve A, B and possibly C". I'm not saying that's what you did, but when an answer clearly helps someone and it still doesn't get accepted it can certainly seem that way. – jonahe Sep 13 '17 at 12:41
  • Ok, got this working. As you advised, I used a callback with dataFromRequest as an argument and in callback dispatched another action. Those promises are sometimes confusing, what do you think about using Redux-Thunk instead? And indeed the answer was helpful! – Mateusz Szymajda Sep 13 '17 at 13:11
  • My experience with thunk is that it can be even more confusing. But that's a matter of taste I guess. There's also the option of using ONLY promises (not redux-promise or anything like that). And then you can simply call one action (`LOOK_UP_FETCH`) before you start the API-call, then another action if the call succeds (after a .then().. ) where you send in the response data. Or yet another one if the call fails. It can become messy too (three actions for each api-call), but for most cases it's good enough. – jonahe Sep 13 '17 at 13:16
  • Promises are so widely used that I feel it's very good to really learn how to use them "as is" (only promises, no other library) first, even if you want to use a library later. – jonahe Sep 13 '17 at 13:18

2 Answers2

3

Best way to use another callback in action need to replace you middleware "redux-promise" to "redux-thunk"

import ReduxThunk from 'redux-thunk'
const store = createStore(applyMiddleware(ReduxThunk));

action:

export function lookup(company, callback) {
  const id = company.textfield;
  const url = `${ROOT_URL}${id}`;


  return function(dispatch) { 
        axios.get(url)
       .then((res) => {
            dispatch({ type: LOOK_UP, payload: res,  meta: id });
            callback();
         )
      };
 }

reducers:

import { LOOK_UP } from '../actions/index';

export default function(state = {}, action) {
    switch (action.type) {
        case LOOK_UP:
            const data = action.payload.data;
            const id = action.meta;

            if (data.Success === true) {
                return { ...state, [id]: data.CompanyInformation };
            } else {
                return state;
            }
    }
    return state;

}

Birbal Singh
  • 1,062
  • 7
  • 16
2

request is equals to this whole statement:

const request = axios.get(url)
   .then(() => callback())

So if callback() doesn't return something, then the promise will resolve to nothing. I assume you are fetching some data with this call, so you need to pass that data on, or else your reducer will never get it. Like so:

const request = axios.get(url)
   .then((dataFromRequest) => { 
     callback(); 
     return dataFromRequest; 
});

Or you can separate the two and write something like:

const request = axios.get(url);
request.then(() => callback());

return {
    type: LOOK_UP,
    payload: request,
    meta: id
 };

Compare the promises in the snippet below:

const promisedData = () => Promise.resolve(42);

promisedData()
  .then(data => console.log('Resolved data is ', data));


const promisedTrasformedData = () => 
   Promise.resolve(700)
   .then(data => data - 34);
   
   
promisedTrasformedData()
  .then(transformedData => console.log('Transformed data is ', transformedData));
  
  

const promisedAndLostData = () => 
  Promise.resolve(42)
 .then(data => returnNoting())

function returnNoting() { };

promisedAndLostData()
 .then(lostData => console.log('Lost data: ', lostData))
jonahe
  • 4,820
  • 1
  • 15
  • 19