Post request with axios and redux-thunk in a redux-form - reactjs

I can't figure how to make a post request with axios and redux-thunk so the action is dispatched after the query.
Here's my request module
export default {
get: function (action, url) {
return (dispatch) => {
axios.get(`${ROOT_URL}${url}`)
.then(({ data }) => {
dispatch({
type: action,
payload: data
});
});
};
},
post: function (action, url, props) {
return (dispatch) => {
axios.post(`${ROOT_URL}${url}`, props)
.then(({ data }) => {
return (dispatch) => {
dispatch({
type: action,
payload: data
});
};
});
};
}
}
The GET works. When I call the post function, it enters the function, but never run the returned function.
I tried modifying the order of the functions. I ended up with a working post request, but the action was never dispatched.
post: function (action, url, props) {
//POST works but action is not dispatched to reducer
axios.post(`${ROOT_URL}${url}`, props)
.then(({ data }) => {
return (dispatch) => {
dispatch({
type: action,
payload: data
});
};
});
}
Any idea how I can achieve a working post request that gets send to my api and then the response is dispatched to the reducer?
Thank you!
UPDATE
After extensive testing and back and forth, I think the problem is in redux-form. As pointed by Michael, the dispatch should work. I tested my call in my component with the get method and it doesn't work. Here's my component
const form = reduxForm({
form: 'LoginPage',
validate
});
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div className="form-group">
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} className="form-control" />
{touched && ((error && <span>{error}</span>))}
</div>
</div>
)
class LoginPage extends Component {
displayName: 'LoginPage';
onSubmit(props) {
login(props.email, props.password);
}
render() {
const {handleSubmit} = this.props;
return (
<div className='row'>
<div className='center-block'>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field name="email" type="email" label='Courriel :' component={renderField} />
<Field name="password" type="password" label='Mot de passe :' component={renderField} />
<div className="form-group text-center">
<button type="submit" className='btn btn-primary'>Se connecter</button>
</div>
</form >
</div>
</div>
);
};
};
const validate = values => {
const errors = {}
if (!values.email) {
errors.email = 'Required'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address'
}
if (!values.password) {
errors.password = 'Required'
} 4
return errors
}
export default reduxForm({
form: 'LoginPage',
validate
})(LoginPage);
The post request is in the onSubmit method. The login method is called but the return value is never dispatched.
Again, thank you for your time and help

I found my problem. Thanks to Michael for his comment that helped me look in another direction.
The problem was that my form wasn't connected to redux. I ended up adding the connect function to my export statement.
export default connect(null, { login })
(reduxForm({
form: 'LoginPage',
validate
})(LoginPage));
And I also had to change the call to the login function in onSubmit
onSubmit(props) {
this.props.login(props.email, props.password);
}

Can't add a comment, but I'm glad you figured it out. I'd like to warn you however that on newer versions of redux-form you'll have to decorate your form outside of the connect function to redux. I ran into this problem and it drove me nuts trying to figure it out.
it would be a two step process to connect redux-form after the update. for example, it would look like this.
LoginPage = reduxForm({form: 'LoginPage', validate})(LoginPage)
and then afterwards your standard connect function
export default connect(null, actions)(LoginPage)
hope this saves you the headache in the future

your function shouldnt 'return (dispatch)' again, because what the action does is return a function. it should just be
function myFunc(action, url) {
return function (dispatch) {
axios.post(`${ROOT_URL}${url}`, props)
.then(response => {
dispatch({
type: action,
payload: response.data
})
})
}
}
edited with full function.

Related

using dispatch on react

