Redux Saga seems to be making an unknown API call? - reactjs

I have a login form (built with Ant Design's Form). This is all hooked up to Redux Saga. In this Saga, I am making an API call, and everything seems to be working, but mysteriously, when I dispatch any other action using put, there seems to be an extra mysterious API call that fails because it's not reading from the form correctly.
My Form:
import React from 'react';
import { connect } from 'react-redux';
import { Form, Icon, Input, Button } from 'antd';
import { FormComponentProps } from 'antd/lib/form';
import { loginRequest, ICredentials } from '../redux/auth';
import { FormContainer, LoginContainer } from './styles';
type Props = {
login: (data: ICredentials) => {};
}
class LoginForm extends React.Component<Props & FormComponentProps> {
handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.login(values)
}
});
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<FormContainer>
<Form className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
)}
</Form.Item>
<LoginContainer>
<Button type="primary" htmlType="submit" className="login-form-button" onClick={this.handleSubmit}>
Log in
</Button>
</LoginContainer>
</Form>
</FormContainer>
);
}
}
const mapDispatchToProps = { login: loginRequest };
const WrappedLoginForm = Form.create()(LoginForm);
export default connect(null, mapDispatchToProps)(WrappedLoginForm);
My Action:
export const loginRequest = (data: ICredentials): Action => ({
type: Actions.LoginRequest,
payload: data,
});
My Reducer:
const initialState: State = {
loading: false,
}
const reducer: Reducer<State> = (state = initialState, action: Actions & any) => {
switch (action.type) {
case Actions.LoginRequest:
return {
...state,
loading: true,
}
case Actions.LoginSuccess:
return {
...state,
loading: false,
}
default:
return state;
}
}
export default reducer;
my Sagas:
import { all, call, takeLatest, put } from 'redux-saga/effects';
import axios from 'axios';
import { push } from 'react-router-redux'
import message from 'antd/lib/message';
import { loginRequest, LoginRequest, loginSuccess } from './actions';
import { ICredentials } from './model';
import { AccessToken } from '../../storage/token';
const login = (payload: ICredentials) => axios({
method: 'POST',
url: //....
data: {
username: payload.username,
password: payload.password,
grant_type: 'password',
scope: 'admin,super_admin',
},
headers: {
'Content-Type': 'application/json',
Authorization: //....
}
});
function* loginSaga({ payload }: LoginRequest) {
try {
const data = yield call(login, payload);
AccessToken.set({ ...data.data, retrieved_at: Date.now() / 1000 })
yield call(message.success, 'Welcome!');
// yield all([
// put(loginSuccess()),
// put(push('/'))
// ]);
// yield put(loginSuccess());
// yield put(push('/'))
} catch (err) {
console.log(err)
}
}
function* watcherSaga() {
yield takeLatest(loginRequest, loginSaga)
}
export default watcherSaga;
In this saga, when the AccessToken is set, I ideally want to use react-router-redux to push the user into another route. However, it seems that there's a mysterious API call that's being made and it fails because there are no credentials being passed.
Pictured here is the API call coming back with a 200, but then comes back with a 400 because it's looking for username again
I suspect that it's the form that may be at fault, and though I don't want to switch to another form library, I feel like I may have to do that. Does anyone have any thoughts?

takeLatest ideally must be provided with a string. In your case a function is passed which returns an object.
takeLatest does not check the contents(i.e keys and values) of the object. That is something you would have to do on your own.
So, no matter what action is dispatched the login saga is started, which calls the login api, with an inappropriate payload and hence the error.
So, to avoid the error you could pass a string to takeLatest or in other words initiate the login saga only when Actions.LoginRequest(an action of type Actions.LoginRequest) is dispatched.
yield takeLatest(Actions.LoginRequest, loginSaga)

Related

Call a function after an asynchronous function finishes executing in React

