1

I am building a random matching feature for my website. At the end of the startMatching Redux action, if the matching is successful, I want to dispatch another action called startConversation before dispatching MATCHING_SUCCESS. The startConversation action is used to create or update the chat history between 2 users and will dispatch the id of the firestore document that stores the chat history as the payload at the end. Since (1) startConversation is dispatched before MATCHING_SUCCESS and (2) startConversation dispatches the id of the chat history document, I can create a field called chatid in my Redux state to access the id of the document once the matching is successfully done and use it to generate something like a chat window.

I have been testing the code with a case in which matching should be successfully done. The problem I got was that the first dispatch (startConversation) was completed after the second dispatch (MATCHING_SUCCESS) was completed. I knew this because I put console.log in the componentDidUpdate method of the MatchingBox component and at the end of the startConversation action, and the console.log in the former was executed earlier than the latter, which means that MATCHING_SUCCESS was finished dispatching before startConversation which caused a change in Redux state and thus props to the component. Indeed, the buddy and loading fields in the state of the MatchingBox component were updated and the chatid remained as an empty string. I do not want this to happen because as you can see I need to pass chatid further down to the MatchingWindow component.

This makes me confused: After all, isn't Redux synchronous? Also, I tried chaining the 2 dispatches with "then" and startConversation was still finished dispatching later than MATCHING_SUCCESS. I am wondering how I can fix the issue. Thank you so much for your patience!

Redux actions


export const startConversation = (user2id, user2profile, user1profile, message) => (dispatch, getState) => {

  ......

  console.log(chatid);
  dispatch({ type: CHAT_SUCCESS, payload: chatid });

}


export const startMatching = (userid, userprofile, usergender, genderpreference) => (dispatch) => {
    dispatch({ type: MATCHING_REQUEST });

  ...Matching algorithm...

  //Dispatches                      
 firebase.firestore().collection("users").doc(userspool[number].id).get()
     .then((doc) => {
       dispatch(startConversation(userspool[number].id, doc.data(), userprofile, {time: "", from: "", content: ""}));      
       dispatch({ type: MATCHING_SUCCESS, payload: doc.data() });
      })

  if (buddy === "") {
      dispatch({ type: MATCHING_FAIL, payload: [] });
    }

  console.log(buddy);      
}

Redux reducer

const initialState = {

    ......

    loading: false,
    chatid: "",
    buddy: [],
}

const authReducer = (state = initialState, action) => {
    const {type, payload} = action;

    switch(type) {

        ......

        case CHAT_SUCCESS:
            return {
                ...state,
                chatid: action.payload
            }
        case MATCHING_REQUEST:
            return {
                ...state,
                loading: true
            }
        case MATCHING_SUCCESS:
            return {
                ...state,
                buddy: action.payload,
                loading: false
            }
        case MATCHING_FAIL:
            return {
                ...state,
                buddy: action.payload,
                loading: false
            }

        default: return state;
    }
}

MatchingBox component

class MatchingBox extends Component {

    state = {
        show: true,
        loading: false,
        chatid: "",
        buddy: []
    }

    componentDidMount() {
        this.setState({
            loading: this.props.loading,
        })
    }


    componentDidUpdate(prevprops) {
        console.log(this.props.chatid);
        console.log(this.props.buddy.first_name);
        if (prevprops.loading !== this.props.loading) {
            this.setState({
                loading: this.props.loading,
                chatid: this.props.chatid,
                buddy: this.props.buddy
            })
        }
    }



    render() {

        let box;
        if (this.props.loading === true) {
            box = <span>Loading...</span>
        }
        else {

            if (this.props.buddy.length !== 0) {
                box = <div>
                <div className="form-inline">
                    <img src={this.state.buddy.image}></img>
                    <img src={this.props.profile.image}></img>
                </div>
                <MatchingWindow chatid={this.state.chatid} />
                </div>
            }

            else {
                box = <span>Sorry we cannot help you find a study/work buddy currently</span>
            }
        }
        return (
            <Modal show={this.state.show}>
            <Modal.Header closeButton></Modal.Header>
            <Modal.Body>
              {box}
            </Modal.Body>
          </Modal>
        );
    }

}

const mapStateToProps = (state) => {
    return {
        auth: state.firebase.auth,
        loading: state.auth.loading,
        chatid: state.auth.chatid,
        buddy: state.auth.buddy,
        profile: state.firebase.profile
    };
}

export default connect(mapStateToProps)(MatchingBox);
Xi Liu
  • 559
  • 9
  • 18

1 Answers1

0

It looks you are using redux thunk and as the docs says:

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

your action reducer startConversation does some async action right? in that way redux thunk will hold that dispatch to be executed until its resolution, but wont block the following code from being executed.

next line MATCHING_SUCCESS is executed right away, it does not wait for the pending previous one to be solved. Likewise, MATCHING_FAIL is outside from the promise and probably is triggered before all dispatches (with exception MATCHING_REQUEST).

since startConversation is async and you need these dispatches to be executed after its resolution, you should consider calling MATCHING_SUCCESS and MATCHING_FAIL at startConversation.

buzatto
  • 9,704
  • 5
  • 24
  • 33
  • Thanks for your help! I still do not understand why we cannot et MATCHING_SUCCESS wait for startConversation to be solved by using then: firebase.firestore().collection("users").doc(...).get().then((doc) => { dispatch(startConversation(...)); return doc; }).then((doc) => { dispatch({ type: MATCHING_SUCCESS, payload: doc.data() }) }); – Xi Liu May 24 '20 at 00:20