I am new to react and I'm trying to create a register and login page with react-redux and dispatch using the mern stack.
When I am calling the method the function did not run.
I have a file for the login page:
import React from "react";
import {login} from '../../actions/authActions';
export class Login extends React.Component {
constructor(props) {
super(props);
}
checkIfElementIsEmpty = (element) => {
if (!element) {
return false
}
if (element.value.length === 0)
{
return false;
}
return true;
}
handleOnClickLogin = () =>
{
let usernameElement = document.getElementsByName("loginUsername")[0];
let passwordElement = document.getElementsByName("loginPassword")[0];
if (!this.checkIfElementIsEmpty(usernameElement))
{
usernameElement.style.backgroundColor = "#ff000042";
return;
}
if (!this.checkIfElementIsEmpty(passwordElement))
{
passwordElement.style.backgroundColor = "#ff000042";
return;
}
console.log("asd");
login(usernameElement.value, passwordElement.value);
}
setColorToDefault = (e) =>{
e.target.style.backgroundColor = "#f3f3f3";
}
render() {
return <div className="base-container" ref={this.props.containerRef}>
<div className="header">Login</div>
<div className="content">
<div className="image">
{/* <img src={loginImg}/> */}
</div>
<div className="form">
<div className="form-group">
<label htmlFor="username">Username:</label>
<input type="text" name="loginUsername" placeholder="username" onFocus={this.setColorToDefault}/>
</div>
<div className="form-group">
<label htmlFor="password">Password:</label>
<input type="password" name="loginPassword" placeholder="password" onFocus={this.setColorToDefault}/>
</div>
</div>
</div>
<div className="footer">
<button type="button" className="btn" onClick={this.handleOnClickLogin}>
Login
</button>
</div>
</div>
}
}
and a file called "authActions.js" with the function "login" that should send the request to the server and validate the login.
export const login = (email, password) => (dispatch: Function) => {
console.log("bbb");
// Headers
const config = {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': "*"
}
}
// Request body
const body = JSON.stringify({ email, password });
axios
.post('http://${HOST}:${PORT}/api/auth/login', body, config)
.then(res =>
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
})
)
.catch(err => {
dispatch(
returnErrors(err.response.data, err.response.status, 'LOGIN_FAIL')
);
dispatch({
type: LOGIN_FAIL
});
});
}
When handleOnClickLogin is called, I only see the 'aaa' on the console. The 'bbb' is never being printed.
Why this is happening and how I need to use dispatch and react-redux correctly?
Your question needs more detail, but I'll guess and give you an overview of what it should look like.
Redux has a connect method that basically will call a function you pass to it with a dispatch (and getState) parameter. So, given: login = (email, password) => (dispatch: Function). You call login(email, pass); and it returns a function (dispatch, [getState]) => xxx. Redux will handle it by calling it with the store's dispatch.
For this to work, you'll also need to configure redux globally, a store, a provider, but I'm assuming your project already does that. Otherwise you'll need to go to the official docs which are really good https://react-redux.js.org/tutorials/connect
However, if you're new to Redux and don't have all the connect set up, it'll be easier (and recommended way also) to use the Hooks API (It's also recommended in react to use Hooks rather than class components). https://react-redux.js.org/introduction/getting-started#hooks
Back to your code, the important pieces you'll need:
import React from "react";
import { login } from '../../actions/authActions';
import { connect } from 'react-redux';
class MyLoginPage extends React.Component {
handleOnClickLogin = () => {
...
// calling the login bound by redux
this.props.doLogin(usernameElement.value, passwordElement.value);
}
}
const LoginPageHOC = connect(null, {
doLogin: login, // changing names for clarity (we could do login: login)
})(MyLoginPage);
export const LoginPage = LoginPageHOC; // use the HOC instead of the class

Persisting data without redux-persist or localStorage

