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`
})
Related
I am trying to learn react and got an issue in redux.
The code is as follows.
import * as postActions from '../../redux/actions/postActions';
class PostForm extends Component {
handleSubmit = (e) => {
this.props.getBooks()
}
render() {
return (
<div>
<h1>Create Post</h1>
<form onSubmit={this.handleSubmit}>
<input required type="text" ref={(input)=>this.getTitle = input}
placeholder="Enter Post Title"/>
<br /><br />
<textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28"
placeholder="Enter Post" />
<br /><br />
<button>Post</button>
</form>
</div>
);
}
}
export default connect(state => ({
...state.books,
}),{
...postActions,
})(PostForm);
As you can see, when the form is submitted, this.props.getBooks() action is called.
The action is defined as follows.
import * as types from '../constants/actionTypes';
export function getBooks(obj={}) {
const api = types.API_URL_BOOKS;
return dispatch => {
return dispatch({
type: types.ACTION_BOOK_LIST,
promise: client => client.get(api).then((data) => {
return data;
}),
});
};
}
I am using axios for making api calls. The issue is that I am not getting the server response in reducer. The reducer is as follows.
import * as types from '../constants/actionTypes';
export default function reducer(state = {}, action = {}) {
switch (action.type) {
case types.ACTION_BOOK_LIST:
return {
...state,
books : action.result.data.response.books
};
default:
return state;
}
}
On debugging, i found that the action is having only the following
{type: "BOOK_LIST"}
After that, in the apiMiddlewareCreator (which is defined in clientMiddleware.js), i am getting the server response
function apiMiddlewareCreator(client) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, client);
}
const { promise, type, hideLoader, ...rest } = action;
if (!promise) {
return next(action);
}
next({ ...rest, type: `${type}` });
const actionPromise = promise(client);
actionPromise
.then(result => {
debugger
if(result.data.success === false) throw result.data.message;
if (result && result.data && result.data.response) {
switch(action.type) {
default:
//nothing
}
}
return next({ ...rest, result, type: `${type}_SUCCESS`, originalType: type })
})
return actionPromise;
};
}
reducers/index.js
import { combineReducers } from 'redux';
//import { routerReducer as routing } from 'react-router-redux';
import postReducer from './postReducer';
const appReducer = combineReducers({
// routing,
books: postReducer,
});
const rootReducer = (state, action) => {
return appReducer(state, action)
};
export default rootReducer;
actionTypes.js
export const ACTION_BOOK_LIST = 'BOOK_LIST';
I need the data to be available in the reducer. That is, action.result.data.response.books should contain the response from server.
I am not sure on how to fix this.
Any help would be appreciated.
I think it's because you're dispatching the action before the promise is resolved.
Only dispatch the action once the promise has been resolved, e.g:
import * as types from '../constants/actionTypes';
export function getBooks(obj={}) {
const api = types.API_URL_BOOKS;
return dispatch => {
client.get(api).then(data => {
dispatch({
type: types.ACTION_BOOK_LIST,
books: data.response.books
});
});
};
}
Your reducer will need to be updated to:
case types.ACTION_BOOK_LIST:
return { ...state,
books: action.books
};
I have been working on authentication with my project. I have a REST api backend that serves JWT tokens. My front end stack is ReactJS, Redux, Axios and Redux Thunk.
My question is why when I submit my form it does not send any credentials?
But when I console log the action and payload on credChange it seems to be correct. Am I not setting the state somewhere?
Also, axios does not catch the 400 Bad Request error.
Here is my code:
AuthActions.js
export const credChange = ({ prop, value }) => {
return {
type: CRED_CHANGE,
payload: { prop, value },
};
};
export const logoutUser = () => {
return (dispatch) => {
dispatch({ type: LOGOUT_USER });
};
};
const loginSuccess = (dispatch, response) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: response.data.token,
});
};
const loginError = (dispatch, error) => {
dispatch({
type: LOGIN_USER_ERROR,
payload: error.response.data,
});
};
export const loginUser = ({ empNum, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
axios({
method: 'post',
url: 'http://127.0.0.1:8000/profiles_api/jwt/authTK/',
data: {
emp_number: empNum,
password,
},
})
.then(response => loginSuccess(dispatch, response))
.catch(error => loginError(dispatch, error));
};
};
AuthReducer.js
const INITIAL_STATE = {
empNum: '',
password: '',
empNumErr: null,
passwordErr: null,
authTK: null,
loading: false,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CRED_CHANGE:
return { ...state, [action.payload.prop]: action.payload.value };
case LOGIN_USER:
return {
...state,
...INITIAL_STATE,
loading: true,
};
case LOGOUT_USER:
return {
...state,
INITIAL_STATE,
};
case LOGIN_USER_SUCCESS:
return {
...state,
...INITIAL_STATE,
authTK: action.payload,
};
case LOGIN_USER_ERROR:
return {
...state,
...INITIAL_STATE,
empNumErr: action.payload.emp_number,
passwordErr: action.payload.password,
};
default:
return state;
}
};
LoginForm.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
credChange,
loginUser,
logoutUser,
} from '../Actions';
class LoginForm extends Component {
constructor() {
super();
this.onFormSubmit = this.onFormSubmit.bind(this);
this.renderEmpNumErr = this.renderEmpNumErr.bind(this);
this.empNumChange = this.empNumChange.bind(this);
this.passwordChange = this.passwordChange.bind(this);
}
onFormSubmit() {
const { empNum, password } = this.props;
this.props.loginUser({ empNum, password });
}
empNumChange(text) {
this.props.credChange({ prop: 'empNum', value: text.target.value });
}
passwordChange(text) {
this.props.credChange({ prop: 'password', value: text.target.value });
}
renderEmpNumErr() {
if (this.props.empNumErr) {
return (
<p>
{this.props.empNumErr}
</p>
);
}
return null;
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<label htmlFor="numberLabel">Employee Number</label>
<input
id="numberLabel"
type="password"
value={this.props.empNum}
onChange={this.empNumChange}
/>
<label htmlFor="passLabel">Password</label>
<input
id="passLabel"
type="password"
value={this.props.password}
onChange={this.passwordChange}
/>
<button type="submit">Login</button>
</form>
{this.renderEmpNumErr()}
</div>
);
}
}
const mapStateToProps = ({ counter }) => {
const {
empNum,
password,
loading,
empNumErr,
passwordErr,
authTK,
} = counter;
return {
empNum,
password,
loading,
empNumErr,
passwordErr,
authTK,
};
};
export default connect(mapStateToProps, { credChange, loginUser, logoutUser })(LoginForm);
After Submitting form with credentials
The console says:
POST XHR http://127.0.0.1:8000/profiles_api/jwt/authTK/ [HTTP/1.0 400 Bad Request 5ms]
And the POST request Raw Data is blank, therefore no credentials were sent.
{"emp_number":["This field is required."],"password":["This field is required."]}
EDIT
If there is any other information I can provide please say so but I think this should be sufficient.
Looks like empNum and password aren't getting set in the state. This is because the action object returned by credChange doesn't get dispatched, so the reducer never get called:
// dispatch calls the reducer which updates the state
dispatch(actionCreator())
// returns an action object, doesn't call reducer
actionCreator()
You can dispatch actions automatically by calling a bound action creator:
// calls the reducer, updates the state
const boundActionCreator = () => {dispatch(actionCreator())}
// call boundActionCreator in your component
boundActionCreator()
mapDispatchToProps can be used to define bound action creators (to be passed as props):
const mapDispatchToProps = (dispatch) => {
return {
credChange: ({ prop, value }) => {dispatch(credChange({prop, value})},
loginUser: ({ empNum, password }) => {dispatch(loginUser({empNum, password})},
logoutUser: () => {dispatch(logoutUser()},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
This should solve the state update issue, allowing props that read from state (empNumber, password, etc.) to update as well.
I am trying use a redux form to pass a field into a action creator that hits an api with that field result. The results should be sent back to that same component and render under the search input field. here is my action creator
export function getStock({ticker_symbol}){
console.log(ticker_symbol)
return function(dispatch) {
axios.get(`https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=${ticker_symbol}&interval=1min&apikey=5QP2C2R2YCZ71HDB&datatype=json`, {
headers: { authorization: localStorage.getItem('token')}
})
.then(res => {
console.log(res.data["Time Series (1min)"])
dispatch({
type: GET_STOCK,
payload: res.data
})
}).catch(res => {
dispatch(authError(res.error))
});
}
}
here is the reducer
import {
FETCH_MESSAGE,
GET_STOCK
} from '../actions/types';
export default function(state = {}, action){
switch(action.type) {
case FETCH_MESSAGE:
return {...state, message: action.payload};
case GET_STOCK:
return {...state, stock: action.payload,
stockDataLoaded: false};
}
return state;
}
here is the component that has the form and is suppose to get the rendering information. The console logs are getting me the correct data.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import * as actions from '../actions';
class Feature extends Component{
componentWillMount(){
this.props.fetchMessage();
}
handleFormSubmit({ ticker_symbol }){
console.log( ticker_symbol );
this.props.getStock({ticker_symbol});
}
render(){
const { handleSubmit, fields: { ticker_symbol }} = this.props;
return (
<div>
<form onSubmit={ handleSubmit(this.handleFormSubmit.bind(this))}>
<fieldset className="form-group">
<label> Ticker Symbol: </label>
<input {...ticker_symbol} className="form-control" />
</fieldset>
<button action="submit" className="btn btn-primary"> Get Quote </button>
</form>
<h1></h1>
</div>
);
}
}
function mapStateToProps(state){
console.log(state);
return { message: state.stock.message,
stock: state.stock.stock};
}
export default reduxForm({
form: 'getstock',
fields: ['ticker_symbol']
}, mapStateToProps, actions)(Feature);
Here is a screenshot of the page/console
I want the results to appear under the ticker symbol
Responding to your comment;
Any component that relies on it's state needs to initialize the state attribute using the getInitialState lifecycle hook, as such;
getInitialState() {
return {
};
}
You can leave it entirely empty if you wish to merely initialize the state object, or you can populate it with default starting values.
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!' });
});
}
I'm very beginner in React/Redux and I was trying to do something in an already existing code, just to understand how it works. The part I want to edit is the connection part. As it's now, if the login and password are OK, you go to a new page, and if not, it does nothing.
The only simple thing I'm trying to do is to show the user their login informations are wrong, by adding a red border on the fields for example.
So here is the code I added, I'll try not to show you useless code and not to forget useful code, but let me know if you need more.
The first thing I did is adding a constant for the error in actionTypes.js:
export const AUTH_REQUEST = 'AUTH_REQUEST';
export const AUTH_RECEIVE = 'AUTH_RECEIVE';
export const AUTH_ERROR = 'AUTH_ERROR';
Then in actions/auth.js, I added the authError function and called it after a fail response from the server:
function authRequest() {
return {
type: actionTypes.AUTH_REQUEST
};
}
function authReceive(authToken) {
return {
type: actionTypes.AUTH_RECEIVE,
authToken
};
}
function authError() {
return {
type: actionTypes.AUTH_ERROR
};
}
export function fetchLogin(email, password) {
return function (dispatch) {
dispatch(authRequest());
const urlApi = `//${AUTH_BACKEND_HOST}:${AUTH_BACKEND_PORT}/${AUTH_BACKEND_URL.login}`
fetch(urlApi, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
if(response.ok) {
// SUCCESS
response.json().then(function(json) {
dispatch(authReceive(json.key));
dispatch(push('/'));
});
} else {
// FAIL
response.json().then(function(json) {
dispatch(authError());
});
}
})
.catch(function(ex) {
console.log(ex);
});
};
}
Now, in reducers/auth.js:
const initialState = {
authToken: '',
isFetching: false,
error: false,
errorMessage: ''
}
export default function (state=initialState, action) {
switch (action.type) {
case actionType.AUTH_REQUEST:
return {
...state,
isFetching: true
};
case actionType.AUTH_RECEIVE:
return authLogin(state, action);
case actionType.AUTH_ERROR:
return {
...state,
error: true,
errorMessage: 'Incorrect login or password!'
};
}
return state;
}
function authLogin(state, action) {
const { authToken } = action;
return {
...state,
isFetching: false,
authToken
};
}
Until now, it seems to work when I inspect it in Firefox. The state contains the error and errorMessage values.
So here is my components/Login/presenter.jsx which I thought was going to display the right HTML depending on the state:
import React from 'react';
const Login = React.createClass({
handleSubmit(event) {
event.preventDefault()
const email = this.refs.email.value
const password = this.refs.password.value
this.props.onAuth(email, password);
},
render() {
const { errorMessage } = this.props
return (
<form onSubmit={this.handleSubmit}>
<label>Email <input ref="email" placeholder="email" required /></label>
<label>Password <input ref="password" placeholder="password" type="password" required /></label><br />
<p>{errorMessage}</p>
<button type="submit">Login</button>
</form>
)
}
});
export default Login;
And here is components/Login/index.js which I think imports the presenter and do... things...:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Login from './presenter';
function mapDispatchToProps(dispatch) {
return {
onAuth: bindActionCreators(actions.fetchLogin, dispatch)
};
}
export default connect(null, mapDispatchToProps) (Login);
Edit : it seems that one of the problems is that I'm not mapping the state to props. I tried Mael Razavet and azium's answers, adding mapStateToProps in Login/index.js:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Login from './presenter';
function mapDispatchToProps(dispatch) {
return {
onAuth: bindActionCreators(actions.fetchLogin, dispatch)
};
}
function mapStateToProps (state) {
return {
errorMessage: state.errorMessage
};
}
export default connect(mapStateToProps, mapDispatchToProps) (Login);
But it seems that errorMessage is still undefined.
Thank you.
I think you forgot to map your state to props. In your case, you should add this content to your components/Login/index.js:
import * as actions from './actions/auth.js';
import Login from './presenter';
const mapStateToProps = (state) => {
return {
error: state.login.error,
errorMessage: state.login.errorMessage,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onAuth: (email, password) => {
dispatch(actions.fetchLogin(email, password));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login); // maps your state to your component's props
In your reducers/auth.js:
const initialState = {
authToken: '',
isFetching: false,
error: false,
errorMessage: ''
}
export default function loginReducer(state=initialState, action) {
switch (action.type) {
case actionType.AUTH_REQUEST:
return {
...state,
isFetching: true
};
case actionType.AUTH_RECEIVE:
return authLogin(state, action);
case actionType.AUTH_ERROR:
return {
...state,
error: true,
errorMessage: 'Incorrect login or password!'
};
}
return state;
}
function authLogin(state, action) {
const { authToken } = action;
return {
...state,
isFetching: false,
authToken
};
}
Then, in your code, you should be combining your reducer like:
import { combineReducers, createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import Login from './components/Login';
import login from './reducers/auth.js'; //import your default reducer function
//COMBINE REDUCERS
const reducers = combineReducers({
//reducers go here
login, //name of your reducer => this is is why your access your state like state.login
});
//WRAP WITH STORE AND RENDER
const createStoreWithMiddleware = applyMiddleware()(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Login/>
</Provider>
, document.querySelector('.container'));
In Redux, you manage your state (setState) in a different layer (reducer) than your actual component. To do so, you need to map your state from the reducer to the component so you can use it as a props. This is why in your Login class, you are able to do :
const { errorMessage } = this.props; // now you can use errorMessage directly or this.props.errorMessage
This errorMessage comes from your state managed in your reducer and can be used in your component as this.props.errorMessage.
Here is the link to the tutorial which helped me understand Redux in React : https://github.com/happypoulp/redux-tutorial
It should help you understand better the workflow