0

I've create a wizard form that I change a little bit. Each step of the form fetch some data from API when componentWillMount and when user click on "next" to go to the next form part, I call dispatch another action with componentWillUnmout to dispatch an Update (PUT) action from the API.

I'm using redux-saga with react.

Here is the saga.js file :

import { call, put, takeEvery } from 'redux-saga/effects';
import accountApi from '../../api/account';
import * as actions from '../../actions';

import {
    FETCH_ACCOUNT_REQUEST,
    FETCH_ACCOUNT_SUCCESS,
    UPDATE_ACCOUNT_REQUEST,
    UPDATE_ACCOUNT_SUCCESS
} from '../../actions/types';

export function* fetchAccount(action) {
    const account = yield call(accountApi.fetchAccount, action.params);
    yield put({ type: FETCH_ACCOUNT_SUCCESS, account: account });
}

export function* updateAccount(action) {
    const accountUpdated = yield call(accountApi.updateAccount, action.params, action.accountUpdated);
    yield put({ type: UPDATE_ACCOUNT_SUCCESS, accountUpdated: accountUpdated });
}

export function* accountListener() {
    yield takeEvery(FETCH_ACCOUNT_REQUEST, fetchAccount);
    yield takeEvery(UPDATE_ACCOUNT_REQUEST, updateAccount);
}

There is the API file :

import axios from 'axios';

const baseUrl = '<link>';

const accountApi = {
    fetchAccount(params) {
        return axios.get(`${baseUrl}${params}`)
            .then(response => {
                return response.data
            })
    },

    updateAccount(params, accountUpdated) {
        return axios.put(`${baseUrl}${params}`, accountUpdated)
            .then(response => {
                return response.data
            })
    }
}

export default accountApi;

the action :

import axios from 'axios';

import {
    FETCH_ACCOUNT_REQUEST,
    UPDATE_ACCOUNT_REQUEST
} from './types';

export function fetchAccount(params) {
    return {
        type: FETCH_ACCOUNT_REQUEST,
        params
    }
}

export function updateAccount(params, accountUpdated) {
    return {
        type: UPDATE_ACCOUNT_REQUEST,
        params,
        accountUpdated
    }
}

the reducer :

import { 
    FETCH_ACCOUNT_REQUEST, 
    FETCH_ACCOUNT_SUCCESS, 
    UPDATE_ACCOUNT_REQUEST,
    UPDATE_ACCOUNT_SUCCESS
} from '../actions/types';

export default function(state = null, action) {
    switch (action.type) {
        case FETCH_ACCOUNT_REQUEST:
            return null;
        case FETCH_ACCOUNT_SUCCESS:
            return action.account;
        case UPDATE_ACCOUNT_REQUEST:
            return null;
        case UPDATE_ACCOUNT_SUCCESS:
            return action.accountUpdated;
        default:
            return state;
    }
}

And the component :

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Field, reduxForm } from 'redux-form';
import * as actions from '../../actions';
import validate from './validate';

class FormGlobalInformations extends Component {
    componentWillMount() {
        const fakeAccountId = '?accountId=0012400000oAMhY';
        this.props.actions.fetchAccount(fakeAccountId);
    }

    componentDidMount() {
        if (this.props.account) {
            this.props.initialize({
                companyName: this.props.account.companyName,
                typologie: this.props.account.typologie,
                phone: this.props.account.phone
            });
        }
    }

    componentWillUnmount() {
        const fakeAccountId = '?accountId=0012400000oAMhY';
        this.props.actions.updateAccount(fakeAccountId, this.props.form.Information.values);
    }

    render() {
        const { handleSubmit } = this.props;
        return (
            <form onSubmit={handleSubmit}>
                    <Field 
                        label="Nom de l'établissement"
                        name="companyName"
                        type="text"
                        className="form-group"
                        component={this.renderFields}
                    />
                    <Field 
                        label="Type d'établissement : "
                        name="typologie"
                        component={this.renderSelect}
                        className="form-group"
                    />
                    <Field 
                        label="Votre numéro de téléphone de contact"
                        name="phone"
                        type="number"
                        className="form-group"
                        component={this.renderFields}
                    />
                    <button type="submit" className="btn btn-tiller-full btn-tiller-right">Suite</button>
            </form>
        );  
    }
}

function mapStateToProps(state) {
    return {
        account: state.account,
        form: state.form
    }
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(actions, dispatch)
    }
}

FormGlobalInformations = connect(mapStateToProps, mapDispatchToProps)(FormGlobalInformations);

FormGlobalInformations = reduxForm({
    form: 'Information',
    destroyOnUnmount: false,
    validate
})(FormGlobalInformations);

export default FormGlobalInformations;

(The next form step component is the same so I fetch data onmount and I update on unmount).

Here is the index.js file :

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
// import ReduxThunk from 'redux-thunk';
import { all } from 'redux-saga/effects';

import createSagaMiddleware from 'redux-saga';
import reducers from './reducers';

// Components
import Informations from './components/Informations/Informations';
import Navigation from './components/Navigation/Navigation';
import Header from './components/Navigation/Header';

import { accountListener } from './services/account/saga';

function* RootListener() {
    yield all([
        accountListener()
    ]);
}

const sagaMiddleware = createSagaMiddleware();
const store = createStore(
    reducers,
    applyMiddleware(sagaMiddleware)
);

sagaMiddleware.run(RootListener);

ReactDOM.render(
    <Provider store={store}>
        <BrowserRouter>
            <div>
                <Header />
                <Navigation />
                <Switch>
                    <Route path="/informations" component={Informations} />
                </Switch>
            </div>
        </BrowserRouter>
    </Provider>,
    document.querySelector('.container-fluid')
);

Here is the error that I have when I try to update : enter image description here

And I have a OPTIONS request...

Someone could help ?

Antonin Mrchd
  • 584
  • 1
  • 7
  • 25
  • 1
    Maybe is a CORS plus webserver issue and not a client one? I think about CORS because you have an OPTIONS here. Keep in mind that some webserver (nginx for example) needs you to configure HTTP verb like OPTIONS – keul Nov 10 '17 at 17:35

1 Answers1

1

Update (PUT) action from the API.

And I have a OPTIONS request...

Most likely that issue is not dependent on redux-saga, because error occurs in axios call inside. Supposed preconditions with PUT verb affects mandatory OPTIONS preflight query, which returns available CORS politics for request origin. It's mandatory because of PUT is state-changing verb, so returning allowance HTTP header, like in GET-requests, is very late, that's why independent preflight query is used.

Read more here How to apply CORS preflight cache to an entire domain and here Why is an OPTIONS request sent and can I disable it?

Simplest solution without preflight query provided here: https://stackoverflow.com/a/40373949/7878274

Community
  • 1
  • 1
Vladislav Ihost
  • 1,706
  • 7
  • 23