I'm trying to build simple login form (data for authorization from API). So I have Slice for auth here is code :
auth.js
import { createSlice } from '#reduxjs/toolkit'
export const authSlice = createSlice({
name: 'auth',
initialState: {
isLoggedIn: false,
token: null,
role: null
},
reducers: {
logIn: (state) => {
state.isLoggedIn = true
},
role:(state, action) =>{
state.role = action.payload
},
token:(state, action) =>{
state.token = action.payload
},
logOut: (state) => {
state.isLoggedIn = false
},
},
})
export default authSlice.reducer;
export const { logIn, logOut, role, token } = authSlice.actions
authService.js :
import axios from 'axios';
import { api } from '../api/index'
export function authenticateUser(username, password) {
axios
.post(api + 'login', {username, password})
.then(res => {
console.log(res.headers.authorization)
})
}
LoginForm.js
import React, { Component } from 'react';
import { Form, Col, Button } from 'react-bootstrap';
import { IoMdLogIn } from "react-icons/all";
import { authenticateUser } from '../services/authService'
export default class LoginForm extends Component{
constructor(props) {
super(props);
this.state = this.initialState;
this.credentialsChange = this.credentialsChange.bind(this);
this.userLogin= this.userLogin.bind(this);
}
initialState = {
username: '', password: ''
}
userLogin = (e) => {
e.preventDefault();
authenticateUser(this.state.username, this.state.password);
this.setState( () => this.initialState)
}
credentialsChange = e => {
this.setState({
[e.target.name]:e.target.value
});
}
render(){
const {username, password} = this.state;
return(
<Form onSubmit={this.userLogin} id="loginFormId">
<Form.Row>
<Form.Group as={Col} controlId="formGridCountry">
<Form.Label>Username</Form.Label>
<Form.Control required autoComplete="off"
type="text" name="username"
value={username}
onChange={this.credentialsChange}
className={"bg-light"}
placeholder="Username" />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId="formGridZipCode">
<Form.Label>Password</Form.Label>
<Form.Control required autoComplete="off"
type="password" name="password"
value={password}
onChange={this.credentialsChange}
className={"bg-light"}
placeholder="Password" />
</Form.Group>
</Form.Row>
<Button type="submit" variant="success">
<IoMdLogIn />
</Button>
</Form>
);
}
}
What I'm trying to reach is : I want to update state isLoggedIn : true after calling function authenticateUser.
I've tried to use const dispatch = useDispatch() and then calling dispatch(logIn()) but it's throwing error.
Where should I call dispatcher to update state?
You need to call the dispatcher in your AuthService.js in the api response.
Check the response, if it is ok, store it. If your redux is well implemented, it will work.
axios
.post(api + 'login', {username, password})
.then(res => {
console.log(res.headers.authorization)
//Call it here, or create a function and call it here.
})
}
If it doesn't work, please share the error with us
Related
On the first click the data array received from dispatch is empty, however if i click on the save button one more time it works perfectly fine.
Here what i get from the console from Account.js
As you can see i get the error false in data with user information which is exactly what i need.
I'm ensure what is wrong here and why it does not work on the first click.
Account.js
import React, { Component, useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
// react-bootstrap components
import {
Button,
Card,
Form,
Row,
Col,
} from "react-bootstrap";
import userActions from '../../redux/auth/actions';
const {
updateAccount,
} = userActions;
function Account() {
const loggedUser = JSON.parse(localStorage.getItem('user'));
const dispatch = useDispatch();
const history = useHistory();
const user = useSelector(
state => state.Auth
);
const [first_name, setFirstName] = useState(loggedUser.first_name);
const [last_name, setLastName] = useState(loggedUser.last_name);
const [email, setEmail] = useState(loggedUser.email);
const [password, setPassword] = useState('');
const [success, setSuccess] = useState(false);
const handleUpdateAccount = e => {
e.preventDefault();
setSuccess(false);
const obj = {first_name: first_name, last_name: last_name, email: email, password: password};
dispatch(updateAccount(obj));
console.log(user);
if(user.data) {
if(user.data.error == false) {
setSuccess(true);
}
}
};
return (
<>
<Row>
<Col offset-md="2" md="8">
<Card>
<Card.Header>
<Card.Title as="h4">Mon Compte</Card.Title>
</Card.Header>
<Card.Body>
<Form onSubmit={handleUpdateAccount}>
<Row>
<Col className="pl-1" md="4">
<Form.Group>
<label htmlFor="exampleInputEmail1">
Adresse e-mail
</label>
<Form.Control
value={email}
onChange={e => setEmail(e.target.value)}
type="email"
></Form.Control>
</Form.Group>
</Col>
<Col className="pr-1" md="5">
<Form.Group>
<label>Mot de passe</label>
<Form.Control
value={password}
onChange={e => setPassword(e.target.value)}
type="password"
></Form.Control>
</Form.Group>
</Col>
</Row>
<Row>
<Col className="pr-1" md="6">
<Form.Group>
<label>Prénom</label>
<Form.Control
value={first_name}
onChange={e => setFirstName(e.target.value)}
type="text"
></Form.Control>
</Form.Group>
</Col>
<Col className="pl-1" md="6">
<Form.Group>
<label>Nom de famille</label>
<Form.Control
value={last_name}
onChange={e => setLastName(e.target.value)}
type="text"
></Form.Control>
</Form.Group>
</Col>
</Row>
{(user.data && user.data.error) && <div className="error-message mb-2">{user.data.message}</div>}
{(success) && <div className="success-message mb-2">Succès</div>}
<Button
className="btn-fill pull-right mt-3"
type="submit"
variant="info"
disabled={user.isLoading}
>
{user.isLoading && <span>Mettre à jour...</span>}
{!user.isLoading && <span>Mettre à jour</span>}
</Button>
<div className="clearfix"></div>
</Form>
</Card.Body>
</Card>
</Col>
</Row>
</>
);
}
export default Account;
saga.js
import { all, takeEvery, put, call } from 'redux-saga/effects';
import actions from './actions';
import axios from "axios";
function update_account(data){
var actionUrl = '/pages/update_account';
const isLoggedIn = JSON.parse(localStorage.getItem('user'));
return axios ({
method: 'POST',
url: process.env.REACT_APP_API_URL + actionUrl,
data: {
data,
user_id: isLoggedIn.id
},
headers: { 'Content-Type': 'application/json;charset=UTF-8', "Access-Control-Allow-Origin": "*", "Accept": "application/json" }
});
}
function* updateAccount(payload) {
try {
const resp = yield call(update_account, payload.payload.data);
if(resp.data.json.error == false) {
if(resp.data.json.user) {
// Do something
localStorage.setItem("user", JSON.stringify(resp.data.json.user));
}
}
yield put(actions.postToApiSuccess(resp.data.json));
} catch (error) {
yield put(actions.postToApiError(error));
}
}
export default function* rootSaga() {
yield all([
takeEvery(actions.UPDATE_ACCOUNT, updateAccount),
]);
}
reducer.js
import actions from './actions';
const user = JSON.parse(localStorage.getItem("user"));
const initState = {
isLoading: false,
errorMessage: false,
data: [],
loggedUser: user
};
export default function reducer(
state = initState,
{ type, payload }
) {
switch (type) {
case actions.UPDATE_ACCOUNT:
return {
...state,
isLoading: true,
errorMessage: false
};
case actions.UPDATE_COMPANY_ACCOUNT:
return {
...state,
isLoading: true,
errorMessage: false
};
case actions.POST_TO_API:
return {
...state,
isLoading: true,
errorMessage: false
};
case actions.POST_TO_API_SUCCESS:
return {
...state,
isLoading: false,
data: payload.data,
errorMessage: false,
loggedUser: payload.data.user
};
case actions.LOGOUT_USER:
return {
...state,
loggedUser: null,
data: []
};
case actions.POST_TO_API_ERROR:
return {
...state,
isLoading: false,
errorMessage: 'There is a problem'
};
default:
return state;
}
}
actions.js
const actions = {
LOGOUT_USER: 'LOGOUT_USER',
POST_TO_API: 'POST_TO_API',
UPDATE_ACCOUNT: 'UPDATE_ACCOUNT',
UPDATE_COMPANY_ACCOUNT: 'UPDATE_COMPANY_ACCOUNT',
POST_TO_API_SUCCESS: 'POST_TO_API_SUCCESS',
POST_TO_API_ERROR: 'POST_TO_API_ERROR',
postToApi: data => {
return {
type: actions.POST_TO_API,
payload: { data },
};
},
updateAccount: data => {
return {
type: actions.UPDATE_ACCOUNT,
payload: { data },
};
},
updateCompanyAccount: data => {
return {
type: actions.UPDATE_COMPANY_ACCOUNT,
payload: { data },
};
},
logoutUser: data => {
return {
type: actions.LOGOUT_USER
};
},
postToApiSuccess: data => ({
type: actions.POST_TO_API_SUCCESS,
payload: { data },
}),
postToApiError: error => ({
type: actions.POST_TO_API_ERROR,
payload: { error },
}),
};
export default actions;
In your initialState "data" is an empty array and you are only updating it when actions.POST_TO_API_SUCCESS get trigger.
I'm doing a login page with JWT and trying to redirect the page once log in to the home using this.props.history.push("/") but I get this typeError: Cannot read property 'push'of undefined. I don't know what I am doing wrong.
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "cihemzine#gmail.com",
password: "test",
};
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
login = () => {
const { email, password} = this.state;
axios("/users/login", {
method: "POST",
data: {
email,
password
},
})
.then((response) => {
localStorage.setItem('token', response.data.token);
console.log(response.data);
this.props.history.push("/");
})
.catch((error) => {
console.log(error);
});
};
render() {
return (
<div>
<div className="container">
<input value={this.state.email} onChange={this.handleChange} className="form-control mb-2 mt-4"name="email" type="text"placeholder="Your email" />
<input value={this.state.password} onChange={this.handleChange} className="form-control mb-2"name="password" type="password" placeholder="Your password" /><br></br>
<button onClick={this.login}className="text-center btn btn-light">LOGIN</button>
</div>
</div>
)
}
}
export default Login;
If you have nothing like 'history, match etc' in your props you need to check that you use your login component as Route
<Route path ='' component={Login} />
If you don't use Login in such way and can't for some reasons.
You can use HOC or hook for you component to add history to your component.
import { useHistory } from "react-router-dom"
or
import { withRouter } from "react-router";
I am developing a login/register system using Redux,Hooks and Axios the action should fetch the backend and return a session to be updated in my reducer , after that I am trying to console.log(session) from my component the first time it is {} empty 'The initial state of session' is consoled the second time it is updated , I checked my redux state and everything works good and the state is updated from the first time so not a redux issue .The problem is in my component as I need it to wait for the Redux finishing and then console.log() , I tried to setTime() but it waits for the given time and after that It console the initial state {} again.
My code:
Component:
The problem is in the Redirect() function
import { LoginAction } from "../../Redux/Actions";
import { useForm } from "react-hook-form";
import { connect } from "react-redux";
const Login = (props) => {
let history = useHistory();
const alert = useAlert();
const { handleSubmit, register, errors } = useForm();
const onSubmit = (data) => {
const userData = {
username: data.username.toUpperCase(),
password: data.password,
};
props.login(userData);
setTimeout(1000, Redirect());
};
const Redirect = () => {
if (props.session.user) {
console.log(props.session);
sessionStorage.setItem("storedSession", props.session.user.username);
history.push("/dashboard");
} else {
alert.show(<div style={{ size: "10px" }}>{props.session.error}</div>);
}
};
return (
<div className="login">
<div className="login-form">
<h3 className="title">Sign In</h3>
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
placeholder="Enter your username"
name="username"
id="username"
ref={register({ required: true })}
/>
{errors.username && errors.username.type === "required" && (
<p className="error-before-submit">This is required</p>
)}
<input
id="password"
placeholder="Enter your password"
name="password"
type="password"
ref={register({ required: true })}
/>
{errors.password && errors.password.type === "required" && (
<p className="error-before-submit">This is required</p>
)}
<input
className="btn"
type="submit"
ref={register({ required: true })}
/>
<a href="/register" className="register-link">
Create an account
</a>
</form>
</div>
</div>
);
};
const mapStateToProps = (state) => ({
session: state.Session,
});
const mapDispatchToProps = (dispatch) => ({
login: (user) => dispatch(LoginAction(user)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Reducer:
const initialState = {
Session: {},
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case Register:
return {
...state,
Session: action.payload,
};
case Login:
return {
...state,
Session: action.payload,
};
default:
return state;
}
};
export default rootReducer;
Action:
export function LoginAction(user) {
return (dispatch) => {
Axios.post(
"/api/login",
{
username: user.username,
password: user.password,
},
{
headers: { "Access-Control-Allow-Origin": "*" },
withCredentials: true,
crossdomain: true,
}
).then((res) => {
dispatch({
type: Login,
payload: res.data,
});
});
};
}
How can I make my component take the updated state not the initial state FROM FIRST CLICK ??
You can leverage useEffect hook instead of using timeout
const {session} = props;
useEffect(()=>{
Redirect()
},[session])
I have a login component that stores the user information in the global state after a successful login. The login component is pretty straight forward. It contains a form with a handleSubmit event that calls an endpoint. Based on the result of that endpoint an action is taken. The login component looks like this.
import React, { Component } from 'react';
import { StateContext } from '../state';
import { login } from '../repositories/authenticationRepository';
class Login extends Component {
static contextType = StateContext;
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
message: '',
};
}
handleChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
}
handleSubmit = async (event) => {
event.preventDefault();
const [{}, dispatch] = this.context;
const { history } = this.props;
const { email, password } = this.state;
const isLoggedInResponse = await login({ email, password });
if (isLoggedInResponse.data.type === 'error') {
this.setState({ message: isLoggedInResponse.data.message });
return;
}
dispatch({ type: 'storeUserInformation', userInformation: isLoggedInResponse.data.message });
history.push('/');
}
render() {
const { email, password, message } = this.state;
return (
<div className="login-wrapper">
<form onSubmit={this.handleSubmit}>
<label htmlFor="email">
Email:
<input autoComplete="off" name="email" type="text" value={email} onChange={this.handleChange} />
</label>
<label htmlFor="password">
Password:
<input autoComplete="off" id="password" name="password" type="password" value={password} onChange={this.handleChange} />
</label>
{message.length > 0 && <span className="text-danger error">{message}</span> }
<input className="btn btn-secondary" type="submit" value="Submit" />
</form>
</div>
);
}
}
export default Login;
When testing it myself I can see the user information being set in the ReactJS devtools. Of course I want to test this automatically using a unit test, so I wrote the following.
jest.mock('../../repositories/authenticationRepository');
import React from 'react';
import { mount } from 'enzyme';
import Login from '../../pages/Login';
import { StateProvider } from '../../state';
import { login } from '../../repositories/authenticationRepository';
import { act } from 'react-dom/test-utils';
import history from '../../sitehistory';
import { BrowserRouter as Router } from 'react-router-dom';
import { reducer } from '../../reducer';
it('Saves the user information in the store on a succesfull login', async () => {
login.mockReturnValue(({ data: { type: 'success', message: 'Message should be stored' }}));
let initialStateMock = {}
const wrapper = mount(
<StateProvider initialState={initialStateMock} reducer={reducer}>
<Router>
<Login history={history} />
</Router>
</StateProvider>
);
let emailEvent = { target: { name: 'email', value: 'test#example.com'} }
let passwordEvent = { target: { name: 'password', value: 'password'} }
wrapper.find('input').first().simulate('change', emailEvent);
wrapper.find('input').at(1).simulate('change', passwordEvent);
const submitEvent = { preventDefault: jest.fn() }
await act(async () => {
wrapper.find('form').first().simulate('submit', submitEvent);
});
act(() => {
wrapper.update();
});
console.log(initialStateMock); // expected { userInformation: 'Message should be stored' } but got {}
});
I expect the initialStatemock to have the value of { userInformation: 'Message should be stored' }. However it still has the initial value of {}. I tried wrapper.update() to force a refresh but to no avail. What am I overlooking?
Please note that I've already checked answers in this question and nothing seems to work.
I'm using this repo as a boilerplate. Instead of firebase database, I'm trying to send username and email with the firebase auth userid , to the node server. I created an action creator signup to handle this.
This is signup.js action creator
import * as types from '../constants/action_types';
import axios from 'axios';
export const signup = (user) => {
console.log(user);
return async dispatch => {
try {
const response = await axios.get('http://localhost:5000/api/user/register', user)
const data = await {response};
dispatch({
type : types.SIGN_UP,
payload : data.fromback
})
} catch (error) {
console.lot(error)
}
}
}
Then I've connected it with the component with mapDispatchToProps., So,under the SignUpPage component, React dev tools shows signup as a function. But when it get triggers, it gives an error saying _this2.props.signup is not a function Why's that ?
This is my SignUpPage component
import React, { Component } from 'react';
import {
Link,
withRouter,
} from 'react-router-dom';
import { auth } from '../../firebase';
import * as routes from '../../constants/routes';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {signup} from './../../actions/signup';
const SignUpPage = ({ history }) =>
<div>
<h1>SignUp</h1>
<SignUpForm history={history} />
</div>
const updateByPropertyName = (propertyName, value) => () => ({
[propertyName]: value,
});
const INITIAL_STATE = {
username: '',
email: '',
passwordOne: '',
passwordTwo: '',
error: null,
};
class SignUpForm extends Component {
constructor(props) {
super(props);
this.state = { ...INITIAL_STATE };
}
onSubmit = (event) => {
const {
username,
email,
passwordOne,
} = this.state;
const {
history,
} = this.props;
auth.doCreateUserWithEmailAndPassword(email, passwordOne)
.then(authUser => {
const userid = authUser.user.uid;
const user = { email, userid };
this.props.signup(user);
})
.catch(error => {
this.setState(updateByPropertyName('error', error));
});
event.preventDefault();
}
render() {
const {
username,
email,
passwordOne,
passwordTwo,
error,
} = this.state;
const isInvalid =
passwordOne !== passwordTwo ||
passwordOne === '' ||
username === '' ||
email === '';
return (
<form onSubmit={this.onSubmit}>
<input
value={username}
onChange={event => this.setState(updateByPropertyName('username', event.target.value))}
type="text"
placeholder="Full Name"
/>
<input
value={email}
onChange={event => this.setState(updateByPropertyName('email', event.target.value))}
type="text"
placeholder="Email Address"
/>
<input
value={passwordOne}
onChange={event => this.setState(updateByPropertyName('passwordOne', event.target.value))}
type="password"
placeholder="Password"
/>
<input
value={passwordTwo}
onChange={event => this.setState(updateByPropertyName('passwordTwo', event.target.value))}
type="password"
placeholder="Confirm Password"
/>
<button disabled={isInvalid} type="submit">
Sign Up
</button>
{ error && <p>{error.message}</p> }
</form>
);
}
}
const SignUpLink = () =>
<p>
Don't have an account?
{' '}
<Link to={routes.SIGN_UP}>Sign Up</Link>
</p>
const mapDispatchToProps = dispatch => bindActionCreators({ signup }, dispatch)
export default connect(null, mapDispatchToProps)(withRouter(SignUpPage));
export {
SignUpForm,
SignUpLink,
};
Its not a prop,
you've imported it as a function,
you can directly use it as function like this
import {signup} from './../../actions/signup';
.....
auth.doCreateUserWithEmailAndPassword(email, passwordOne)
.then(authUser => {
const userid = authUser.user.uid;
const user = { email, userid };
signup(user);
})
.catch(error => {
this.setState(updateByPropertyName('error', error));
});