【问题标题】:Callback after Redux Thunk function: Uncaught TypeError: Cannot read property 'then' of undefinedRedux Thunk 函数后的回调:未捕获的类型错误:无法读取未定义的属性 'then'
【发布时间】:2017-08-19 19:20:44
【问题描述】:

问题

我在 redux 中有一个异步函数(带有 redux thunk),它在 redux 存储中设置了一个值。它在我的应用程序中多次使用,但是,我希望在使用 redux 函数设置的新值运行 redux 函数后发生不同的事情。似乎是一个很好的回调理由,对吧?所以我尝试这样做,但出现以下错误:

未捕获的类型错误:无法读取未定义的属性 'then'

尝试修复的逻辑

认为原因可能是尽管redux 函数调度了动作,但它实际上并没有返回任何东西。因此,我在 redux 函数中添加了returns,认为该函数会将某些内容返回给调用它的组件,这将解决我收到的错误。运气不好。


问题:

有谁知道我如何异步 redux 函数(带有 redux thunk)运行并完成在 redux 存储中设置新值之后执行功能?


我最近尝试解决的问题

组件

import {fetchUser} from './../actions/index.js';

class MyComponent extends React.Component {

  constructor(props) {
    super(props);
  }

  componentDidMount() {
    // Attempt #1: this.props.fetchUserData()); // originally I attempted just running one function and then the rest without a callback but the new value was not set in redux store before the next function that needed it ran
    // Attempt #2: this.props.fetchUserData().then(function() => {  // I also tried running just a standard .then(function()
    // Attempt #3: (line below) 
    this.props.fetchUserData().then((resultFromReduxFunction) => {
      console.log("fetchUserData result = " + resultFromReduxFunction);

      // do some stuff with the new data
      console.log("this.props.reduxData.userHairColor = " + this.props.reduxData.userHairColor);
      console.log("this.props.reduxData.userHeight = " + this.props.reduxData.userHeight);
    });
  }
}

function mapStateToProps(state) {
    return {
        reduxData: state.user
    };
}

function matchDispatchToProps(dispatch) {
    return bindActionCreators({
      fetchUserData: fetchUserData
    }, dispatch)
}

export default connect(mapStateToProps, matchDispatchToProps)(MyComponent);

动作创建者

export const set = (idToSet, payloadToSet) => {
  return {
    type: 'SET',
    id: idToSet,
    payload: payloadToSet
  }
}

export const fetchUserData = (callbackFunction) => {
  console.log("fetchUserData triggered...");
  return (dispatch, getState) => {
    firebase.auth().onAuthStateChanged(function (user) {
      if (!user) {
        console.log("no user logged in");
        return false // Added in attempt to fix callback 
      } else {
        // fetch some more user data
        firebase.database().ref().child('users').child(user.uid).on('value', function(snapshot) {
          var userHairColor = snapshot.child(userHairColor).val();
          var userHeight = snapshot.child(userHeight).val();
          dispatch(set('userHairColor', userHairColor));
          dispatch(set('userHeight', userHeight));
          return true // Added in attempt to fix callback 
        })
      }
    })
  }
}

Redux 商店

const initialState = {
    userHairColor: "",
    userHeight: ""
}

export default function (state=initialState, action) {
  switch(action.type) {
    case 'SET':
      return {
        ...state,
        [action.id]: action.payload
      }
    default:
      return {
        ...state
      }
  }
}

提前感谢您的帮助

【问题讨论】:

  • dispatch(set('userHairColor', userHairColor)); set 是动作创建者吗?显示它,如果是的话。
  • 这是一个reducer,我想看看action creator。
  • 添加了 SET 动作创建者
  • 一切看起来都正确,您的第一个变体应该可以工作...
  • 在第一个变体中,userHeightuserHairColor 在 redux 函数运行后我的函数还没有任何东西(我猜是因为调用了 redux 函数但异步函数在设置这些值之前需要一些时间,同时我的 componentDidMount 中的其余内容现在已经运行)

标签: javascript reactjs firebase redux redux-thunk


【解决方案1】:

要使用.then(...),您的 thunk 必须返回一个 Promise。 Firebase 的 onAuthStateChanged 似乎返回了一个取消订阅函数,而不是一个 Promise,所以即使你返回它也不允许你在你的动作创建者调用站点链接额外的回调。而且您无法从传递给onAuthStateChanged 的回调中返回,因为此时您处于不同的调用堆栈中(它是异步的)。

您需要做的是将一个回调函数传递给您的操作创建者,它需要从您的数据获取的成功回调中调用该回调函数。

类似这样的:

export const fetchUserData = (callbackFunction) => {
  return (dispatch, getState) => {
    firebase.auth().onAuthStateChanged(function (user) {
      if (!user) {
        console.log("no user logged in");
        // You can pass false to your callback here, so that it knows the auth was unsuccessful
        callbackFunction(false); 
      } else {
        // fetch some more user data
        firebase.database().ref().child('users').child(user.uid).on('value', function(snapshot) {
          var userHairColor = snapshot.child(userHairColor).val();
          var userHeight = snapshot.child(userHeight).val();
          dispatch(set('userHairColor', userHairColor));
          dispatch(set('userHeight', userHeight));
          // Passing true so your callback knows it was a success
          callbackFunction(true); 
        })
      }
    })
  }
}

然后根据需要使用它:

this.props.fetchUserData((success) => {
  console.log("fetchUserData result = " + success);

  // do some stuff with the new data
  console.log("this.props.reduxData.userHairColor = " + this.props.reduxData.userHairColor);
  console.log("this.props.reduxData.userHeight = " + this.props.reduxData.userHeight);
});

这里发生了一些回调地狱,但为避免这种情况,您必须使用 Firebase API 的“承诺”版本,在这种情况下,您还可以从您的 thunk 中返回承诺,以便任何使用您的操作创建者可以附上.then(...)

【讨论】:

  • 感谢@philraj 的帮助。我尝试了这个解决方案,但它返回以下错误:callbackFunction is not a function .. 知道为什么吗?
  • @Rbar 我得看看你是怎么调用fetchUserData的,但听起来你传递给它的不是函数。
  • @Rbar 顺便说一句,尽管我展示了如何做到这一点,但我同意 Martin Kadlec 的回答,即这感觉像是一种反模式。有关更多信息,请参阅他的回答。
  • @philaj 感谢您的帮助,我同意这确实有点像反模式。不过,替代方案似乎是在组件中多次复制此功能,这也不是一个好选择
【解决方案2】:

如果您实际上在 thunk 中显式调用它,回调将起作用,但它确实感觉像是一种反模式。

相反,您可以调度另一个可以处理任何进一步逻辑的 thunk。

export const fetchUserData = (callbackFunction) => {
  console.log("fetchUserData triggered...");
  return (dispatch, getState) => {
    firebase.auth().onAuthStateChanged(function (user) {
      ...do stuf...
      dispatch(doMoreStuff())
    })
  }
}

或者,如果你需要在你的 react 组件中对结果做出反应,请调度一个操作来修改你的 redux 状态。这将反过来调用反应生命周期方法和render,您可以根据新状态对那里的更改做出反应。

为了改进答案,了解您在操作完成后想要做什么会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 2017-04-25
    • 2019-01-29
    • 2016-10-28
    • 2017-09-27
    • 1970-01-01
    • 2018-02-02
    相关资源
    最近更新 更多