I have a react app,
I am trying to perform login using redux and PHP.
I have a component, the component contains a form. the user enters the password and email to the form. after submitting the form the data enter an async-await function called handleSubmit.
That function has another function called onSubmitLogin in the await.
from the onSubmit this goes to the actiOn creator in ajax.js file.
the next step is the API PHP code, in there the PHP function checks if the user exists.
now from there to the reducer and back to function via mapStateToProps,
I want the states notActiveUserError and UserDoesNotExist to change depending on the props (this.props.auth) value I receive from the reducer using the checkUserValidation function.
The problem I have is that the props change but the state is not changing on the first run, every other time it works amazing but it never works on the first time after page loads.
Any help would be great.
this is the code I have:
handleSubmit is in LoginForm.js (full component is at the bottom of the question)
handleSubmit = async (event) => {
await this.onSubmitLogin(event);
this.checkUserValidation();
}
onSubmitLogin is in LoginForm.js (full component is at the bottom of the question)
onSubmitLogin(event){
event.preventDefault();
if(this.clientValidate()){
this.clientValidate();
}else{
let userData ={
email: this.state.email,
password: this.state.password
}
this.props.userLogin(userData);
}
}
the action creator
export const userLogin = (userData) => {
return (dispatch) => {
axios({
url: `${API_PATH}/users/Login.php`,
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(userData)
})
.then(function(response) {
dispatch({ type: USER_LOGIN, value: response.data });
})
.catch(function(error) {
console.log(error);
});
}
}
LoginForm component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect, Link } from 'react-router-dom';
import {
Button,
Form,
FormGroup,
FormControl,
Col,
Alert,
Grid,
Row
} from 'react-bootstrap';
import { userLogedIn } from '../../actions';
import { userLogin } from '../../actions/ajax';
class LoginForm extends Component {
constructor() {
super();
this.state={
email: '',
username: '',
password: '',
auth: false,
usernameError: '',
passwordError: '',
EmptyUsernameError: '',
EmptyPasswordError: '',
notActiveUserError: '',
UserDoesNotExist: '',
userid: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
clientValidate = () => {
let isError = false;
if(this.state.email === ''){
this.setState({EmptyUsernameError: 'לא הזנתם דואר אלקטרוני'});
}
if(this.state.password === ''){
isError = true;
this.setState({EmptyPasswordError: 'לא הזנתם סיסמה'});
}
return isError;
}
checkUserValidation(){
if(this.props.auth === false && this.props.userid !== undefined){
console.log('this.props 1', this.props);
this.setState({notActiveUserError: 'חשבון לא מאומת'});
}
if(this.props.auth === false && this.props.userid === undefined){
console.log('this.props 2', this.props);
this.setState({UserDoesNotExist: 'משתשמ לא קיים'});
}
}
onSubmitLogin(event){
event.preventDefault();
if(this.clientValidate()){
this.clientValidate();
}else{
let userData ={
email: this.state.email,
password: this.state.password
}
this.props.userLogin(userData);
}
}
handleSubmit = async (event) => {
await this.onSubmitLogin(event);
this.checkUserValidation();
}
redirectUser = () => {
if(this.props.auth === true && this.props.userid != null){
const timestamp = new Date().getTime(); // current time
const exp = timestamp + (60 * 60 * 24 * 1000 * 7) // add one week
let auth = `auth=${this.props.auth};expires=${exp}`;
let userid = `userid=${this.props.userid};expires=${exp}`;
document.cookie = auth;
document.cookie = userid;
return <Redirect to='/records/biblist' />
}
}
onChange(event){
this.setState({
[event.target.name]: event.target.value,
auth: false,
usernameError: '',
EmptyPasswordError: '',
EmptyUsernameError: '',
notActiveUserError: '',
UserDoesNotExist: ''
})
}
isLoggedIn = () =>{
console.log(' this.props.auth ', this.props.auth);
}
render() {
this.isLoggedIn();
return (
<Form>
<FormGroup controlId="formHorizontalusername">
<Col xs={12} sm={5} style={TopMarginLoginBtn}>
<Row style={marginBottomZero}>
<FormControl ref="email" name="email" type="email" onChange={this.onChange} placeholder="דואר אלקטרוני" aria-label="דואר אלקטרוני"/>
</Row>
</Col>
<Col xs={12} sm={4} style={TopMarginLoginBtn}>
<Row style={marginBottomZero}>
<FormControl ref="password" name="password" type="password" onChange={this.onChange} placeholder="הקלד סיסמה" aria-label="סיסמה"/>
</Row>
</Col>
<Col xs={12} sm={3} style={TopMarginLoginBtn} >
<Button onClick={this.handleSubmit} type="submit" className="full-width-btn" id="loginSubmit">התחבר</Button>
{this.redirectUser()}
</Col>
<Col xs={12}>
<Link to="/passwordrecovery">שכחתי את הסיסמה</Link>
</Col>
</FormGroup>
{
this.state.EmptyUsernameError ?
<Alert bsStyle="danger"> {this.state.EmptyUsernameError} </Alert> :
''
}
{
this.state.EmptyPasswordError ?
<Alert bsStyle="danger"> {this.state.EmptyPasswordError} </Alert> :
''
}
{
this.state.usernameError ?
<Alert bsStyle="danger"> {this.state.usernameError} </Alert> :
''
}
{
//PROBLEM!! state updates before props
this.state.notActiveUserError ?
<Alert bsStyle="danger">{this.state.notActiveUserError}</Alert> :
''
}
{
//PROBLEM!! state updates before props
this.state.UserDoesNotExist ?
<Alert bsStyle="danger">{this.state.UserDoesNotExist} </Alert> :
''
}
<Row className="show-grid">
</Row>
</Form>
);
}
}
const bold={
fontWeight: 'bolder'
}
const mapDispatchToProps = dispatch => {
return {
userLogedIn: (params) => dispatch(userLogedIn(params))
};
};
const mapStateToProps = state => {
return {
userid: state.authReducer.userid,
auth: state.authReducer.auth,
email: state.authReducer.email
}
}
export default connect(mapStateToProps, {userLogedIn, userLogin})(LoginForm);
If you want to use async-await in your component then you have to move your API call to your component because when you call the action from component it doesn't return data back to your component.
if you want to use redux then I suggest you should remove async-await from your component it won't work, instead use the redux state to store success or failed state and handle that change in your component from getDerivedStateFromProps
export const userLogin = (userData) => {
return (dispatch) => {
dispatch({ type: USER_LOGIN_BEGIN }); // reset error/login state
axios({
url: `${API_PATH}/users/Login.php`,
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(userData)
})
.then(function(response) {
dispatch({ type: USER_LOGIN, value: response.data });
})
.catch(function(error) {
dispatch({ type: USER_LOGIN_FAILED, value: error });
});
}
}
in your component
onSubmitLogin(event){
event.preventDefault();
if(!this.clientValidate()){
let userData ={
email: this.state.email,
password: this.state.password
}
this.props.userLogin(userData);
}
}
handleSubmit = (event) => {
this.onSubmitLogin(event);
// this.checkUserValidation // move this logic to reducer and set error there according to response
}
static getDerivedStateFromProps(nextProps, prevState) {
// handle success/error according to your need and return update state
}
Related
In this page the user can login, but if the untilDate is bigger than the current date it should log out the user. The code runs fine 1/2 times, the other giving me the error on the title.
I am working with createContext for user login. This is the AuthContext file
import React from "react";
import { createContext, useEffect, useReducer } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null,
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null,
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload,
};
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser,
};
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
When the user clicks the login button, it runs the handleClick function:
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
let date = new Date().toJSON();
let userdate = date;
try {
const res = await axios.post("/auth/signin", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
userdate = user.activeUntil;
//do if date is <=current datem dispatch logout
} catch (err) {
if (userdate > date) {
console.log("undefined data");
} else {
dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
}
}
if (userdate > date) {
dispatch({ type: "LOGOUT" });
console.log("If you are seeing this your contract has expired");
} else {
// navigate("/myinfo");
}
};
The console error happens from this line dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
Is there a way I can bypass this error or a different way I can write my code to make it work?
This is the full code of login page
import React from "react";
import axios from "axios";
import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import { AuthContext } from "../../context/AuthContext";
import {
Container,
FormWrap,
FormContent,
Form,
FormInput,
FormButton,
Icon,
FormH1,
SpanText,
IconWrapper,
IconL,
} from "./signinElements";
import Image from "../../images/Cover.png";
const Login = () => {
const [credentials, setCredentials] = useState({
namekey: undefined,
password: undefined,
});
/* */
// to view current user in console
const { user, loading, error, dispatch } = useContext(AuthContext);
let msg;
const navigate = useNavigate();
const handleChange = (e) => {
setCredentials((prev) => ({ ...prev, [e.target.id]: e.target.value }));
};
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
let date = new Date().toJSON();
let userdate = date;
try {
const res = await axios.post("/auth/signin", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
userdate = user.activeUntil;
//do if date is <=current datem dispatch logout
} catch (err) {
if (userdate > date) {
console.log("undefined data");
} else {
dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
}
}
if (userdate > date) {
dispatch({ type: "LOGOUT" });
console.log("If you are seeing this your contract has expired");
} else {
// navigate("/myinfo");
}
};
// console.log(user.activeUntil); //type to view current user in console
return (
<>
<Container>
<IconWrapper>
<IconL to="/">
<Icon src={Image}></Icon>
</IconL>
</IconWrapper>
<FormWrap>
<FormContent>
<Form action="#">
<FormH1>
Sign in with the namekey and password written to you on your
contract.
</FormH1>
<FormInput
type="namekey"
placeholder="Namekey"
id="namekey"
onChange={handleChange}
required
/>
<FormInput
type="password"
placeholder="Password"
id="password"
onChange={handleChange}
/>
<FormButton disabled={loading} onClick={handleClick}>
Login
</FormButton>
<SpanText>{msg}</SpanText>
{error && <SpanText>{error.message}</SpanText>}
{error && (
<SpanText>
Forgot namekey or password? Contact our support team +355 69
321 5237
</SpanText>
)}
</Form>
</FormContent>
</FormWrap>
</Container>
</>
);
};
export default Login;
The problem was i was trying to call a localy stored user and 1 time it wasnt loaded and the other it was. Simply fixed it by changing the if statement to check directly in result details without having to look in local storage.
const [expired, setExpired] = useState(false);
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/auth/signin", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let date = new Date().toJSON();
if (res.data.details.activeUntil < date) {
dispatch({ type: "LOGOUT" });
console.log("Users contract has expired");
setExpired(!expired);
} else {
navigate("/myinfo");
}
} catch (err) {
dispatch({ type: "LOGIN_FAILURE", payload: err.response.data });
}
};
I am using Redux with ReactJS. I also am utilizing devise-JWT for auth. Upon a refresh of the page, my state is changed and loggedIn becomes "false". I also get 400 Bad Request error upon posting data through fetch. I can login just fine and be redirected.
My user reducer:
import {
SIGNUP_USER,
LOGIN_USER,
LOGOUT_USER,
STORE_TOKEN
} from '../actions/types'
const INITIAL_STATE = {
loggedIn: false,
currentUser: {}
}
export default (state = INITIAL_STATE, action) => {
switch(action.type){
case SIGNUP_USER:
return {
...state,
loggedIn: true,
currentUser: action.payload
}
case LOGIN_USER:
return {
...state,
loggedIn: true,
currentUser: action.payload
}
case LOGOUT_USER:
return {
...state,
user: state.users.filter(user => user.id !== action.payload.id),
loggedIn: false
}
case STORE_TOKEN:
return {
token: action.payload.token,
}
default:
return state
}
}
My action:
export function loginUser(data){
return (dispatch) => {
fetch("http://localhost:3000/login", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user: {
...data
}
})
})
.then(resp => {
//debugger
if (resp.ok) {
resp.json().then(json => {
localStorage.setItem('token', json.token)
dispatch({ type: LOGIN_USER, payload: json })
})
}
})
}
}
My login component:
import React from 'react'
import TextField from '#material-ui/core/TextField'
import Button from '#material-ui/core/Button'
import { loginUser } from '../actions/index'
import { connect } from 'react-redux'
class Login extends React.Component {
state = {
email: '',
password: ''
}
handleSubmit = (e) =>{
e.preventDefault()
let credentials = this.state
this.props.loginUser(credentials)
this.props.history.push('/bookmarks')
}
handleChange = (e) => {
this.setState({[e.target.name]: e.target.value})
}
render() {
const { email, password } = this.state
return (
<div className="login-form">
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
<div>
<TextField type="text" name="email" placeholder="Email" onChange={this.handleChange} value={email} />
</div>
<div>
<TextField type="password" name="password" placeholder="Password" onChange={this.handleChange} value={password}/>
</div><br></br>
<Button type="submit" value="Login">Login</Button>
</form>
</div>
)
}
}
const mapDispatch = (dispatch) => {
return {
loginUser: (credentials) => dispatch(loginUser(credentials))
}
}
export default connect(null, mapDispatch)(Login)
I used devise-JWT for authentication.
You're logged out because the only place you store the credentials is in the redux store, which is (essentially) a variable.
When you refresh the page, you reinitialise the store to the default state.
You haven't stored the credentials anywhere where they would persist, such as local storage or a cookie.
Ok..first things first:
Please refer the image of webApp attached:
My Application displays loginIdCard's consisting(website,username,password) from mongoDb which
i can edit from react when clicking on edit button.
What i did is initially i maintained a editMode key in component state a set it as false.
when a user clicks on edit button the LoginIdCard becomes editable and on clicking save button new values are set in component state and then editLoginId function is dispatch which updates this new value in database.
Now,
following are the things i want:
Initially when edit button is clicked, the value inside the input field should be the original values,
but now it is show empty.
2.The new values should be displayed immediately without rerendering of component.
Note: Now,after cliciking on save button , the component rerenders and the endpoint api return res data which is not a array, so the LoginDisplay component is not able to map and gives this.map is not a function error.
Please Help me
Web app rendering LoginIdCard in LoginDisplay Component
"LoginDispaly Component:Here LoginIdCard Component Will Rendender"
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import LoginIdCard from "./LoginIdCard";
import EditLoginIdComponent from "./EditLoginIdComponent";
import { fetchLoginIds } from "../actions/loginInIdsAction";
import "../css/displaySection.css";
class LoginsDisplay extends Component {
componentWillMount() {
this.props.fetchLoginIds();
}
render() {
const { logins } = this.props;
return (
<div className="display-section">
{logins.map((logins) => (
<LoginIdCard logins={logins} />
))}
</div>
);
}
}
function mapStateToProps(state) {
return {
logins: state.logins.logins,
};
}
LoginsDisplay.propTypes = {
logins: PropTypes.array.isRequired,
};
export default connect(mapStateToProps, { fetchLoginIds })(LoginsDisplay);
"LoginIdCard Component it will be render in LoginDispaly Component"
import React, { Component } from "react";
import { connect } from "react-redux";
import { editLoginId } from "../actions/loginInIdsAction";
import "../css/card.css";
class LoginIdCard extends Component {
constructor(props) {
super(props);
this.state = {
website: "",
username: "",
password: "",
editMode: false,
};
this.handleChange = this.handleChange.bind(this);
// this.handleSave = this.handleChange.bind(this);
}
handleChange = (fieldName, val) => {
console.log(val);
this.setState({
[fieldName]: val,
});
};
handleSave = () => {
const { website, username, password } = this.state;
const { logins } = this.props;
this.props.dispatch(editLoginId(website, username, password, logins._id));
console.log(this.state.website, username, password, logins._id);
};
render() {
const { editMode } = this.state;
const { logins } = this.props;
// const website = logins.website;
// const username = logins.username;
// const password = logins.password;
return (
<div className="card">
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("website", e.target.value)}
value={this.state.website}
/>
) : (
<p>{this.state.website}</p>
)}
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("username", e.target.value)}
value={this.state.username}
/>
) : (
<p>{logins.username}</p>
)}
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("password", e.target.value)}
value={this.state.password}
/>
) : (
<p>{logins.password}</p>
)}
{editMode ? (
<button onClick={this.handleSave}>save</button>
) : (
<button onClick={() => this.handleChange("editMode", true)}>
edit
</button>
)}
</div>
);
}
}
// this.handleChange("editMode", false)
function mapStateToProps(state) {
return {
// user: state.user.users,
// cards: state.cards.cards,
logins: state.logins.logins,
};
}
// App.propTypes = {
// user: PropTypes.array.isRequired,
// };
export default connect()(LoginIdCard);
"redux action file for editing the LoginId in mongodb"
export function editLoginId(website, username, password, id) {
return function (dispatch) {
const req = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
website: website,
username: username,
password: password,
cardId: id,
}),
};
fetch("http://localhost:9000/editLoginId", req)
.then((res) => res.json())
.then((data) =>
dispatch({
type: EDIT_LOGIN_ID,
payload: data,
})
)
.catch((err) => {
console.log(err);
<!-- begin snippet: js hide: false console: true babel: false -->
});
};
}
I have just started learning ReactJS and I decided to use new context API in the ReactJS to manage the state in the project I am building while learning.
Here is the context.js code,
import React, { Component } from "react";
import axios from "axios";
const Context = React.createContext();
const reducer = async (state, action) => {
switch (action.type) {
case "USER_LOGIN":
const { token } = action.payload;
return { ...state, user: { token } };
case "GET_USER_DATA":
const url = "api/users/dashboard";
const userToken = action.payload.token;
let res = await axios.get(url, {
headers: {
Authorization: userToken
}
})
let urls = res.data.urls;
urls = urls.map(url => ( { ...url,shortUrl: axios.defaults.baseURL + "/" + url.urlCode} ) )
return { ...state, user: { token } };
}
};
export class Provider extends Component {
state = {
user: {
token: "",
data: [{id: 'adsasd'}]
},
dispatch: action => {
this.setState(state => reducer(state, action));
}
};
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
I have two types of action here one is for login and one is for fetching the user data based on the JWT token received after a successful login.
Here is my login component
import React, { Component } from "react";
import { Row, Col, Input, Icon, CardPanel, Button } from "react-materialize";
import axios from 'axios'
import { Consumer } from '../store/context'
class Login extends Component {
state = {
errors: {
name: "",
password: ""
}
};
constructor(props) {
super(props);
this.emailInputRef = React.createRef();
this.passwordInputRef = React.createRef();
}
login = async (dispatch) => {
const email = this.emailInputRef.state.value;
const password = this.passwordInputRef.state.value;
if (typeof password != "undefined" && password.length < 6) {
this.setState({ errors: { password: "Password length must be atleast 6 characters!" } })
}
else {
this.setState({ errors: { password: "" } })
}
if (typeof email != "undefined") {
if (!validateEmail(email)) {
console.log('invalid email');
this.setState({ errors: { email: "Invalid email address!" } })
}
else {
this.setState({ errors: { email: "" } })
}
}
else {
this.setState({ errors: { email: "Invalid email address!" } })
}
// console.log(this.state.errors);
if ((email !== "" || typeof email !== "undefined") && (password !== "" || typeof password !== "undefined")) {
const res = await axios.post('/api/users/login', {
'email': email,
'password': password
})
dispatch({
type: 'USER_LOGIN',
payload: {
token: res.data.data.token
}
})
this.props.history.push('/dashboard')
}
}
render() {
const { errors } = this.state;
return (
<Consumer>
{value => {
const { dispatch } = value
return (
<CardPanel className="bg-primary" style={{ padding: "20px 5%" }}>
<Row className="login">
<h1 style={{ color: "white" }}>Login</h1>
<Col s={12} m={12}>
<Input
s={12}
m={12}
name="email"
error={errors.email}
className="error"
label="Email"
ref={ref => this.emailInputRef = ref}
>
<Icon>account_circle</Icon>
</Input>
<Input
s={12}
m={12}
name="password"
error={errors.password}
label="Password"
type="password"
ref={ref => this.passwordInputRef = ref}
>
<Icon>lock</Icon>
</Input>
<Button onClick={this.login.bind(this, dispatch)} style={{ marginTop: "20px" }} waves="yellow">
Login
</Button>
</Col>
</Row>
</CardPanel>
)
}}
</Consumer>
);
}
}
function validateEmail(sEmail) {
const reEmail = /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+#(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/
if (sEmail === "") return false;
return reEmail.test(sEmail);
}
function isEmpty(obj) {
if (obj == null) return true;
return Object.entries(obj).length === 0 && obj.constructor === Object;
}
export default Login;
What I want to achieve is that when a user tries to log in, I make a request to the backend and receive the JWT token then I dispatch a login action in context.js to store the token for future use. After that, I redirect the user to the dashboard where he can get the data he had generated, To get the data I again make an AJAX request to the backend with the stored JWT token in the context. I do that inside the componentDidMount() method, But I always receive the empty object when I try to access the context data. Here is the dashboard
Dashboard.jsx
import React, { Component } from 'react'
import axios from 'axios'
import 'react-bootstrap-table-next/dist/react-bootstrap-table2.min.css';
import BootstrapTable from 'react-bootstrap-table-next';
import overlayFactory from 'react-bootstrap-table2-overlay';
import { Consumer } from '../store/context'
const columns = [
{
dataField: 'url',
text: 'URLs'
},
{
dataField: 'hits',
text: 'Hits'
},
{
dataField: 'shortUrl',
text: 'Short URL'
},
{
dataField: 'createdDate',
text: 'Date'
},
];
export default class Dashboard extends Component {
state = {
data: []
}
componentDidMount() {
// const url = 'api/users/dashboard'
const context = this.context
console.log(context); // always empty
}
render() {
return (
<Consumer>
{value => {
const { user } = value
return (
isEmpty(user) ? <h3 className="center-align">Please Login To View Dashboard...</h3> : (
< BootstrapTable keyField='shortUrl'
data={this.state.data}
columns={columns}
bordered={true}
hover={true}
/>
)
)
}}
</Consumer>
)
}
}
function isEmpty(obj) {
if (obj == null) return true;
return Object.entries(obj).length === 0 && obj.constructor === Object;
}
By default, this.context is undefined. In order for it to be populated, you need to tell react what to populate it with. Assuming you're on react 16.6 or later, that will look like this:
// In context.js, you must export the entire context, not just the consumer
export const Context = React.createContext();
// In Dashboard.jsx, you must import the context, and add a static contextType property to your component
import { Context } from '../store/context';
export default class Dashboard extends Component {
static contextType = Context;
componentDidMount() {
console.log(this.context);
}
}
I'm developing a mobile application by use react-native and redux,thunk and it's the first time I write by react-native.
My problem is I call an api and the response is valid, I want to do somethings as update UI, navigate to new screen... for do that I will need to used flag in my component to mark it.
This is login example, after user login success, i want to navigate to Home screen. for do that, i need check an flag isLoginSuccess in props on the method componentWillReceiveProps to know user have been login success or not, but i think it's not good solution.
My question is we have other way to do it without use flag.
action.js
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGIN = "LOGIN";
export function login(username, password) {
console.log(username)
return {
type: LOGIN,
username: username,
password: password
}
}
export function loginSuccess(data) {
return {
type: LOGIN_SUCCESS,
loginData: data
}
}
export function loginFail(error) {
return {
type: LOGIN_FAIL,
error: error
}
}
export function doLogin(username, password) {
return (dispatch) => {
dispatch(login(username, password))
api.login(username, password)
.then(response => response.json())
.then(jsonData => {
console.log(jsonData)
dispatch(loginSuccess(jsonData))
})
.catch((error) => {
dispatch(loginFail(error))
})
}
}
reducer.js
const initialState = {
loginData:{},
isLoginDoing : false,
isLoginSuccess : false,
username :"",
password : "",
error : {},
}
export default function(state = initialState , action ={}){
switch(action.type){
case actionType.LOGIN:{
return {
...state,
username: action.username,
password: action.password,
isLoginDoing : true
}
}
case actionType.LOGIN_SUCCESS:{
return {
...state,
loginData: action.loginData,
isLoginDoing : false,
isLoginSuccess : true
}
}
case actionType.LOGIN_FAIL:{
return {
...state,
isLoginDoing : false,
isLoginSuccess : false,
error : action.error
}
}
default :{
return state
}
}
}
component.js
import { connect } from "react-redux"
import { bindActionCreators } from 'redux';
import { doLogin } from '../actions'
import BaseComponent from './baseComponent'
class Login extends BaseComponent {
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
}
this.functionLogin = this.functionLogin.bind(this);
}
functionLogin() {
const { username, password } = this.state;
if(!this.props.loginReducer.isLoginDoing){
this.props.doLogin(username, password)
}
}
componentWillReceiveProps (nextProps) {
console.log("componentWillReceiveProps");
const { navigate, goBack } = this.props.navigation;
if(nextProps.loginReducer.isLoginSuccess){
// this._navigateTo('Home')
navigate('Home',nextProps.loginReducer.loginData);
}
}
render() {
const { navigate, goBack } = this.props.navigation;
return (
<View style={{ backgroundColor: 'color', marginTop: 10 }} >
<TextInput
style={{ height: 40 }}
placeholder="Username"
onChangeText={value => this.setState({ username: value })}
/>
<TextInput
style={{ height: 40 }}
placeholder="Password"
onChangeText={value => this.setState({ password: value })}
/>
<Button
onPress={this.functionLogin}
title="Login"
color="#841584"
/>
</View>
);
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
loginReducer: state.loginReducer
};
}
function mapDispatchToProps(dispatch) {
return {
doLogin: (username, password) => dispatch(doLogin(username, password))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
Thanks
In your function doLogin, you can dispatch a navigation action after dispatch(loginSuccess(jsonData)).
For example for react-navigation (if you have integrated it with redux, if it's not the case, see https://reactnavigation.org/docs/guides/redux):
dispatch(NavigationActions.navigate({routeName: 'Home'});
(Don't forget import { NavigationActions } from 'react-navigation';)