So, once again, I've been facing this issue of persisting the state tree. In login, for the user to persist, I dispatched an action from my main App.js and got the current logged in user like this:
App.js
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch({ type: "TOKEN_VERIFICATION_STARTS" })
this.props.dispatch(getCurrentUser(authToken))
}
}
Now, I have a form and when it is submitted I'm redirecting the user to the feed where I will show the post title, description in a card form. But as usual, the postData is disappearing after refresh.
It means do I have to make another route, similar to the /me route that I made for getting the current logged in user? And dispatch an action again from the componentDidMount() in App.js?
NewPostForm.js
import React, { Component } from "react"
import { connect } from "react-redux"
import { addpost } from "../actions/userActions"
class NewpostForm extends Component {
constructor(props) {
super(props)
this.state = {
postTitle: "",
postDescription: "",
maxLength: 140
}
}
handleChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value
})
}
handleSubmit = () => {
const postData = this.state
this.props.dispatch(addpost(postData, () => {
this.props.history.push("/feed")
})
)
}
render() {
const charactersRemaining = (this.state.maxLength - this.state.postDescription.length)
return (
<div>
<input
onChange={this.handleChange}
name="postTitle"
value={this.state.postTitle}
className="input"
placeholder="Title"
maxLength="100"
/>
<textarea
onChange={this.handleChange}
name="postDescription"
value={this.state.postDescription}
className="textarea"
maxLength="140">
</textarea>
<button onClick={this.handleSubmit}>Submit</button>
<div>
Characters remaining: {charactersRemaining}
</div>
</div>
)
}
}
const mapStateToProps = (store) => {
return store
}
export default connect(mapStateToProps)(NewpostForm)
addPost action
export const addpost = (postData, redirect) => {
console.log("inside addpost action")
return async dispatch => {
dispatch({
type: "ADD_post_STARTS"
})
try {
const res = await axios.post("http://localhost:3000/api/v1/posts/new", postData, {
headers: {
"Content-Type": "application/json",
"Authorization": `${localStorage.authToken}`
}
})
dispatch({
type: "ADD_post_SUCCESS",
data: { post: res.data.post },
})
redirect()
} catch (err) {
dispatch({
type: "ADD_post_ERROR",
data: { error: "Something went wrong" }
})
}
}
}
Feed.js
import React from "react";
import { connect } from "react-redux";
const Feed = (props) => {
// const postTitle = (props.post && props.post.post.post.postTitle)
return (
<div className="card">
<header className="card-header">
<p className="card-header-title">
{/* {postTitle} */}
</p>
</header>
<div className="card-content">
<div className="content">
The text of the post written by the user.
</div>
</div>
<footer className="card-footer">
<a href="#" className="card-footer-item">
Edit
</a>
<a href="#" className="card-footer-item">
Delete
</a>
</footer>
</div>
);
};
const mapStateToProps = state => {
return state;
};
export default connect(mapStateToProps)(Feed);
I know you want without redux-persist but the redux normal behavior force to initialize store again from scratch. If you want to persist your state even refresh your page, I would recommend the following package:
https://github.com/rt2zz/redux-persist
If you are losing your state on a page redirect or traveling to a different route using react-router you will want to use:
https://github.com/reactjs/react-router-redux
If I understand correctly it looks like you are using response of /api/v1/posts/new in your feed page however trying to access local state of NewPostForm.js
this.state = {
postTitle: "",
postDescription: "",
maxLength: 140
}
Instead of using local state to save form data which cannot be shared to another component(unless passed as props which is not the case here) you may need to save data to redux store so that it can be shared across different route
handleChange = (event) => {
const { dispatch } = this.props;
const { name, value } = event.target;
dispatch(setPostData(name, value));
}
You action may look like:-
export const setPostData = (name, value) => ({
type: "SET_POST_DATA",
name,
value,
});
After that you can use this.props.postTitle on feed page
Edit: in order to keep state between page reload (full browser reload), you may need to either fetch all data on mount(higher order components are helpful) or use local storage.

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`
})

Cant get data from api

I trying to make request geocoding to yandex maps.
ymaps.geocode(cityname) returning a promise.
I using somthing like that
action>index.js
export function addWay(text) {
return async dispatch => {
try {
const request = await window.ymaps.geocode(text)
debugger
dispatch({
type: 'ADD_WAY',
payload: request
})
}
catch (e) {}
}
}
MarkAdd.js
import React, { Component} from 'react';
import {addWay} from '../actions/index';
import { connect } from 'react-redux';
class MarkerAdd extends Component {
constructor(props) {
super(props);
this.state = {value:''}
}
onInputChange = e => {
this.setState({ value: e.target.value})
}
keyPress = e => {
if(e.keyCode === 13){
console.log('enter', e.target.value);
this.props.addWay(this.state.value);
this.setState({ value: ''})
}
}
render() {
return(
<div className="field">
<div className="control">
<input className="input is-medium"
type="text"
placeholder="Add mark"
onKeyDown={this.keyPress}
onChange={this.onInputChange}
value={this.state.value}
>
</input>
</div>
</div>
)
}
}
export default connect(null, {addWay})(MarkerAdd);
But error say: Actions must be plain objects. Use custom middleware for async actions.
(Redux Thunk is installed and connected)
Whats wrong?
If i launch it via console it actually return promise.
If you have redux-thunk installed then you can dispatch actions from component this way:
import {addWay} from '../actions/index';
...
keyPress = e => {
if(e.keyCode === 13){
this.props.dispatch(addWay(this.state.value)); // <-- dispatch action
this.setState({ value: ''})
}
}
The action itslef must return a function that accepts dispatch:
export function addWay(text) {
return async dispatch => {
try {
const request = await window.ymaps.geocode(text)
dispatch({
type: 'ADD_WAY',
payload: request
})
}
catch (e) {}
}
}

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!' });
});
}

Resources