I am pretty new to React and trying to learn by making small, simple applications. I am making a simple React application which has a Login functionality. I am also using Redux store and Redux-saga. My login flow is:
There is a Login component that takes email and password from the user, and on clicking the login button a POST call is made.
email and password are sent to the server, if they are valid the server returns a token in the response which I save in local storage.
If a token is received, action for Login success is fired. Here I set a flag called success: true.
In my front end I check the value of the success flag, and if success==true then I redirect to another page called Customers
Login Component
import React, { Component } from 'react';
import { connect } from "react-redux";
import { withRouter } from 'react-router-dom';
import { loginRequest } from "../../actions/loginActions";
import './styles.css';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
error: '',
};
}
dismissError = () => {
this.setState({ error: '' });
}
handleSubmit = (evt) => {
evt.preventDefault();
let { email, password } = this.state;
if (!email) {
return this.setState({ error: 'Username is required' });
}
if (!password) {
return this.setState({ error: 'Password is required' });
}
let data = {
email: email,
password: password
}
this.props.login(data); //dispatches a method which then makes the POST call
//the checking happens before the above function has finished executing
if (this.props.success)
this.props.history.push('/customers');
else
return this.setState({
error: 'Invalid Username/Password'
});
}
handleChange = (evt) => {
this.setState({
[evt.target.name]: evt.target.value
});
}
render() {
let { email, password } = this.state;
return (
<form className="loginForm" onSubmit={this.handleSubmit}
action="/upload">
<h2>Login</h2>
{
this.state.error &&
<h3 className='error' onClick={this.dismissError}>
<button onClick={this.dismissError}>✖</button>
{this.state.error}
</h3>
}
<label className="FormFields label">Email</label>
<input type="email" className="FormFields" name="email"
value={email}
onChange={(event) => this.handleChange(event)} />
<br />
<label className="FormFields label">Password</label>
<input type="password" className="FormFields" name="password"
value={password}
onChange={(event) => this.handleChange(event)} />
<br />
<input type="submit" className="FormFields submit"
value="Login" />
</form>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.login.loading,
success: state.login.success
}
}
const mapDispatchToProps = (dispatch) => {
return { login: (data) => {dispatch(loginRequest(data))} }
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login));
Login Saga
import { put, takeEvery, call } from 'redux-saga/effects'
import { LOGIN_REQUEST, LOGIN_PENDING, LOGIN_SUCCESS, LOGIN_FAILURE } from '../actions/loginActions';
export function* login(action) {
const { data } = action.payload;
yield put({ type: LOGIN_PENDING })
let url = 'myserverurl/login'
try {
const response = yield call(fetch, url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
}
});
let tokenObj = yield response.json();
if (response.status === 200) {
localStorage.setItem('user', tokenObj.token);
yield put({ type: LOGIN_SUCCESS, token: tokenObj.token })
}
}
catch (error) {
yield put({ type: LOGIN_FAILURE, error })
}
}
export function* watchLogin() {
yield takeEvery(LOGIN_REQUEST, login)
}
The login reducer is very simple.
Login Reducer
import { LOGIN_REQUEST, LOGIN_PENDING, LOGIN_SUCCESS, LOGIN_FAILURE } from '../actions/loginActions';
const initState = {
loading: false,
success: false,
error: ''
}
const loginReducer = (state = initState, action) => {
switch (action.type) {
case LOGIN_REQUEST:
return {
...state,
loading: false
}
case LOGIN_PENDING:
return {
...state,
loading: true
}
case LOGIN_SUCCESS:
return {
...state,
success: true,
loading: false
}
case LOGIN_FAILURE:
return {
...state,
loading: false,
success: false,
error: action.error
}
default: return state;
}
}
export default loginReducer;
The statement this.props.login(data) in Login Component dispatches the action which then makes a POST call. I want to wait for the entire flow I mentioned above to complete, before it checks the value of success flag, but that doesn't happen.
In the event of a login, how do I wait till the actions of my login reducer are completed before my front end checks for the success flag?? I read the docs on async/await but I didn't really understand how to use them properly. Can anyone help me with this
You cannot immediately check for this.props.success as you are making an async call, you need to add a check for success props in getDerivedStateFromProps
add getDerivedStateFromProps in your Login component
static getDerivedStateFromProps(nextProps, prevState) {
if(!nextProps.loading){
if(nextProps.success === true) {
nextProps.history.push('/customers');
} else {
return { error: 'Invalid Username/Password' }
}
}
return null
}
remove below code from handleSubmit
if (this.props.success)
this.props.history.push('/customers');
else
return this.setState({
error: 'Invalid Username/Password'
});

