I am trying to access data received from web API to action in component. I set up registerUser action that posts new user data to API and then it is being sent to DB. API sents back status in JSON format. I want to render errors/notifications based on what was being passed as value of status key.
EDIT: I added key status in redux state, in REGISTER_USER type of action i am assigning value to it according to status being sent from backend.
However, i cannot access this propery in state by this.props.state/this.props.user - console loging it results in "undefined"
authActions.js
const authState = {
users: [],
status: ''
}
export const registerUser = user => dispatch => {
axios.post('https://damianlibrary.herokuapp.com/users/register', user)
.then(res => dispatch({
type: REGISTER_USER,
payload: res.data,
status: res.data.status
}))
}
authReducer.js
import { LOGIN_USER, REGISTER_USER } from '../actions/types';
const authState = {
users: []
}
export default function(state = authState, action) {
switch(action.type) {
case LOGIN_USER:
return {
...state
};
case REGISTER_USER:
return {
...state,
users: [action.payload, ...state.users]
};
default:
return state;
}
}
RegistrationForm.js component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { registerUser } from '../../actions/authActions';
import './RegisterForm.css';
class RegisterForm extends Component {
state = {
user_name: '',
password: '',
}
onChangeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value })
};
onSubmitHandler = (e) => {
const { user_name, password } = this.state
const newUser = {
user_name: user_name,
password: password
}
this.props.registerUser(newUser)
this.setState({
user_name: '',
password: ''
})
e.preventDefault();
}
render() {
const { user_name, password } = this.state;
return (
<div className='formContainer'>
<div className='form'>
<form className='bookForm' onSubmit={this.onSubmitHandler.bind(this)}>
<div className='inputs'>
<input
type='text'
name='user_name'
placeholder='Username'
onChange={this.onChangeHandler}
value={user_name}/>
<input
type='password'
name='password'
placeholder='Password'
onChange={this.onChangeHandler}
value={password}/>
</div>
<div className='buttonSpace'>
<button>Register</button>
</div>
</form>
</div>
</div>
)
}
}
const mapStateToProps = (state) => ({
user: state.user
});
export default connect(mapStateToProps, { registerUser })(RegisterForm);
Do i have to get such value in my App container (It is in ), then get status: state.status (redux state) and pass it via props to my RegisterForm component?
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(...middleware)
));
export default store;
rootReducer.js
import { combineReducers } from 'redux';
import bookReducer from './bookReducer';
import authReducer from './authReducer';
export default combineReducers({
book: bookReducer,
auth: authReducer
});
Fixed my issue. I called auth: authReducer in my rootReducer.js file and after that i tried to get what my reducer was returning by calling user: state.user instead of user: state.auth.
I can reach my redux state without any problems now.
Related
I'm learning redux and I'm trying to pull out values from state using useSelector hook and I really don't know why I cant see my error and loading property from state which is inside user obj. I'm also using initial state in store and when I try console log userInfo, error and loading I can see only userInfo and not loading and error. Is that initial state in store causing this problem? please help me out ..thank you
my code
login.js
import React, {useState, useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {loginUser, logoutUser} from '../../actions/userAction';
import {alert} from '../../actions/alertAction';
const Login = (props) => {
const dispatch = useDispatch();
const user= useSelector(state => state.user)
const alertMsg = useSelector(state => state.alert)
**console.log(user)**
**const {userInfo, loading, error} = user**
**console.log(userInfo, loading, error)**
return ("<h1>welcome to login page")
}
userAction.js file
import {USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS,USER_LOGIN_ERROR, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS, USER_LOGOUT_SUCCESS} from '../types';
import axios from 'axios';
export const loginUser = (email, password) => async(dispatch) => {
try {
console.log('login user')
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-Type": "application/json",
},
};
const {data} = await axios.post(
"/user/login",
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
}catch(error) {
console.log(error)
dispatch({
type: USER_LOGIN_ERROR,
payload: error.response.data.msg
})
}
}
userReducer.js file
import {USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS, USER_REGISTER_REQUEST,USER_LOGIN_ERROR, USER_REGISTER_SUCCESS, USER_LOGOUT_SUCCESS} from '../types';
export default (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return {
...state,
user: {loading: true}
};
case USER_LOGIN_SUCCESS:
return {
...state,
user: {
userInfo: action.payload, loading: false
}
}
case USER_LOGIN_ERROR:
return {
...state,
user: {
loading: false, error: action.payload
}
}
case USER_LOGOUT_SUCCESS:
return {
...state
};
default:
return state
}
}
index.js file
import {combineReducers} from 'redux';
import cartReducer from './cartReducer';
import userReducer from './userReducer';
export default combineReducers({
cart: cartReducer,
user: userReducer
})
store.js file
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index.js';
const cartItemsFromStorage = localStorage.getItem('cartItems') ? JSON.parse(localStorage.getItem('cartItems')) : []
const userInfoFromStorage = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null
const initialState = {
**cart: {
cartItems: cartItemsFromStorage
},
user: {
userInfo: userInfoFromStorage
}**
};
const middleware = [thunk];
const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(...middleware)));
export default store;
Problem is in how you are storing data in redux.
case USER_LOGIN_REQUEST:
return {
...state,
loading: true, // Here you are storing loading in redux state directly
error: null // same as loading
};
To solve this you need to store like below:-
case USER_LOGIN_REQUEST:
return {
...state,
user: { ...state.user, loading: true, error: null}
};
You also need to change this for all your cases where you are trying to store loading and error in user
I am trying to implement a simple login form, that gets username and password as input.
User.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
set,
reject,
verify,
isLoggedIn,
} from './userSlice';
export function User() {
const isUserLoggedIn = useSelector(isLoggedIn);
const dispatch = useDispatch();
const [loginError, setLoginError] = useState(false);
return (
<div>
<div> {loginError? 'Invalid credentials': ''}</div>
{/* Form elements here */}
<button
onClick={() => dispatch(verify())}
>
Verify User
</button>
</div>
);
}
userSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
loggedIn: false,
email: '',
name: '',
token:''
},
reducers: {
set: (state, action) => {
return Object.assign({}, state, action.payload);
},
reject: (state, action) =>{
state.value = action.payload
}
},
});
export const { set, reject } = userSlice.actions;
export const verify = user => dispatch => { // For making an api call to verify the credentials are correct
axios.post('login', data).then(function(){
dispatch(set({loggedIn:true}))
}).catch(function(){
dispatch(reject({loggedIn:false}))
});
};
export const isLoggedIn = state => state.user.loggedIn;
export default userSlice.reducer;
All codes are working fine.
Now if the api call fails, i need to update the state loginError to true. How it can be done from userSlice.js file to User.js file.
Something like that I guess
User.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
set,
reject,
verify,
isLoggedIn,
isLoginError, //<-------------
} from './userSlice';
export function User() {
const isUserLoggedIn = useSelector(isLoggedIn);
const dispatch = useDispatch();
const isLoginError = useSelector(isLoginError); //<----------------
return (
<div>
<div> {isLoginError ? 'Invalid credentials': ''}</div> //<-------------
{/* Form elements here */}
<button
onClick={() => dispatch(verify())}
>
Verify User
</button>
</div>
);
}
userSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
loggedIn: false,
email: '',
name: '',
token:''
},
reducers: {
set: (state, action) => {
return Object.assign({}, {...state, loginError:false}, action.payload); //<------
},
reject: (state, action) =>{
return Object.assign({}, {...state, loginError:true}, action.payload); //<-------
}
},
});
export const { set, reject } = userSlice.actions;
export const verify = user => dispatch => { // For making an api call to verify the credentials are correct
axios.post('login', data).then(function(){
dispatch(set({loggedIn:true}))
}).catch(function(){
dispatch(reject({loggedIn:false}))
});
};
export const isLoggedIn = state => state.user.loggedIn;
export const isLoginError = state => state.user.loginError; //<----------
export default userSlice.reducer;
I am trying to update component on state change in redux. I have a list of images, when user deletes the image the component should update after deleting the targeted image.
I have tried using componentWillReceiveProps and componentDidUpdate life cycle method, but none is working here. Can someone please suggest me what I am doing wrong here?
what I have done so far
action
import { DELETE_GALLERY_SUCCESS, DELETE_GALLERY_FAIL} from "./types";
export const deleteGalleryImage = (id) => (dispatch, getState) => {
axios
.delete(`${baseURL}/api/aws/gallery/${id}/delete/`, tokenConfig(getState))
.then(res => {
dispatch({
type: DELETE_GALLERY_SUCCESS,
payload: res.data
});
})
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({
type: DELETE_GALLERY_FAIL
});
});
};
types.js
export const DELETE_GALLERY_SUCCESS = "DELETE_GALLERY_SUCCESS"
export const DELETE_GALLERY_FAIL = "DELETE_GALLERY_FAIL"
Reducer
import {DELETE_GALLERY_SUCCESS, DELETE_GALLERY_FAIL} from "../actions/types";
const initialState = {
paginations: true,
isLoading: false,
gallery: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case DELETE_GALLERY_SUCCESS:
return {
...state,
isLoading: false,
gallery: state.gallery.filter(gallery => gallery.id !== action.payload)
};
case DELETE_GALLERY_FAIL:
return {
...state,
isLoading: false
};
default:
return state;
}
}
Here is my component
import { getAllGalleryImages, deleteGalleryImage } from '../settings/../../actions/gallery';
class DeleteGalleryImage extends Component {
componentDidMount() {
this.props.getAllGalleryImages();
}
componentWillReceiveProps(nextProps) {
if (this.props.images !== nextProps.images) {
// This is not working.
// what life cycle method should I use for this scenario?
//this.props.getAllGalleryImages()
}
}
handleDelete = (id) => {
this.props.deleteGalleryImage(id)
}
render() {
return (
<Row>
<Col xs={24} sm={22} offset={1}>
<h1 className='sub-page-heading'><span className='common_dlt'>Delete</span> Gallery Image</h1>
<div className='masonry'>
{this.props.images && this.props.images.results && this.props.images.results.map(result =>
<div className='masonry-item' key={result.id}>
<img src={result.gallery_img_url} className='dlt_blg_img' alt='img' id={result.id} />
<span className='gallery_delete_zone' onClick={() => this.handleDelete(result.id)}><Icon type="delete" /></span>
</div>
)}
</div>
</Col>
</Row>
)
}
}
const mapStateToProps = state => ({
images: state.gallery
});
export default connect(
mapStateToProps,
{ getAllGalleryImages, deleteGalleryImage }
)(DeleteGalleryImage);
Store
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
rootReducer
import { combineReducers } from "redux";
import gallery from './gallery';
export default combineReducers({
gallery,
});
In this case no need to use componentWillReceiveProps, If you can change the store succcesfully the component will be rendered automatically since it is connected to the store.
Another thing I noticed is in your deleteGalleryImage action, I think you need to send id input parameter as payload, because we cannot assume if res.data will be id.
dispatch({
type: DELETE_GALLERY_SUCCESS,
payload: id
})
Edit: based on the info I got from comments, I understood your initial state
const initialState = {
count: 2,
gallery: {},
isLoading: true,
next: null,
paginations: true,
previous: null,
results: [{}, {}]
};
So your DELETE_GALLERY_SUCCCESS case must be like this if you want to remove a image in results array.
case DELETE_GALLERY_SUCCESS:
return {
...state,
isLoading: false,
results: state.results.filter(image => image.id !== action.payload)
}
};
As the title says, I'm having issues with my React app where I'm able to hit the Redux Action, but it does not hit the Reducer after that. I've looked at a past project I worked on, as well as several posts on here, but I'm not certain what is wrong with my code that's preventing the Action from hitting the reducer. I've pasted the code below, but please let me know if there's anything else I can provide.
index.js:
import React from 'react';
import ReactDom from 'react-dom';
import App from './components/App.jsx';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from './reducers/usersRedcuers';
import './index.css';
const store = createStore(reducer);
ReactDom.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root')
)
App.jsx Component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Auth from '../modules/Auth';
import { login } from '../actions/usersActions';
class App extends Component {
constructor(props) {
super(props);
this.state = {
auth: null,
token: '',
password: '',
username: '',
}
const {
login,
} = this.props;
}
loginHandler() {
const { password, username } = this.state;
const auth = Auth.isUserAuthenticated()
const token = null;
login(auth, token, password, username);
};
render() {
return (
<div className="App">
<div className="title">
Recipe Box
</div>
<div className="form-inline login-form">
<div className="form-group">
<input
className="form-control"
onChange={e => this.setState({ username: e.target.value })}
placeholder="Username"
/>
<input
className="form-control"
onChange={e => this.setState({ password: e.target.value })}
placeholder="Password"
type="password"
/>
</div>
<button
className="btn btn-success"
onClick={() => this.loginHandler()}
type="button"
>
Login
</button>
</div>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
console.log('dispatching:', dispatch)
return bindActionCreators({login}, dispatch);
}
function mapStateToProps(state) {
return { state }
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Action Constants:
// AUTH
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
// USERS
export const ADD_USER = 'ADD_USER';
export const DELETE_USER = 'DELETE_USER';
export const UPDATE_USER = 'UPDATE_USER';
Actions:
import {
ADD_USER,
DELETE_USER,
LOGIN,
LOGOUT,
UPDATE_USER,
} from '../constants/constants';
export const login = (auth, token, password, username) => {
const action = {
type: LOGIN,
auth,
token,
password,
username,
}
console.log('login action:', action)
return action;
}
Reducer:
import {
LOGIN,
LOGOUT,
} from '../constants/constants';
const login = (action) => {
console.log('hitting B')
const { auth, token, password, username } = action;
return {
auth: auth,
token: token,
password: password,
username: username,
}
}
const authControl = (state = [], action) => {
console.log('hitting C: ', action)
let authControl = null;
switch(action.type) {
case LOGIN:
authControl = [...state, login(action)]
console.log('authControl:'. authControl);
return authControl;
default:
console.log('hittibbng default', state)
return state;
}
}
export default authControl;
In the App.jsx component you should use the action passed as a prop to the component and not call the action directly.
The loginHandler should look like this:
loginHandler() {
const { password, username } = this.state;
const auth = Auth.isUserAuthenticated()
const token = null;
this.props.login(auth, token, password, username);
};
Seems like you have missed to dispatch to the reducer
import {
ADD_USER,
DELETE_USER,
LOGIN,
LOGOUT,
UPDATE_USER,
} from '../constants/constants';
export const login = (auth, token, password, username) => dispatch => {
const action = {
type: LOGIN,
auth,
token,
password,
username,
}
console.log('login action:', action)
dispatch(action)
}
I'm using reduxForm 7.4.2 , I want to submit form and display server side validation error , but it gives me below error :
Server response :
Here is LoginForm component :
import React,{Component} from 'react'
import InputGroup from '../ui/InputGroup';
import { bindActionCreators } from "redux"
import { Field , reduxForm,SubmissionError} from 'redux-form';
import {faEnvelope,faLock} from '#fortawesome/free-solid-svg-icons'
import { connect } from 'react-redux';
import { loginUser,loginUserFailure,loginUserSuccess } from '../../actions/authActions';
class LoginForm extends Component {
constructor(){
super();
this.submitLoginForm=this.submitLoginForm.bind(this);
}
submitLoginForm=values=>{
this.props.loginUser(values);
}
render() {
const { handleSubmit, submitting} = this.props;
return(
<form onSubmit={handleSubmit(this.submitLoginForm)}>
<Field component={InputGroup} type="email" placeholder="Email" id="email" name="email" icon={faEnvelope}></Field>
<Field component={InputGroup} type="password" placeholder="Password" id="password" name="password" icon={faLock}></Field>
<button type="submit" className="btn btn-success btn-block" disabled={submitting}>Login</button>
</form>
)
}
}
const mapStateToProps = (state, ownProps) => ({
user:state.user
})
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
loginUser,
loginUserFailure,
loginUserSuccess,
}, dispatch)
}
LoginForm = connect(mapStateToProps,mapDispatchToProps)(LoginForm);
LoginForm = reduxForm({ form: 'loginForm'})(LoginForm);
export default LoginForm;
src/actions/authActions :
import axios from 'axios';
import { SubmissionError} from 'redux-form';
export const LOGIN_USER = 'LOGIN_USER';
export const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS';
export const LOGIN_USER_FAILURE = 'LOGIN_USER_FAILURE';
export const loginUser =userdata=>{
return dispatch => {
try {
const request = axios.post("/api/auth/login", userdata);
request
.then(res => {
dispatch(loginUserSuccess(request));
})
.catch(e => {
//dispatch(loginUserFailure(e));
dispatch(loginUserFailure(e.response.data));
throw new SubmissionError(e.response.data);
});
} catch (e) {
dispatch(loginUserFailure(e));
}
};
}
export const loginUserSuccess=user=>{
return {
type:LOGIN_USER_SUCCESS,
payload:user
}
}
export const loginUserFailure=error=>{
return {
type:LOGIN_USER_FAILURE,
payload:error
}
}
src/reducers/authReducer.js :
import { LOGIN_USER,LOGIN_USER_SUCCESS,LOGIN_USER_FAILURE} from './../actions/authActions';
const INITIAL_STATE = {isAuthenticated: false,user: null, status:null, error:null, loading: false};
export default function(state = INITIAL_STATE, action) {
let error;
switch (action.type) {
case LOGIN_USER:
return { ...state, isAuthenticated: false,user: null, status:'login', error:null, loading: true};
case LOGIN_USER_SUCCESS:
return { ...state, isAuthenticated: true,user: action.payload.user, status:'authenticated', error:null, loading: false};
case LOGIN_USER_FAILURE:
error = action.payload.data || {message: action.payload.message};
return { ...state,isAuthenticated: false, user:null, status:'login', error:error, loading: false};
default:
return state;
}
};
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
root reducer
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form'
import authReducer from './authReducer';
export default combineReducers({
form: formReducer,
auth:authReducer
});
There are two things here which you are doing incorrectly. Maybe I am missing other issues, but I am sure about these two:
Bind your action creators in dispatch to be able to dispatch the action in dumbcomponent Drawer. Do it something like this(only changed part, I am editing):
import { bindActionCreators } from "redux"
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
loginUser,
loginUserFailure,
loginUserSuccess,
}, dispatch)
}
You must be using redux-thunk as middleware. It is used to handle asynchronous dispatches when using redux with react. Your api call is asynchronous, so you cannot directly return the response after making axios post call. you need to return a function there, something this way:
export const loginUser = userdata => {
return dispatch => {
try {
const request = axios.post("/api/auth/login", userdata);
return request
.then(res => {
dispatch(loginUserSuccess(request));
})
.catch(e => {
dispatch(loginUserFailure(e));
});
} catch (e) {
dispatch(loginUserFailure(e));
}
};
};
And remove the promise handling in your dumbcomponent. because all your data is now available in redux, which you can get through mapStateToProps.
Note: I may have not taken care of curly braces. Please check that and feedbacks are welcome