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)
}
Related
I'm just starting in redux and I want to include it on my existing app. What I want to do is to store my login response for me to use the user details on other page.
LandingPage.js
import { useDispatch } from 'react-redux'
function LandingPage(){
const dispatch = useDispatch();
const authLogin = async()=>{
const response = await axios.get('/api',)
let responseValue = response.data.success
if(responseValue === true) {
const parsedData = JSON.parse(response.data.resp)
dispatch({
type: 'SAVE_AUTH',
payload: {
isLoggedIn: responseValue,
username: parsedData.data.user.userName,
token: parsedData.data.token
}
})
}
useEffect(() => {
authLogin();
}, [])
return (
<div>
<label>Authenticating....</label>
<Login1 /> //updated based on #LindaPaiste's answer
</div>
export default LandingPage;
MainLanding.js
import React from 'react'
import Login1 from './Login1'
function MainLanding(){
return(
<div>
<h1>User Login Details</h1>
<Login1 /> //Nothing hapens here
</div>
)
}
export default MainLanding;
Login1.js
import React from 'react'
import LoginDetailsx from './LoginDetailsx'
import { useSelector } from 'react-redux'
function Login1(){
const userLoginDetails = useSelector((state) => state.loginDetails)
console.log('userLoginDetails',userLoginDetails)
return(
<div>
<h2>Login Details</h2>
<LoginDetailsx isLogin={userLoginDetails.isLoggedIn} username={userLoginDetails.username} token={userLoginDetails.token}/>
})}
</div>
)}
export default Login1;
loginDetailsReducer.js
const initialState = [
{
isLoggedIn: false,
}];
const loginDetailsReducer = (state = initialState, action) => {
const { type, payload } = action;
console.log('typex',type)
console.log('payloadx',payload)
switch(type){
case "SAVE_AUTH":
alert('dasdasd')
return payload;
case "LOGOUT_AUTH":
return initialState
default:
return state;
}
}
export default loginDetailsReducer;
rootReducer.js
import { combineReducers } from 'redux'
import loginDetailsReducer from '../reduxReducers/loginDetailsReducer'
const rootReducer = combineReducers({
loginDetails: loginDetailsReducer
});
export default rootReducer;
store.js
import { createStore } from 'redux'
import rootReducer from '../reduxReducers/rootReducer'
const store = createStore(rootReducer);
export default store;
LoginDetailsx.js
import React from 'react'
function LoginDetailsx(props){
return(
<div>
<p>Details: isloggedin: {props.isloggedin}, username: {props.username}, token: {props.token}</p>
</div>
)
}
export default LoginDetailsx;
This is what I'm getting on MainLanding.js after successful login.
and this is what i'm getting on LandingPage.js console.log
State Shape
While not necessarily a problem, it really doesn't make sense that the loginDetails state should be an array. Only one user should be logged in at a time, so it should just be an object with the user details. That makes your reducer extremely simple (as always Redux Toolkit can make it even simpler).
You'll want to add a logout case too. isLoggedIn should be a boolean instead of a string. I personally think that undefined makes more sense than '' for username and token when there is no logged in user but that's up to you.
const initialState = {
isLoggedIn: false,
// no username or token when logged out
};
const loginDetailsReducer = (state = initialState, action) => {
const { type, payload } = action;
switch(type) {
case "SAVE_AUTH":
// replace the state with the action payload
return payload;
case "LOGOUT_AUTH":
// revert to initial state
return initialState;
default:
return state;
}
}
export default loginDetailsReducer;
Logging In
I was going to say that asynchronous actions like API calls need to be done inside a useEffect hook in the component. You can use an empty dependency array to run the effect once when the component is mounted.
useEffect(() => {
authLogin();
}, []);
But now I'm looking at your image and it seems like you are executing the action in response to a button click, so that's fine too.
axios handles JSON parsing so you should not need to use JSON.parse() (unless your API is returning strange data).
function MainLanding() {
const isLoggedIn = useSelector((state) => state.loginDetails.isLoggedIn);
// access dispatch function
const dispatch = useDispatch();
// define the function to log in
const authLogin = async () => {
const response = await axios.get("/api");
const data = response.data;
if (data.success === true) {
dispatch({
type: "SAVE_AUTH",
payload: {
isLoggedIn: true,
username: data.resp.user.userName,
token: data.resp.data.token
}
});
}
};
return (
<div>
{isLoggedIn ? (
<>
<h1>User Login Details</h1>
<Login1 />
</>
) : (
<button onClick={authLogin}>Log In</button>
)}
</div>
);
}
I am creating a react app, when getting data from redux. I am facing the below error message in browser. please check and let me know what am I missing.
I am using create-react-app redux-toolkit setup template to create the app
Here is my app.js:
import React from "react";
import { useSelector } from "react-redux";
import "./App.css";
import { selectUser } from "./features/userSlice";
import Header from "./components/Header";
import Sidebar from "./components/Sidebar";
import Feed from "./components/Feed";
import Login from "./components/Login";
function App() {
const user = useSelector(selectUser);
return (
<div className="App">
<Header />
{ !user ? (
<Login />
) : (
<div className="main_content">
<Sidebar />
<Feed />
</div>
)}
</div>
);
}
export default App;
below you can find the redux reducer and actions
import { createSlice } from '#reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
},
reducers: {
login: (state, action) => {
state.value = action.payload
},
logout: (state, action) => {
state.user = null
}
},
});
export const { login, logout } = userSlice.actions;
export const selectUser = (state) => state.user.user;
export default userSlice.reducer;
below is the screenshot of error which. I'am getting when running the app
Working example for you, be sure you configured your store correctly. You should separate this into responding files.
import React from "react";
import { combineReducers, createStore, createSlice } from "#reduxjs/toolkit";
import { connect, Provider, useDispatch, useSelector } from "react-redux";
// your part
const userSlice = createSlice({
name: "user",
initialState: {
user: null
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state, action) => {
state.user = null;
}
}
});
const { login, logout } = userSlice.actions
const selectUser = (state) => state.user.user;
// what I added
const reducer = combineReducers({
user: userSlice.reducer
});
const store = createStore(reducer);
const Main = (props) => {
const dispatch = useDispatch() // I used this to check if reducers work
const user = useSelector( selectUser )
return (
<div onClick={ () => { dispatch(login({name: "Adam"})) }}>
{ !user ? "LOGIN" : "DASHBOARD "}
</div>
)
}
const mapStateToProps = (state) => ({
user: state.user
});
const Container = connect(mapStateToProps, { login, logout })(Main);
function App() {
return (
<Provider store={store}>
<Container/>
</Provider>
);
}
export default App;
I am working on an authentication system using react at front. I am storing token which comes from my backend server to localStorage and i want user to redirect to dashboard page when there is a token present in localStorage. Every time i login using correct credentials i get token but not redirecting to dashboard page. But when i change route in url it works. I am using react context api.
AuthContext.js
import { createContext } from "react";
const AuthContext = createContext();
export default AuthContext;
AuthState.js
import React, { useReducer, useState } from "react";
import AuthContext from "./AuthContext";
import { SUCCESS_LOGIN } from "../types";
import AuthReducers from "./AuthReducers";
import Axios from "axios";
const AuthState = ({ children }) => {
//setting up initial state for authcontext
const initialState = {
userAuth: null,
userLoading: false,
token: localStorage.getItem("token"),
errors: null,
};
const [state, dispatch] = useReducer(AuthReducers, initialState);
//logging user in
const loginUser = async (userData) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
//posting to api
const res = await Axios.post("/api/user/login", userData, config);
console.log(res.data);
dispatch({
type: SUCCESS_LOGIN,
payload: res.data,
});
} catch (error) {
console.log(error.response);
}
};
return (
<AuthContext.Provider
value={{
userAuth: state.userAuth,
errors: state.errors,
token: state.token,
loginUser,
}}
>
{children}
</AuthContext.Provider>
);
};
export default AuthState;
AuthReducers.js
import { SUCCESS_LOGIN } from "../types";
export default (state, action) => {
switch (action.type) {
case SUCCESS_LOGIN:
const token = action.payload.token;
localStorage.setItem("token", token);
return {
...state,
userAuth: true,
userLoading: true,
errors: null,
token: localStorage.getItem("token"),
};
default:
return state;
}
};
Login.js
import React, { useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import { Button, Form, FormGroup, Label, Input, FormText } from "reactstrap";
import styles from "./login.module.css";
import AuthContext from "../../context/AuthContext/AuthContext";
const Login = (props) => {
//grabbing states from authContext
const { loginUser, userAuth } = useContext(AuthContext);
let history = useHistory();
const [credentials, setCredentials] = useState({
email: "",
password: "",
});
//pulling email and password from state
const { email, password } = credentials;
//method to handle changes on input fields
const handleChange = (e) => {
const { name, value } = e.target;
setCredentials({
...credentials,
[name]: value,
});
};
//method to handle login when user submits the form
const handleLogin = (e) => {
e.preventDefault();
loginUser({ email, password });
console.log(userAuth);
if (userAuth) {
history.push("/dashboard");
}
};
return (
<Form onSubmit={handleLogin}>
<FormGroup>
<Label for="email">Email</Label>
<Input
type="email"
name="email"
value={email}
placeholder="Enter your email"
onChange={handleChange}
/>
</FormGroup>
<FormGroup>
<Label for="password">Password</Label>
<Input
type="password"
name="password"
value={password}
placeholder="Enter password"
onChange={handleChange}
/>
</FormGroup>
<Button className={styles.loginBtn}>Submit</Button>
</Form>
);
};
export default Login;
PrivateRoute.js
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import AuthContext from "../../context/AuthContext/AuthContext";
const PrivateRoute = ({ component: Component, ...rest }) => {
const { token, userAuth } = useContext(AuthContext);
return (
<div>
<Route
{...rest}
render={(props) =>
token ? <Component {...props} /> : <Redirect to="/" />
}
/>
</div>
);
};
export default PrivateRoute;
You need to do this in Login.js.
useEffect(() => {
if (userAuth) {
history.push("/dashboard");
}
},[userAuth,history])
Its happening because when you do handleLogin click functionality you dont have userAuth at that time as true(its taking previous value). Because context update change is not available in handleLogin function . Instead track userAuth in useEffect
If you are trying to redirect the user after successful login via your handleLogin() function, it won't work because of this:
if (userAuth) {
history.push("/dashboard");
}
The above will not run, because userAuth won't change until the component re-renders, after which, the function will have finished executing. You should either return something from your loginUser() action, and redirect based on its return of a successful "login", or implement conditional rendering inside of the Login component, like so:
return userAuth
? <Redirect to="/dashboard" /> // redirect if userAuth == true
: (
// your Login JSX // if userAuth == false, render Login form
)
I am new in firebase and reactjs.I am trying to get data from firestore.I searched about this problem but could not find any answer.
I saw this page too.but not exist right answer
react-redux-firebase not populating profile in firebase reducerenter image description here
there are my codes.it seems right but do not work.index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {createStore, applyMiddleware, compose} from 'redux'
import rootReducer from "./store/reducers/rootReducer";
import {Provider} from 'react-redux'
import thunk from "redux-thunk";
import {reduxFirestore, getFirestore} from 'redux-firestore';
import {reactReduxFirebase, getFirebase} from 'react-redux-firebase';
import fbConfig from './config/fbConfig'
import firebase from "firebase";
const store = createStore(rootReducer,
compose(
applyMiddleware(thunk.withExtraArgument({getFirebase, getFirestore})),
reduxFirestore(fbConfig),
reactReduxFirebase(fbConfig, {userProfile: 'users', useFirestoreForProfile: true, attachAuthIsReady: true})
)
);
store.firebaseAuthIsReady.then(() => {
ReactDOM.render(<Provider store={store}><App/></Provider>, document.getElementById('root'));
serviceWorker.unregister();
})
and this is signIn class codes
import React, {Component} from 'react'
import {connect} from 'react-redux'
import {signIn} from "../../store/actions/authActions";
import {Redirect} from 'react-router-dom'
class SignIn extends Component {
state = {
email: '',
password: ''
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.signIn(this.state);
}
render() {
const {authError, auth} = this.props;
if (auth.uid) return <Redirect to='/'/>
return (
<div className='container'>
<form onSubmit={this.handleSubmit} className="white">
<h5 className='grey-text text-darken-3'>Sign In</h5>
<div className="input-field">
<label htmlFor="email">Email</label>
<input type="email" id='email' onChange={this.handleChange}/>
</div>
<div className="input-field">
<label htmlFor="password">Password</label>
<input type="password" id='password' onChange={this.handleChange}/>
</div>
<div className="input-field">
<button className='btn pink lighten-1 z-depth-0'>LogIn</button>
<div className="red-text center">
{authError ? <p>{authError}</p> : null}
</div>
</div>
</form>
</div>
)
}
};
const mapStateToProps = (state) => {
return {
authError: state.auth.authError,
auth: state.firebase.auth
}
}
const mapDispatchToProps = (dispatch) => {
return {
signIn: (creds) => dispatch(signIn(creds))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn)
sign in action
export const signIn = (credentials) => {
return (dispatch, getState, {getFirebase}) => {
const firebase = getFirebase();
firebase.auth().signInWithEmailAndPassword(
credentials.email,
credentials.password
).then(() => {
dispatch({type: 'LOGIN_SUCCESS'})
}).catch((err) => {
dispatch({type: 'LOGIN_ERROR', err})
})
}
};
authReducer codes
const initState = {};
const authReducer = (state = initState, action) => {
switch (action.type) {
case 'LOGIN_ERROR':
console.log('login error')
return {
...state,
authError: 'LoginFailed'
};
case 'LOGIN_SUCCESS':
console.log('login success')
return {
...state,
authError: null
};
case 'SIGNOUT_SUCCESS':
console.log('signout success');
return state;
case 'SIGNUP_SUCCESS':
console.log('signup success');
return {
...state,
authError: null
};
case 'SIGNUP_ERROR':
console.log('signup error');
return {
...state,
authError: action.err.message
};
default:
return state;
}
return state
}
export default authReducer
rootReducer
import authReducer from "./authReducer";
import projectReducer from "./projectReducer";
import {combineReducers} from 'redux'
import {firestoreReducer} from 'redux-firestore'
import {firebaseReducer} from "react-redux-firebase";
const rootReducer = combineReducers({
auth: authReducer,
project: projectReducer,
firestore:firestoreReducer,
firebase:firebaseReducer
});
export default rootReducer
Check your firestore security rules.
match /users/{user} {
allow write: if request.auth != null;
allow read: if request.auth != null;
}
add this in firestore rules
match /users/{user} {
allow write
allow read: if request.auth.uid != null;
}
allow write - allows everyone to create a new user by signing up
allow read: if request.auth.uid != null - allows everyone who has logged
in to read all other users
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.