React Redux action payload data undefined

I'm trying to set up authentication for my app. Data is returned by axios and action payload is called correctly. The problem comes when I try to access the data contained in the payload. It returns undefined.
Sign in component with redux-form:
class Signin extends Component {
submit = values => {
this.props.signInAction(values, this.props.history);
};
errorMessage() {
if (this.props.errorMessage) {
return <div className="info-red">{this.props.errorMessage}</div>;
}
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.submit)} className="formu">
<div>
<div className="inputf">
<Field
name="login"
component="input"
type="text"
placeholder="Username"
/>
</div>
</div>
<div>
<div className="inputf">
<Field
name="password"
component="input"
type="password"
placeholder="Password"
/>
</div>
</div>
<div>
<button className="bsignin" type="submit">
Sign in
</button>
{this.errorMessage()}
</div>
</form>
);
}
}
function mapStateToProps(state) {
return { errorMessage: state.auth.error };
}
const reduxFormSignin = reduxForm({
form: "signin"
})(Signin);
export default connect(
mapStateToProps,
{ signInAction }
)(reduxFormSignin);
Action creator
export function signInAction({ login, password }, history) {
return async dispatch => {
try {
const res = await HTTP.post(`authenticate`, {
login,
password
});
localStorage.setItem("token", res.data.token);
const req = await HTTP.get("account");
dispatch({
type: AUTHENTICATED,
payload: req.data
});
history.push("/");
} catch (error) {
dispatch({
type: AUTHENTICATION_ERROR,
payload: "Invalid userName or password"
});
}
};
}
Reducer
import {
AUTHENTICATED,
UNAUTHENTICATED,
AUTHENTICATION_ERROR
} from "../actions";
const initialState = {
login: "",
authority: ""
};
export default function(state = initialState, action) {
switch (action.type) {
case AUTHENTICATED:
//This console log works and returns the data
console.log(action.payload);
//Next console log returns payload is undefined
//console.log(action.payload.login);
return {
...state,
authenticated: true,
// login: action.payload.login,
// authority: action.payload.authority
};
case UNAUTHENTICATED:
return { ...state, authenticated: false };
case AUTHENTICATION_ERROR:
return { ...state, error: action.payload };
default:
return state;
}
}
I'd like to set login and authority with the data comming from the payload but can't access the data inside it. ¿What am I missing?
Redux Form has an onSubmit function which takes in an action directly
https://redux-form.com/8.1.0/examples/remotesubmit/
<form onSubmit={handleSubmit} className="formu">
Then wrap within Redux Form
const reduxFormSignin = reduxForm({
form: "signin",
onSubmit: signInAction
})(Signin);
Check within the Redux Debugger, you should see redux form recording data
Also remember to pass form reducer to your store as stated here https://redux-form.com/8.1.0/docs/gettingstarted.md/
import { createStore, combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
const rootReducer = combineReducers({
// ...your other reducers here
// you have to pass formReducer under 'form' key,
// for custom keys look up the docs for 'getFormState'
form: formReducer`enter code here`
})

Why won't Axios give me a response with Redux actions?

I am trying to make a simple signup form. I have a redux form that I'm trying to send some user data to my express backend with. I am trying to do this through redux actions via this redux action:
Ultimately, I'd like to receive a response, and redirect or give errors if necessary. The backend seems to receive the data and can validate, but axios receiving the response to let redux know to update the state. Any ideas?
Edit: I also tried putting the axios request inside of the signupForm itself, and still had issues(wasn't able to get a response).
Edit: Here is the repo if you'd like to see all the files: https://github.com/capozzic1/react-blog
redux signup action:
import axios from 'axios';
import { SubmissionError } from 'redux-form';
/* eslint-disable */
export const signUp = userData => dispatch => axios.post('/api/users', userData)
.then((response) => {
dispatch({ type: 'SIGNUP_REDIRECT_YES ', payload: true})
// this.props.history.go('/');
console.log(response);
})
.catch((error) => {
console.log(error.response);
dispatch({ type: 'SIGNUP_REDIRECT_NO ', payload: false})
throw new SubmissionError({ _error: 'Login failed!' });
});
Also with this signup form component (redux-form):
class SignUpForm extends React.Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(userData) {
this.props.signup(userData);
}
render() {
const { error, handleSubmit, pristine, reset, submitting } = this.props;
return (
<form >
{error && (<strong>{error}</strong>)}
<Field name="username" type="text" component={renderField} label="Username" />
<Field name="email" type="email" component={renderField} label="Email" />
<Field name="password" type="text" component={renderField} label="Password" />
<Field name="passwordConfirm" type="text" component={renderField} label="Enter password to confirm" />
<div>
<button type="button" disabled={submitting} onClick={handleSubmit(this.onSubmit)}>Sign Up</button>
<button type="button" onClick={reset}>Clear Values</button>
</div>
</form>
);
}
}
export default reduxForm({
form: 'signUpForm',
})(SignUpForm);
This form is being fed by a container component(thought this was a standard pattern? Let me know if it's not).
Sign up page container:
const mapDispatchToProps = dispatch => ({
signup: (user) => {
dispatch(signUp(user));
},
});
const SignUp = props => (
<Layout>
<SignUpForm signup={props.signup} />;
</Layout>
);
export default connect(null, mapDispatchToProps)(SignUp);
Here is the sign up reducer:
export default function reducer(state = {
signupRedirect: false,
}, action) {
switch (action.type) {
case 'SIGNUP_REDIRECT_YES': {
return {
...state, signupRedirect: action.payload,
};
}
case 'SIGNUP_REDIRECT_NO' : {
return {
...state, signupRedirect: action.payload,
};
}
}
return state;
}
Here is my store:
import { applyMiddleware, createStore, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import reducer from './reducers';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
applyMiddleware(promise(), thunk, createLogger()),
));
export default store;
It doesn't work because your anonymous function returned by signUp function returns a promise. Use brackets to avoid default behaviour.
export const signUp = userData => dispatch => {
axios.post('/api/users', userData)
.then((response) => {
dispatch({ type: 'SIGNUP_REDIRECT_YES ', payload: true})
// this.props.history.go('/');
console.log(response);
})
.catch((error) => {
console.log(error.response);
dispatch({ type: 'SIGNUP_REDIRECT_NO ', payload: false})
throw new SubmissionError({ _error: 'Login failed!' });
});
}

React Redux Saga function never get executed

Here is the code
//REDUCER.js
import { call, take, fork } from 'redux-saga/effects';
import { request } from '../../utils';
export const LOGIN_REQUEST = "LOGIN_REQUEST";
const LOGIN_SUCCESS = "LOGIN_REQUEST";
const LOGIN_FAILED = "LOGIN_FAILED";
const initialState = { authenticated: false, loading: false };
export default function (state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST:
return { ...state, loading: true };
case LOGIN_SUCCESS:
return { ...state, loading: false, authenticated: true, user: action.payload };
case LOGIN_FAILED:
return { ...state, loading: false, };
default:
return { ...state }
}
}
export function loginRequest(loginData) {
return { type: LOGIN_REQUEST, loginData }
}
export function loginApi(formData) {
return new Promise((resolve, reject) => {
request.post('/login', formData)
.then(response => {
resolve(response)
})
.catch(error => reject(error));
})
}
export function* handleLogin(formData) {
try {
console.log('handleLogin');
const payload = yield call(loginApi, formData);
console.log(payload)
} catch (e) {
console.log('error ', e);
}
}
export function* watchLoginRequest() {
yield take(LOGIN_REQUEST, handleLogin);
}
//ROOTSAGA.js
import { all } from 'redux-saga/effects';
import { watchLoginRequest } from './modules/auth/reducer';
export default function* Root() {
yield all([
watchLoginRequest,
])
}
//store.js
import createHistory from 'history/createBrowserHistory';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../rootReducer';
import rootSaga from '../rootSagas';
const history = createHistory();
const sagaMiddleware = createSagaMiddleware();
const middlewares = [
sagaMiddleware,
thunk,
routerMiddleware(history),
];
/* eslint-disable no-underscore-dangle */
const composeEnhancers = process.env.NODE_ENV !== 'production' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(...middlewares)));
/* eslint-enable */
sagaMiddleware.run(rootSaga);
export default store;
Here is my component:
import { Button, Form, Icon, Input, Row } from 'antd';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { config } from '../../../utils';
import { loginRequest } from '../reducer';
import { Div, DivLogo } from '../styles';
const FormItem = Form.Item;
const formDecorator = Form.create();
const reduxConnect = connect(null, { loginRequest });
class Login extends Component {
static propTypes = {};
static defaultProps = {};
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log(values);
this.props.loginRequest(values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Div>
<DivLogo>
<span>{config.logoText}</span>
</DivLogo>
<Form onSubmit={this.handleSubmit}>
<FormItem hasFeedback>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username' }],
})(
<Input
size="large"
prefix={<Icon type="user" style={{ fontSize: 13 }} />}
placeholder="Username" />,
)}
</FormItem>
<FormItem hasFeedback>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password' }],
})(
<Input
type="password"
size="large"
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="Password"
/>,
)}
</FormItem>
<Row>
<Button type='primary' htmlType="submit" size='large' loading={false}>
Login
</Button>
</Row>
</Form>
</Div>
);
}
}
export default reduxConnect(formDecorator(Login));
When I submit the form it will execute the loginRequest function. I've checked it execute successfully.
The problem is watchLoginRequest never run. It never get into the handleLogin function. The console.log never shown on console.
Any solution?
In your example, there is no "live process" circuit, and source code can be simplified. So, your snippet does only catch events with specific type and invokes callback. Effect takeEvery is default use case for this snippet
export function handleLogin() {
// Your definitive saga
}
// .....
export default function* Root() {
yield [takeEvery(LOGIN_REQUEST, handleLogin)]
}
More than that, don't mix saga handler and reducer. If you want to invoke some reducer from saga, just segregate actions name to REQUEST/RESPONSE format, and then catch all *_REQUEST events and put *_RESPONSE after Process Manager (saga) worker ends.

