I want to set a variable in the redux store that is gotten via Ajax after submitting a Formik react form
import React from 'react';
import {connect} from 'react-redux';
import { withFormik, Form, Field } from 'formik';
const LoginForm = ({
values
}) => {
return (
<div>
<Form className={styles.LoginForm}>
<Field className={input_classes} type="text" name="username" placeholder="username" />
<Field className={input_classes} type="password" name="password" placeholder="password" />
<button>Submit</button>
</Form>
</div>
)
}
const mapDispatchToProps = dispatch => {
return {
setData: (data) => dispatch({type: actionTypes.ADD_DATA, data: data})
}
}
const loginForm = connect(null, mapDispatchToProps)(LoginForm);
const FormikLoginForm = withFormik({
mapPropsToValues({ username, password }) {
return {
username: username || '',
password: password || '',
}
},
handleSubmit(values, props) {
//make AJAX call and set data in redux store
}
})(loginForm);
export default FormikLoginForm;
I cannot find a way to set any data to the store in the handleSubmit method; that is, how do I get access to the setData method that is returned mapDispatchToProps?
Order of HOC's matter, especially in this case (access to prop in another HOC) - redux connect must be outer to withFormik.
const FormikLoginForm = withFormik({
mapPropsToValues({ username, password }) {
return {
username: username || '',
password: password || '',
}
},
handleSubmit(values, { props } ) {
//make AJAX call and set data in redux store
props.setData( result )
}
})(LoginForm);
export default connect(null, mapDispatchToProps)(FormikLoginForm);
Related
Hello I am working on a Registration app, I was using useState hook to set the user information
and this way it works fine
import React, { useState } from 'react';
import axios from 'axios';
const Registration = (props) => {
const [user, setUser] = useState({
email: '',
password: '',
password_confirmation: '',
});
const { email, password, password_confirmation } = user;
const handlChange = (event) => {
setUser({ ...user, [event.target.name]: event.target.value });
};
const handleSubmit = (event) => {
axios
.post(
'http://localhost:3001/registrations',
{
user: {
email: email,
password: password,
password_confirmation: password_confirmation,
},
},
{ withCredentials: true },
)
.then((response) => {
if (response.data.status === 'created') {
props.handleSuccessfulAuth(response.data);
}
})
.catch((error) => {
console.log('registration error', error);
});
event.preventDefault();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
placeholder="Email"
value={email}
onChange={handlChange}
required
/>
<input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={handlChange}
required
/>
<input
type="password"
name="password_confirmation"
placeholder="Confirm Password"
value={password_confirmation}
onChange={handlChange}
required
/>
<button tupe="submit">Register</button>
</form>
</div>
);
};
export default Registration;
now the issue is that I have to use Redux to manage the state ,so created a slicer
here is my slicer ,
the problem i have is that i don't know how to connect redux to my registration component,
for example I am assuming that the information provided in the form is the payload so i will be passing it to my reducer as the action.payload and i think i am setting the state with the information from the payload.
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
email: '',
password: '',
password_confirmation: '',
};
const registrationSlice = createSlice({
name: 'registration',
initialState,
reducers: {
setUsers: (state, action) => {
const { email, password, password_confirmation } = action.payload;
state = {
email,
password,
password_confirmation,
};
},
},
});
export const { setUsers } = registrationSlice.actions;
export default registrationSlice.reducer;
the problem is how do i connect this to my component, I read in the Redux Toolkit website I have to use dispatch and useSelector,
so that is what i think i am doing here but is not working,
I am stuck at this point and don't know how to solve it. Can someone please help me understand how to fix it and make it work.
import React, { useState } from 'react';
import axios from 'axios';
import { useDispatch, useSelector } from 'react-redux';
import { setUsers } from '../../features/user/registrationSlice';
const Registration = (props) => {
const dispatch = useDispatch();
const user = useSelector((state) => state.user);
// const { email, password, password_confirmation } = user;
const handlChange = (event) => {
dispatch(setUsers({ [event.target.name]: event.target.value }));
};
const handleSubmit = (event) => {
axios
.post(
'http://localhost:3001/registrations',
{
user: {
email: email,
password: password,
password_confirmation: password_confirmation,
},
},
{ withCredentials: true },
)
.then((response) => {
if (response.data.status === 'created') {
props.handleSuccessfulAuth(response.data);
}
})
.catch((error) => {
console.log('registration error', error);
});
event.preventDefault();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
placeholder="Email"
value={email}
onChange={handlChange}
required
/>
<input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={handlChange}
required
/>
<input
type="password"
name="password_confirmation"
placeholder="Confirm Password"
value={password_confirmation}
onChange={handlChange}
required
/>
<button tupe="submit">Register</button>
</form>
</div>
);
};
export default Registration;
Issue
The issue here is that your reducer case is expecting a "full set" of state properties to update all at once but the UI is dispatching actions for each sub-state individually.
Solution
I suggest updating the reducer, or rather, add a new reducer to handle setting individual state properties. This leaves the possibility to set the entire state if you want.
const registrationSlice = createSlice({
name: 'registration',
initialState,
reducers: {
setUsers: (state, action) => {
const { email, password, password_confirmation } = action.payload;
state {
email,
password,
password_confirmation,
};
},
setUserProperty: (state, action) => {
const { name, value } = action.payload;
state[name] = value;
},
},
});
And dispatch the new setUserProperty action.
const handlChange = (event) => {
dispatch(setUserProperty({
name: event.target.name,
value: event.target.value,
}));
};
Since you are using Redux-toolkit you may want to eventually consider moving all the asynchronous axios POST request logic into a thunk via the createAsyncThunk. The thunk will have access to the store to get the user values and make the asynchronous calls. This will allow you to further decouple the Registration component from auth/registration logic and your redux state.
i want to display every error messages from Django Rest Api automatically in React frontend. i wanted to test my concept with the signup authentication function and after fixing it i would like to use the concept in every components in fetching data from or into django API.
here is my App.js to register a user just for test:
import React, { useState } from "react";
export default function Signup() {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password1, setPassword1] = useState("");
const [password2, setPassword2] = useState("");
const [user, setUser] = useState("");
function handleEmail(evt) {
setEmail(evt.target.value);
}
function handleUsername(evt) {
setUsername(evt.target.value);
}
function handlePassword1(evt) {
setPassword1(evt.target.value);
}
function handlePassword2(evt) {
setPassword2(evt.target.value);
}
function handle_signup(evt) {
evt.preventDefault();
fetch("http://127.0.0.1:8000/api/v1/rest-auth/registration/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, email, password1, password2 }),
})
.then((res) => res.json())
.then((json) => {
localStorage.setItem("token", json.key);
console.log(json);
setUser(json.username);
})
.catch((err) => {
if(err.res){
console.log(err.res.username)
console.log(err.res.email);
console.log(err.res.password1);
console.log(err.res.password2);
}else if(err.res){
console.log(err.res)
}else{
console.log('Error',err.message)
}
console.log(err.config);
});
}
return (
<form onSubmit={(evt) => handle_signup(evt, setUser())}>
<label htmlFor="register-username">Username:</label>
<input
type="text"
value={username}
onChange={handleUsername}
name="register-username"
id="register-username"
/>
<label htmlFor="register-email">Email:</label>
<input
type="text"
value={email}
onChange={handleEmail}
name="register-username"
id="register-username"
/>
<label htmlFor="register-password1">Password1:</label>
<input
type="password1"
value={password1}
onChange={handlePassword1}
name="register-password1"
id="register-password1"
/>
<label htmlFor="register-password2">password2:</label>
<input
type="password2"
value={password2}
onChange={handlePassword2}
name="register-password2"
id="register-password2"
/>
<input type="submit" value="Register" />
</form>
);
}
in UseEffect i have tried to show every error message under relevant input boxes which are username, email, password1, password2, i tried to do it by React-hook-form but it will be like inserting error messages from frontend. but i want to show actual error messages from backend. in development tools, when i try upper codes by putting wrong infos in input boxes, it would only show POST: 400 (bad request)
how can i show such error messages under every input boxes like Username exist or email address is invalid, or password must be at least 8 which are typical in Django Rest API's typical error messages ?
FYI: this code can register any user if the input boxes are correctly filled up.
The code below is from my article React Token-Based Authentication to Django REST API Backend. It is using react-bootstrap. It is a simple example with username and password, but you can easily extend it.
If there is a known error (axios docs about handling errors) I check if it has a message for username or password. If yes, then I set an error message for FormControl.Feedback. If you don't want to use react-bootstrap you can just make a small red text inside div and make it visible only if the error message is set (not empty).
// frontend/src/components/SignupReducer.js
// import needed actions
import {
CREATE_USER_ERROR,
CREATE_USER_SUBMITTED,
CREATE_USER_SUCCESS
} from "./SignupTypes";
// define the initial state of the signup store
const initialState = {
usernameError: "",
passwordError: "",
isSubimtted: false
};
// define how action will change the state of the store
export const signupReducer = (state = initialState, action) => {
switch (action.type) {
case CREATE_USER_SUBMITTED:
return {
usernameError: "",
passwordError: "",
isSubimtted: true
};
case CREATE_USER_ERROR:
const errorState = {
usernameError: "",
passwordError: "",
isSubimtted: false
};
if (action.errorData.hasOwnProperty("username")) {
errorState.usernameError = action.errorData["username"];
}
if (action.errorData.hasOwnProperty("password")) {
errorState.passwordError = action.errorData["password"];
}
return errorState;
case CREATE_USER_SUCCESS:
return {
usernameError: "",
passwordError: "",
isSubimtted: false
};
default:
return state;
}
}
// frontend/src/components/signup/SignupActions.js
import axios from "axios";
import { toast } from "react-toastify";
import { isEmpty } from "../../utils/Utils";
import {
CREATE_USER_ERROR,
CREATE_USER_SUBMITTED,
CREATE_USER_SUCCESS
} from "./SignupTypes";
export const signupNewUser = userData => dispatch => {
dispatch({ type: CREATE_USER_SUBMITTED }); // set submitted state
axios
.post("/api/v1/users/", userData)
.then(response => {
toast.success(
"Account for " +
userData.username +
" created successfully. Please login."
);
dispatch({ type: CREATE_USER_SUCCESS });
})
.catch(error => {
if (error.resposne) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
toast.error(JSON.stringify(error.response.data));
dispatch({
type: CREATE_USER_ERROR,
errorData: error.response.data
});
} else if (error.message) {
// the error message is available,
// let's display it on error toast
toast.error(JSON.stringify(error.message));
} else {
// strange error, just show it
toast.error(JSON.stringify(error));
}
});
};
// frontend/src/components/signup/Signup.js file
import React, { Component } from "react";
import { withRouter } from "react-router-dom"; // new import
import { connect } from "react-redux"; // new import
import PropTypes from "prop-types"; // new import
import { Link } from "react-router-dom";
import {
Container,
Button,
Row,
Col,
Form,
FormControl
} from "react-bootstrap";
import { signupNewUser } from "./SignupActions"; // new import
class Signup extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: ""
};
}
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
// update function to call the action
onSignupClick = () => {
const userData = {
username: this.state.username,
password: this.state.password
};
this.props.signupNewUser(userData); // <-- signup new user request
};
render() {
return (
<Container>
<Row>
<Col md="4">
<h1>Sign up</h1>
<Form>
<Form.Group controlId="usernameId">
<Form.Label>User name</Form.Label>
<Form.Control
isInvalid={this.props.createUser.usernameError}
type="text"
name="username"
placeholder="Enter user name"
value={this.state.username}
onChange={this.onChange}
/>
<FormControl.Feedback type="invalid">
{this.props.createUser.usernameError}
</FormControl.Feedback>
</Form.Group>
<Form.Group controlId="passwordId">
<Form.Label>Your password</Form.Label>
<Form.Control
isInvalid={this.props.createUser.passwordError}
type="password"
name="password"
placeholder="Enter password"
value={this.password}
onChange={this.onChange}
/>
<Form.Control.Feedback type="invalid">
{this.props.createUser.passwordError}
</Form.Control.Feedback>
</Form.Group>
</Form>
<Button color="primary" onClick={this.onSignupClick}>
Sign up
</Button>
<p className="mt-2">
Already have account? <Link to="/login">Login</Link>
</p>
</Col>
</Row>
</Container>
);
}
}
// connect action and reducer
// replace
// export default Signup;
// with code below:
Signup.propTypes = {
signupNewUser: PropTypes.func.isRequired,
createUser: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
createUser: state.createUser
});
export default connect(mapStateToProps, {
signupNewUser
})(withRouter(Signup));
I'm getting the following error for my code:
Uncaught (in promise) TypeError: _this2.props.login is not a function
at Login.js:37 (for a valid user I get the message "Authorized" without any problem).
When I console log the props what I get is this**{match: {…}, location: {…}, history: {…}, staticContext: undefined}**
import React, { Component } from 'react'
import fetch from 'isomorphic-fetch';
import {connect} from 'react-redux';
import {loginUser} from '../../actions/LoginActions'
export class Login extends Component {
constructor(props) {
super(props)
this.state = {
email:"",
password:""
}
this.handleSubmit=this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log(this.state.email);
event.preventDefault();
fetch("http://127.0.0.1:3001/user/login",{
method:'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
UserEmail:this.state.email,
Password:this.state.password,
})},{withCredentials:'include'})
.then (res=>res.json())
.then (res=>{
if(res.message==='Authorized'){
console.log("authorized");
console.log(this.props);
let { email, password } = this.state;
**this.props.login(email,password);** //here I get the error
this.setState({
email : "",
password : ""
});
localStorage.setItem('sessionType', res.result.sessionType);
localStorage.setItem("UserId" , res.result.UserId);
}
else{
console.log("error");
}
})
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<formgroup>
<input
type="email"
value={this.state.email}
onChange={(event)=>{this.setState({ email: event.target.value })}}
placeholder="Email"
id="email"
required
/>
</formgroup>
<formgroup>
<input
type="password"
value={this.state.password}
type="password"
onChange={(event)=>{this.setState({ password: event.target.value })}}
placeholder="Password "
id="password"
required
/>
</formgroup>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return({
login: (email,password) => {dispatch(loginUser(email,password))}
})
}
const mapStateToProps = (state) =>{
return{}
}
export default connect (mapStateToProps,mapDispatchToProps) (Login)
LoginUser Action file :
import * as actionType from './ActionType';
import fetch from 'isomorphic-fetch';
export const loginBegin =(isloginPending) =>({
type :actionType.LOGIN_BEGINS,
payload:isloginPending
});
export const login =(isloginSuccess) =>({
type :actionType.LOGIN_COMPLETE,
payload:isloginSuccess
});
export const loginError =(isloginError) =>({
type :actionType.LOGIN_ERROR,
payload:isloginError
});
export function loginUser(email, password) {
return dispatch => {
dispatch(loginBegin(true));
dispatch(login(false));
dispatch(loginError(null));
callLoginApi(email, password, error => {
dispatch(loginBegin(false));
if (!error) {
dispatch(login(true));
} else {
dispatch(loginError(error));
}
});
}
}
in mapDispatchToProps you return one object, have you tried removing the parentheses in the function?
const mapDispatchToProps = (dispatch) => {
return {
login: (email,password) => {dispatch(loginUser(email,password))}
}
}
mapDispatchToProps is supposed to be just an object.
const mapDispatchToProps = {
login: loginUser
}
loginUser is the action that you're importing to mapDispatchToProps and you're assinging that as this.props.login.
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`
})
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!' });
});
}