I dont understand why does the below error keeps happening, so I could really use some help!
Error:
Login.jsx:10 Uncaught TypeError: Cannot destructure property 'user' of '(0 , react__WEBPACK_IMPORTED_MODULE_1__.useContext)(...)' as it is undefined.
at Login (Login.jsx:10:1)
Context.js:
import { createContext, useEffect, useReducer } from "react";
import Reducer from "./Reducer";
const INITIAL_STATE = {
user: null,
isFetching: false,
error: false,
};
export const Context = createContext(INITIAL_STATE);
export const ContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, INITIAL_STATE);
return (
<Context.Provider
value = {{
user: state.user,
isFetching: state.isFetching,
error: state.error,
dispatch,
}}
>
{children}
</Context.Provider>
);
};
Login.js:
import axios from 'axios';
import { useContext, useRef } from 'react';
import { Link } from 'react-router-dom';
import { Context } from '../../context/Context';
import './login.css';
export default function Login() {
const userRef = useRef();
const passwordRef = useRef();
const { user, dispatch, isFetching } = useContext(Context); //error
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: 'LOGIN_START' });
try {
const res = await axios.post(`http://localhost:5000/api/auth/login`, {
username: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: 'LOGIN_SUCCESS', payload: res.data });
} catch (err) {
dispatch({ type: 'LOGIN_FAILURE' });
console.log(err);
}
};
console.log(user);
return (
<div className='login'>
<span className='loginTitle'>Login</span>
<form className='loginForm' onSubmit={handleSubmit}>
<label>Username</label>
<input
type='text'
className='loginInput'
placeholder='Enter your username...'
ref={userRef}
/>
<label>Password</label>
<input
type='password'
className='loginInput'
placeholder='Enter your password...'
ref={passwordRef}
/>
<button className='loginButton' type='submit' disabled={isFetching}>
Login
</button>
</form>
<button className='loginRegisterButton'>
<Link className='link' to='/register'>
Register
</Link>
</button>
</div>
);
}
Did you wrap the parent of the Login component with the ContextProvider?
import { ContextProvider } from '../../context/Context';
import Login from "..."
function App() {
return(
<>
<ContextProvider>
<Login>
//any other child over here can access the context from this provider
</ContextProvider>
</>
)
}
export default App;
Related
I don't know why it shows "{
userInfo: undefined
}", testing with postman making a post request was successful and I can get users info including the token but nothing shows on the redux devTools. Also when I try to login it says "Unsupported media type "application/x-www-form-urlencoded, application/json" in request." I don't know if it's related or not..
userReducers.js:
export const userLoginReducers = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { laoding: true };
case USER_LOGIN_SUCCESS:
return { laoding: false, userInfo: action.payload };
case USER_LOGIN_FAIL:
return { laoding: false, error: action.payload };
case USER_LOGOUT:
return {};
default:
return state;
}
};
userActions.js:
export const login = (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-type": "application/json",
},
};
const { data } = await axios.post(
"/api/users/login/",
{ username: email, password: password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.detail
? error.response.data.detail
: error.message,
});
}
};
Store.js:
import {
legacy_createStore as createStore,
combineReducers,
applyMiddleware,
} from "redux";
//import { configureStore } from "#reduxjs/toolkit";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import {
productListReducers,
productDetailsReducers,
} from "./reducers/ProductReducers";
import { cartReducer } from "./reducers/CartReducers";
import { userLoginReducers } from "./reducers/UserReducers";
const reducer = combineReducers({
productList: productListReducers,
productDetails: productDetailsReducers,
cart: cartReducer,
userLogin: userLoginReducers,
});
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 },
userLogin: { userInfo: userInfoFromStorage },
};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
LoginScreen.js:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { Row, Col, Button, Form } from "react-bootstrap";
//import products from "../../products";
import Message from "../Message";
import Loader from "../Loader";
import { useNavigate, useLocation, useSearchParams } from "react-router-dom";
import { login } from "../../actions/UserActions";
import LoginForm from "../LoginForm";
function LoginScreen() {
const [searchParams, setSearchParams] = useSearchParams();
const { search } = useLocation();
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const redirect = searchParams.get(search.split("=")) || 1;
const userLogin = useSelector((state) => state.userLogin);
const { error, loading, userInfo } = userLogin;
useEffect(() => {
if (userInfo) {
navigate(redirect);
setSearchParams(searchParams);
}
}, [navigate, userInfo, redirect, searchParams, setSearchParams]);
const submitHandler = (e) => {
e.preventDefault();
dispatch(login(email, password));
};
return (
<LoginForm>
<h1>Sign In</h1>
{error && <Message variant="danger">{error}</Message>}
{loading && <Loader />}
<Form onSubmit={submitHandler}>
<Form.Group>
<Form.Label>Email Address</Form.Label>
<Form.Control
type="email"
placeholder="Enter Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
></Form.Control>
</Form.Group>
<Form.Group controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Enter Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
></Form.Control>
</Form.Group>
<Button type="submit" className="mt-3" variant="primary">
Sign In
</Button>
</Form>
<Row className="py-3">
<Col>
New Customer?
<Link to={redirect ? `/register?redirect=${redirect}` : "/register"}>
Register
</Link>
</Col>
</Row>
</LoginForm>
);
}
export default LoginScreen;
It is my Auth Login form. I have AuthContext. Let's try log in.
I've clicked "log in" button. And we see AuthContext "user" : underfind
Then click reload page, and here we go, we have logged in
Why does it work like that?
Login.js
import React, {useState} from 'react'
import {useLogin} from '../hooks/useLogin'
import { useNavigate } from 'react-router-dom'
const Login = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const {login, error, isLoading} = useLogin()
let navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
await login(email, password)
}
return(
<div className='container'>
<h1>
login page
</h1>
<form className={'d-flex flex-column'} onSubmit={handleSubmit}>
<h3>Log in</h3>
<label>Email:</label>
<input
type={'email'}
onChange={(e) => setEmail(e.target.value)}
/>
<label>Password</label>
<input
type={'password'}
onChange={(e) => setPassword(e.target.value)}
/>
<button disabled={isLoading} type={'submit'}>
Log in
</button>
{error && <div className={'error'}>
{error}
</div>}
</form>
</div>
)
}
export default Login;
useLogin.js
import { useState } from "react";
import { useAuthContext } from "./useAuthContext";
export const useLogin = () => {
const [error, setError] = useState(null)
const [isLoading, setIsLoading] = useState(null)
const {dispatch} = useAuthContext()
const login = async (email, password) => {
setIsLoading(true)
setError(null)
const response = await fetch('/api/user/login', {
method:'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, password})
})
const json = await response.json()
if(!response.ok){
setIsLoading(false)
setError(json.error)
}
if(response.ok){
localStorage.setItem('user', JSON.stringify(json))
dispatch({type:'LOGIN', paylaod:json})
setIsLoading(false)
}
}
return {login, isLoading, error}
}
AuthContext.js
import { createContext, useReducer, useEffect } from "react";
export const AuthContext = createContext()
export const authReducer = (state, action) => {
switch(action.type){
case 'LOGIN':
return {user: action.payload}
case 'LOGOUT':
return {user:null}
default:
return state
}
}
export const AuthContextProvider = ({children}) => {
const [state, dispatch] = useReducer(authReducer, {
user:null
})
useEffect(() => {
const user = JSON.parse(localStorage.getItem('user'))
if(user){
dispatch({type:'LOGIN', payload: user})
}
}, [])
console.log('AuthContext state', state)
return(
<AuthContext.Provider value={{...state, dispatch}}>
{children}
</AuthContext.Provider>
)
}
useAuthContext.js
import { AuthContext } from "../context/AuthContext";
import { useContext } from "react";
export const useAuthContext = () => {
const context = useContext(AuthContext)
if(!context){
throw Error('useAuthContext must be used inside an AuthContextProvider')
}
return context
}
here is Navbar.js
import React from "react";
import { useAuthContext } from "../hooks/useAuthContext";
import { useLogout } from "../hooks/useLogout";
import { Link } from "react-router-dom";
const Nav = () => {
const {logout} = useLogout()
const {user} = useAuthContext()
const handleClick= () => {
logout()
}
return(
<div>
{user && (
<div className="bg-dark text-light align-item-center d-flex justify-content-between m-auto container-fluid p-2">
<span className="m-0 p-0 d-flex align-item-center">{user.email}</span>
<button className="btn btn-outline-danger" onClick={handleClick}>logout</button>
</div>
)}
{!user && (
<div className="d-flex justify-content-between">
<Link to='/'>
<button>HOME</button>
</Link>
<Link to='/login'>
<button>LOGIN</button>
</Link>
<Link to='/signup'>
<button>SIGNUP</button>
</Link>
</div>
)}
</div>
)
}
export default Nav
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { AuthContextProvider } from './context/AuthContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<AuthContextProvider>
<App />
</AuthContextProvider>
</React.StrictMode>
);
app.js
import {BrowserRouter, Routes, Route} from 'react-router-dom'
import Nav from './components/Nav';
import Home from './pages/home';
import Login from './pages/login';
import Signup from './pages/signup';
function App() {
return (
<div>
<BrowserRouter>
<Nav/>
<Routes>
<Route path={'/'} element={<Home/>}/>
<Route path={'/login'} element={<Login/>}/>
<Route path={'/signup'} element={<Signup/>}/>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
I don't think it is server error.
There's a spelling mistake in useLogin payload is written wrong
dispatch({type:'LOGIN', paylaod:json});
// Should be: Payload
dispatch({type:'LOGIN', payload:json});
The root cause seems to be a typo:
dispatch({type:'LOGIN', paylaod:json})
has a typo in paylaod, so the reducer's
case 'LOGIN':
return {user: action.payload}
basically just sets {user: undefined}.
You should add error checking to your reducer, or better yet switch to a typed language like TypeScript so typos like these are caught at type-check time.
Since the user is not in the component state, it will not re-render after it is set (which seems to happen after first rendering). Add a useEffect with dependency on the value that updates the state and it should be good. F.ex:
const Nav = () => {
const {contextUser} = useAuthContext()
const [user, setUser] = useState()
...
useEffect(() => {
if (contextUser) {
setUser(contextUser)
}
}, [contextUser])
...
}
Okay I'm trying to use global states to store if an user is logged in or not. To do so, I've created a Context file as follows:
import { createContext } from "react";
export const LoginContext= createContext({})
I also have my App.jsx:
import React, { useState } from 'react';
import Component1 from './Component1.jsx';
import Component2 from './Component2.jsx';
import Component3 from './Component3.jsx';
import { LoginContext } from '../Helper/Context.js';
function App(){
const [loggedIn, setLoggedIn] = useState(false);
return (
<LoginContext.Provider value={{loggedIn, setLoggedIn}}>
<Component1 />
<Component2 />
<Component3 />
</LoginContext.Provider>
)
}
export default App;
And then I have my Login component:
import React, {useState, useContext} from "react";
import Axios from 'axios';
import { Link, useHistory } from 'react-router-dom';
import { LoginContext } from "../Helper/Context";
import NavbarHome from "./NavbarHome";
function Login()
{
Axios.defaults.withCredentials = true;
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loginStatus, setLoginStatus] = useState(false);
const {loggedIn, setLoggedIn} = useContext(LoginContext);
const [error, setErrorStatus] = useState("");
let history = useHistory();
let user = {
id: null,
nombre: null,
email: null
};
const login = () => {
Axios.post('http://localhost:3001/login', {
email: email,
password: password,
}).then((response)=>{
if(!response.data.auth) {
setLoginStatus(false);
setErrorStatus(response.data.message)
setLoggedIn(false);
}
else{
localStorage.setItem("token", response.data.token)
user = {
id: response.data.result[0].id,
name: response.data.result[0].name,
email: response.data.result[0].email
}
setLoginStatus(true);
setLoggedIn(true);
history.push('/perfil');
}
});
};
const userAuthenticated = () => {
Axios.get("http://localhost:3001/isUserAuth", {
headers: {
"x-access-token": localStorage.getItem("token"),
},
}).then((response) => {
console.log(response);
});
}
return(
<div>
<div>
<NavbarHome />
<div>
<div>
<h1>Login</h1>
<p className="label-login">Email:</p>
<input type="text" placeholder="Email..." onChange={(event) => {setEmail(event.target.value);}}/>
<p className="label-login">ContraseƱa:</p>
<input type="password" placeholder="Password..." onChange={(event) => {setPassword(event.target.value);}}/> <br />
<button onClick={login}>Login</button>
<p style={{marginTop: '1.3rem', color: 'red'}}>{error}</p>
<p><Link to='/registro'>Register here!</Link></p>
</div>
</div>
</div>
</div>
);
}
export default Login;
The output that I receive is this:
It complains about the line where I do this: setLoggedIn(false); or setLoggedIn(true); I'd like to use that state instead of setLoginStatus (which is the one I'm currently using)
Any ideas on how to fix it?
It keeps showing me this message in react, redux app
I have tried to fix it but nothing work and actually I can't know what is the problem in my code
Unhandled Rejection (TypeError): props.setAlerts is not a function
This is my 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;
This is my function
import { SET_ALERT, REMOVE_ALERT } from './types';
import { v4 as uuidv4 } from 'uuid';
export const setAlerts = (masg, alertType) => dispatch => {
const id = uuidv4();
dispatch({
type: SET_ALERT,
payload: { masg, alertType, id }
});
};
this is my reducer
import { SET_ALERT, REMOVE_ALERT } from '../actions/types';
const initialState = [];
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_ALERT:
return [...state, payload];
case REMOVE_ALERT:
return state.filter(alert => alert.id !== payload);
default:
return state;
}
}
this is my action types
export const SET_ALERT = 'SET_ALERT';
export const REMOVE_ALERT = 'REMOVE_ALERT';
This is my component I want to use my function in
import React, { Fragment, useState } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { setAlerts } from '../../actions/alert';
export const Register = props => {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
password2: ''
});
const { name, email, password, password2 } = formData;
const onChange = e =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = async e => {
e.preventDefault();
if (password !== password2) {
props.setAlerts('Password dont match', 'danger');
} else {
console.log('Succes');
}
};
return (
<Fragment>
<section className='container'>
<h1 className='large text-primary'>Sign Up</h1>
<p className='lead'>
<i className='fas fa-user'></i> Create Your Account
</p>
<form className='form' onSubmit={e => onSubmit(e)}>
<div className='form-group'>
<input
type='text'
placeholder='Name'
name='name'
value={name}
onChange={e => onChange(e)}
required
/>
</div>
<div className='form-group'>
<input
type='email'
placeholder='Email Address'
name='email'
value={email}
onChange={e => onChange(e)}
/>
<small className='form-text'>
This site uses Gravatar so if you want a profile image, use a
Gravatar email
</small>
</div>
<div className='form-group'>
<input
type='password'
placeholder='Password'
name='password'
minLength='6'
value={password}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input
type='password'
placeholder='Confirm Password'
name='password2'
minLength='6'
value={password2}
onChange={e => onChange(e)}
/>
</div>
<input type='submit' className='btn btn-primary' value='Register' />
</form>
<p className='my-1'>
Already have an account? <Link to='/login'>Sign In</Link>
</p>
</section>
</Fragment>
);
};
export default connect(null, { setAlerts })(Register);
Try this:
const mapDispatchToProps = dispatch => {
return {
setAlerts : (masg, alertType) => { dispatch(setAlerts (masg, alertType)) }
}
}
export default connect(null,mapDispatchToProps)(Register)
When using Redux, do not export this way in the component.
Change below:
export const Register = props => {...}
To this:
const Register = props => {...}
You will need to modify this component's import in the App.js.
Change below:
import { Register } from '...';
To this:
import Register from '...';
Can you explain me what wrong with my mapStateToProps?
I have Login component form like this.
I want to get state after action send data state to store but it returns undefined (console.log(propReducerState.isAuthenticated)). Here is my Login
import React, {Fragment, useState} from 'react';
import {connect} from 'react-redux';
import {Link, withRouter} from 'react-router-dom';
import {LoginAction} from '../../actions/LoginAction';
import PropTypes from 'prop-types';
const Login = (propAction, propReducerState) => {
const [formData, setFormData] = useState({
email:'',
password:'',
isAuthenticated:null
});
const {email, password} = formData;
const onChange = e => setFormData({...formData, [e.target.name]: e.target.value});
const onSubmit = async e => {
e.preventDefault();
propAction.LoginAction({
email,
password
});
console.log(propReducerState.isAuthenticated);
}
return (
<Fragment>
<h1>Sign In</h1>
<div className="message">{formData.isAuthenticated}</div>
<form className='form' onSubmit={e => onSubmit(e)}>
<div className='form-groum'>
<input
type='text'
placeholder='Please enter your Email'
name='email'
value={email}
onChange={e => onChange(e)} />
<input
type='password'
name='password'
value={password}
onChange={e => onChange(e)} />
</div>
<input type='submit' className='btn btn-primary btn-normal' value='Login' />
</form>
</Fragment>
)
};
const stateStoreToProp = state => ({
propReducerState: state.LoginReducer
})
export default connect(
stateStoreToProp,
{LoginAction}
)(Login);
and here is my LoginReducer:
import {
LOGIN_SUCCESS, LOGIN_FAIL
} from '../actions/typeName';
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null
}
const LoginReducer = (state = initialState, action) => {
const {type, payload} = action;
switch(type) {
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token)
return {
...state,
...payload,
isAuthenticated: true,
loading: false
}
case LOGIN_FAIL:
localStorage.removeItem('token')
return {
...state,
token:null,
isAuthenticated: false,
loading: true
}
default:
return {
state
}
}
}
export default LoginReducer;
Thank you for your support.
Update: As I tested with all of your suggestions but no luck. I will add LoginAction for you to check it. Thanks
import axios from 'axios';
import {
LOGIN_SUCCESS, LOGIN_FAIL
} from './typeName';
export const LoginAction = ({email, password}) => async next => {
const config = {
headers: {
'Content-Type': 'application/json'
}
}
const body = JSON.stringify({email, password});
try {
const res = await axios.post('/api/admin/login', body, config);
next({
type: LOGIN_SUCCESS,
payload: res.data
});
}catch(err){
next({
type: LOGIN_FAIL,
});
}
}
This is how you're suppose to connect:
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
loginAction: () => dispatch(LoginAction),
}
}
Please make sure that your connect function look like this :
export default connect(
stateStoreToProp,
mapDispatchToProps
)(Login);
and not like this :
export default connect(
null,
{stateStoreToProp, LoginAction}
)(Login);
That depends on how are you using the reducer
You should have something lik this:
export default combineReducers({
test: testReducer
});
And then
const store = createStore(reducer, {});
Where reducer is the result of combineReducers
Then in mapStateToProps the result state of testReducer will be inside test property
Don't use stringify to convert the state to string, keep it as an object
Your connect() has wrong parameter list. The first has to be stateStoreToProp, as the documentation suggests.
You should export it as the following:
export default connect(
stateStoreToProp, {LoginAction}
)(Login);
Read further here: https://react-redux.js.org/api/connect#connect-parameters
I hope that helps!
I solved this problem. Problem is that I put it in form submit
The stateStore always change then I just put it under form submit and everything work well.
Also with that, the prop should be one only. I defined two props and it is not right way.
const Login = ({LoginAction, propReducerState}) => {
const [formData, setFormData] = useState({
email:'',
password:'',
isAuthenticated:null
});
const {email, password} = formData;
const onChange = e => setFormData({...formData, [e.target.name]: e.target.value});
const onSubmit = async e => {
e.preventDefault();
LoginAction({
email,
password
});
}
console.log(propReducerState);
return (
<Fragment>
<h1>Sign In</h1>
<div className="message">{formData.isAuthenticated}</div>
<form className='form' onSubmit={e => onSubmit(e)}>
<div className='form-groum'>
<input
type='text'
placeholder='Please enter your Email'
name='email'
value={email}
onChange={e => onChange(e)} />
<input
type='password'
name='password'
value={password}
onChange={e => onChange(e)} />
</div>
<input type='submit' className='btn btn-primary btn-normal' value='Login' />
</form>
</Fragment>
)
};
const stateStoreToProp = state => ({
propReducerState: state.LoginReducer
})
export default connect(
stateStoreToProp,
{LoginAction}
)(Login);