Actions must be plain objects while using Redux

Im getting an error like
Actions must be plain objects. Use custom middleware for async actions
while using react redux. Im developing an application with a login functionality. Here is my code.
component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import * as AuthActions from '../../actions/AuthAction';
import {blueGrey50,lightBlue500} from 'material-ui/styles/colors';
const style = {
height: 350,
width: 370,
marginLeft: 80,
marginRight: 380,
marginTop: 80,
marginBottom: 50,
textAlign: 'center',
display: 'inline-block',
backgroundColor: blueGrey50,
paddingTop: 20,
};
const style1 = {
color: lightBlue500
};
const style2 = {
margin: 12,
};
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: ''
};
}
singin=()=>{
console.log('signing in');
this.props.SigninActions.signIn({email:this.state.email,password:this.state.password});
this.setState({email: '',password: '',loading:true});
console.log('done sending to actions');
}
render() {
return (
<div style={{backgroundImage: "url(" + "https://addmeskype.files.wordpress.com/2015/09/d62cb-teenagers-offlinle-online.jpg" + ")",
width:1301, height:654}}>
<Paper style={style} zDepth={2}>
<h1 style={style1}><center>Sign In</center></h1>
<TextField hintText="Email" floatingLabelText="Email" onChange={e=>{this.setState({email:e.target.value})}}/>
<TextField hintText="Password" floatingLabelText="Password" type="password" onChange={p=>{this.setState({password:p.target.value})}}/>
<br/><br/>
<RaisedButton label="Sign In" primary={true} style={style2} onTouchTap={this.singin}/>
</Paper>
{
(this.props.isError)? <span>Email or Password combination is wrong!</span> : <div>No errors</div>
}
</div>
);
}
}
Login.PropTypes = {
isError: PropTypes.bool,
SigninActions: PropTypes.object
}
const mapStateToProps = (state,ownProps) => {
return {
isError: state.isError
}
}
const mapDispatchToProps = (dispatch) => {
return {
SigninActions:bindActionCreators(AuthActions,dispatch)
};
}
export default connect(mapStateToProps,mapDispatchToProps)(Login);
Actions
import axios from 'axios';
import jwtDecode from 'jwt-decode';
import { SIGN_UP_REQUEST, SIGN_IN_REQUEST, GET_USER_DETAILS, UPDATE_USER_DETAILS } from '../constants/user';
export const getUserDetails=(email)=>{
axios.get('http://localhost:3030/user',
email
)
.then((data)=>{
console.log(data);
return ({
type: GET_USER_DETAILS,
user:data.data
});
})
.catch((error)=>{
console.log('err', error);
});
}
export const updateUserDetails=(user)=>{
axios.put('http://localhost:3030/user',
user
)
.then((data)=>{
console.log(data);
return ({
type: UPDATE_USER_DETAILS,
user:data.data
});
})
.catch((error)=>{
console.log('err', error);
});
}
Reducer
import { SIGN_UP_REQUEST, SIGN_IN_REQUEST} from '../constants/user';
const initialState = {
loading: false,
isError: false
};
export default function User(state = initialState, action) {
switch (action.type) {
case SIGN_UP_REQUEST:
return Object.assign({},state,{isError:action.data.isError});
case SIGN_IN_REQUEST:
return Object.assign({},state,{isError:action.data.isError});
default:
return state;
}
}
Rootreducer
import { combineReducers } from 'redux';
import ChatReducer from './ChatReducer';
import UserReducer from './UserReducer';
export default combineReducers({
chat: ChatReducer,
user: UserReducer
})
Store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import RootReducer from '../reducers/RootReducer';
export default() => {
return createStore(RootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
}
The browser displays the error as
How to overcome this issue?. im quite new to redux.
Vanilla redux only handles plain object actions such as
{ type: SOME_ACTION, ...parameters }
returned synchronously.
You need to look into using middleware like redux-thunk if you want to return Promises or, really, anything other than a plain object from your action creators (or, in this case, handle asynchronous actions).
see this: How to dispatch a Redux action with a timeout?
edit:
The problem is kind of two fold:
first:
export const getUserDetails = (email) => {
axios.put('http://localhost:3030/user', user) .then((data) => {
return {
type: UPDATE_USER_DETAILS,
user:data.data
};
});
});
you're returning an action inside the promise (axios.put) but you're not returning the promise - javascript doesn't work how you're intending it to work. return, in this case, is limited to the nearest parent scope; in this case the promise body. Just given what you have currently, the return type of the getUserDetails action creator is undefined.
// this is still technically *wrong*, see below
export const getUserDetails = (email) => {
// notice the return on the next line
return axios.put('http://localhost:3030/user', user) .then((data) => {
return {
type: UPDATE_USER_DETAILS,
user:data.data
};
});
});
returns a Promise<Action> which still doesn't really solve your problem.
second:
When working with redux-thunk, you wrap your action in a function like
export const getUserDetails = (email) => {
return (dispatch) => {
return axios.put('http://localhost:3030/user', user) .then((data) => {
// this is where the action is fired
dispatch({
type: UPDATE_USER_DETAILS,
user:data.data
});
// if you want to access the result of this API call, you can return here
// the returned promise will resolve to whatever you return here
return data;
});
}
});
when you bind the action creator, it will "unwrap" the creator while keeping the method signature - you use it like you would normally
this.props.getUserDetails("email#domain.com").then((data) => {
// optional resolved promise
})

Resources