0

I created a component that contains the "New Article" form. The user can add a new article after clicking the Save button. The click event calls this.props.fetchAddPaper(data), which saves the article to the database. If the response is 200, I would like to display information on the page for the user that the article has been successfully saved. If the response is 500 or 400 or 401, I would like to display information that 'something went wrong try again'. To display alerts I use react-toasts. My question is: how can I get a response from the API after clicking the Save button so that you can display a success or error alert? How do I get a response from this.props.fetchAddPaper (data) in the handleSubmit method that I am calling? Below is the fetchAddPaper that connects to the API. How do I get a response from such a method in a component?

const apiMiddleware = ({ dispatch }) => next => action => {
    next(action);
    if (action.type !== 'API')
        return;

    let {
        url,                // Endpoint address, relative to $HOST/api/
        method,             // http method (GET, POST, DELETE etc.)
        params,             // URI string params
        data,               // Post data
        onSuccess,          // Function accepting response. If redux action is returned, it will be dispatched
        onFinish,           // Function run on either success or error
        onError,            // Function accepting error
        onValidationError,  // Function accepting response with validation error
        text,               // Loading text. If not provided there will be no overlay while loading data
        successText         // Success text, shown on green bar. If not provided it won't be shown
    } = action.payload;

    // Allow for onSuccess, onFinish and onError to be either redux (and thunk) actions or normal functions
    const conditionalDispatch = (action) => 
        action && _.isFunction(action) ? dispatch(action) : action;

    const request = {
        headers: {
            'Accept': 'application/json'
        },
        url: `${host}/api/${url}`,
        method,
        timeout: 180000
    };

    if (params) {
        params = { ...params };
        for (let prop in params) {
            if (Array.isArray(params[prop])) {
                const arrayData = arrayToGetParameters(params[prop], prop);
                delete params[prop];
                Object.assign(params, arrayData);
            }
        }
    }

    if (data) {
        if (method.toUpperCase() === "GET" || method.toUpperCase() === "DELETE") {
            throw new Error("Can't add request data to get or delete method");
        }
        request.headers['Content-Type'] = 'application/json;text/plain;text/json';
    } 

    request.data = data;
    request.params = params;

    text && dispatch(onLoadingStart(text));
    let notificationId = shortId.generate();
    axios.request(request)
        .then((response) => {
            text && dispatch(onLoadingEnd());
            onSuccess && dispatch(onSuccess(response.data));
            onFinish && conditionalDispatch(onFinish);
            if (successText) {
                dispatch(onAddFlashMessage({type: 'success', text: successText, id: notificationId}));
                setTimeout(() => { 
                    dispatch(onDeleteFlashMessage(notificationId));
                }, 5000); 
            }
        })
        .catch((error) => {
            onFinish && conditionalDispatch(onFinish);
            // onError && conditionalDispatch(onError(error));
            onError && dispatch(onError(error));
            dispatch(onLoadingEnd());
            if (error.response && error.response.status === 401) {
                //dispatch(onLogOut()); todo: wylogowanie
                return;
            }
            if (error.response && error.response.status === 422 && onValidationError) {
                conditionalDispatch(onValidationError(error));
            }
            else {
                dispatch(onAddFlashMessage({...httpReqErrorHandler(error), id: notificationId}));
            }
            setTimeout(() => { 
                dispatch(onDeleteFlashMessage(notificationId));
            }, 5000); 
        });

};

export const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.ON_FETCH_ADD_PAPER:
            return {
                ...state,
                paper: action.response
            };            
        default: 
            return state;
    }

const onFetchAddPaper = (response) => ({ type: actionTypes.ON_FETCH_ADD_PAPER, response });

export const fetchAddPaper = (data) => {
    return (dispatch) => {
        dispatch({
            type: 'API',
            payload: {
                url: 'Papers/addPaper',
                method: 'POST',
                headers: {
                    'Content-Type': 'multipart/form-data',
                    'Accept': 'application/json',
                },
                data: data,
                onSuccess: (response) => onFetchAddPaper(response),
                onError: (error) => onFetchAddPaper(error)
            }
        });
    };
};



 handleSubmit(e) {
            e.preventDefault();
            let data = {
                title: this.state.title,
                header: this.state.header       
            }       
            this.props.fetchAddPaper(data); 
         console.log(this.props.paper); 
//when the user first clicks the save button, the response is empty, but the second time the response has a value 200
        }

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

function mapDispatchToProps(dispatch) {
    return {
        fetchAddPaper: data => dispatch(fetchAddPaper(data))
    }
}

//initialstore.jsx
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import apiMiddleware from './ApiMiddleware';
import rootReducers from '../RootReducers';

export default function initStore() {

    const store = createStore(
        rootReducers, 
        compose(
            applyMiddleware(thunk, consoleMessages, apiMiddleware),
            window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f
        )
    );

    if (module.hot) {

        module.hot.accept('../RootReducers', () => {
            const nextRootReducer = require('../RootReducers').default;
            store.replaceReducer(nextRootReducer);
        });
    }
    return store;
}
meissaa
  • 1
  • 2
  • 1
    Who handles the API action? I think you are looking for a more generic solution to manage side effects with redux, there is more than one way to do it: https://goshakkk.name/redux-side-effect-approaches/ – Ponpon32 Aug 03 '19 at 20:03

1 Answers1

0

You can return a promise from your fetchAddPaper action Something like this:

export const fetchAddPaper = (data) => {
    return (dispatch) => {
        return new Promise((resolve,reject) => {
          dispatch({
            type: 'API',
            payload: {
                url: 'Papers/addPaper',
                method: 'POST',
                headers: {
                    'Content-Type': 'multipart/form-data',
                    'Accept': 'application/json',
                },
                data: data,
                onSuccess: (response) => {
                     onFetchAddPaper(response);
                     resolve(response); //return your promise on success
                },
                onError: (error) => {
                    onFetchAddPaper(error);
                    reject(error); //return your promise on failure
                }
            }
        });
        })
    };
};

So, whenever your action executes, it'll be a promise which you can then evaluate like -

this.props.fetchAddPaper(data).then(response => {..do something})      
Aseem Upadhyay
  • 4,279
  • 3
  • 16
  • 36
  • thanks for response but displays Error: Actions must be plain objects. Use custom middleware for async actions. – meissaa Aug 04 '19 at 20:09
  • then that's the problem with your middleware. do you have redux-thunk integrated? – Aseem Upadhyay Aug 05 '19 at 10:23
  • yes, I integrated redux-thunk in file initialStore.jsx. I added above code initialStore.jsx – meissaa Aug 05 '19 at 12:20
  • 1
    Did you try searching? This is a pretty common problem, https://stackoverflow.com/questions/46765896/react-redux-actions-must-be-plain-objects-use-custom-middleware-for-async-acti – Aseem Upadhyay Aug 05 '19 at 16:15