So, I (finally) can fetching data from jsonplaceholder using redux only. The problem is, my login won't let me logged in, saying "user not found". Before using redux, it works well. Please take a minute to see my code (and help me):
body.js
import React, {Component} from "react";
import "./style.css";
import { Switch, Route } from "react-router-dom"
import {About, Login, Register} from "../../pages"
import { connect } from "react-redux";
class Body extends Component {
constructor(props) {
super(props);
this.state = {
userData: [],
admin: [{
name: "admin",
role: "adm",
email: "adm#adm.com",
username: "admin",
password: "123"
}]
}
}
componentDidMount = () => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(data => {
const dataUser = data.map(user =>({
...user,
password: "pass",
role: "user",
}));
this.props.doFetch({
userData: [...this.state.admin, ...dataUser]
})
})
}
showPage = () => {
return (
<Switch>
<Route path="/about" children={(props) => <About {...props} listUsers={this.state.userData} />} />
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register listUsers={this.state.userData} tambahUser={this.addUsers} />
</Route>
</Switch>
)
}
render() {
return (
<>
{
this.showPage()
}
</>
);
}
}
const mapStateToProps = (state) => ({
userList: state.data.userData
})
const mapDispatchToProps = (dispatch) => ({
doFetch: (userData) => dispatch({ type: "FETCH", payload: userData })
})
export default connect(mapStateToProps, mapDispatchToProps)(Body);
login.js
import React, { Component } from 'react';
import { RowInput, Button } from '../../components';
import { connect } from "react-redux";
import { Redirect } from "react-router-dom"
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
}
}
onChangeInput = e => {
this.setState({
[e.target.name]: e.target.value
})
}
onLogin = async () => {
const { username, password } = this.state
const exist = this.props.userList.find(user => user.username === username && user.password === password)
if (exist) {
alert(`Welcome ${username}`)
this.props.doLogin(username, password)
} else alert("User not found.")
}
render() {
if (this.props.statusLogin){
return <Redirect to="/about" />
}
return (
<div className="login">
<form className="login-form" method="POST">
<div className="container-log">
<h1 className="judul">Login</h1>
<RowInput value={this.state.username} label="Username" placeholder="Username" type="text" name="username" onChange={this.onChangeInput}/>
<RowInput value={this.state.password} label="Password" placeholder="Password" type="password" name="password" onChange={this.onChangeInput}/>
<Button onClickInput={this.onLogin}>Masuk</Button>
</div>
</form>
</div>
);
}
}
const mapStateToProps = (state) => ({
statusLogin: state.auth.isLoggedIn,
usernameLogin: state.auth.username,
userList: state.data.userData
})
const mapDispatchToProps = (dispatch) => ({
doLogin: (users) => dispatch({ type: "LOGIN", payload: {username: users} })
})
export default connect(mapStateToProps, mapDispatchToProps)(Login)
getdata.js
const initialState = {
userData: []
}
const dataReducer = (state = initialState, action) => {
console.log("state: ", state);
console.log("action: ", action);
switch (action.type) {
case "FETCH":
return {
userData: [action.payload.userData]
}
default:
return state
}
}
export default dataReducer
Console log:
The array has been stored................right?
I'm seriously have no idea why my login not work? Is there something on my code that make it not work? Thank you before!
So, after three hours searching and trying, I only need to comment/delete function mapStateToProps in body.js. I assume it's because I'm calling and store data at the same time? Idk for sure either, so, if anyone know the exactly why, feel free to correct me. Thankies. Have a good day, everyone!
Related
I am trying to check if the user is logged in or not in Register.js component but I am getting undefined values of auth state in it. Please look into it if you can find where I am doing wrong. I have tried doing a lot of changes still I was not able to get it right.
Thank you for all of your help.
Ignore the context files
App.js
import { useEffect } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import "./App.css";
import { Navbar } from "./components/layout/Navbar";
import { Home } from "./components/pages/Home";
import { MyList } from "./components/favorites/MyList";
import Register from "./components/auth/Register";
import Login from "./components/auth/Login";
import { Provider } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/auth";
import { setAuthToken } from "./utils/setAuthToken";
if (localStorage.token) {
setAuthToken(localStorage.token);
}
function App() {
useEffect(() => {
store.dispatch(loadUser());
}, []);
return (
<Provider store={store}>
<>
<div>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/mylist' element={<MyList />} />
<Route path='/signin' element={<Login />} />
<Route path='/signup' element={<Register />} />
</Routes>
</div>
</>
</Provider>
);
}
export default App;
Register.js
import React, { useContext, useEffect, useState } from "react";
import { Link, Navigate, useNavigate } from "react-router-dom";
import { connect } from "react-redux";
import { register } from "../../actions/auth";
import axios from "axios";
const Register = ({ register, isAuthenticated, history, location, auth }) => {
const navigate = useNavigate();
const [user, setUser] = useState({
name: "",
email: "",
password: "",
password2: "",
});
const { name, email, password, password2 } = user;
const onFormDataChange = (e) => {
setUser({
...user,
[e.target.name]: e.target.value,
});
};
const onFormSubmit = async (e) => {
e.preventDefault();
register({ name, email, password });
};
console.log(auth);
console.log(isAuthenticated);
return (
<div>
<h1>Sign Up</h1>
<form action='' onSubmit={onFormSubmit}>
<div className='form-group'>
<label htmlFor='name'>Name</label>
<input
type='text'
name='name'
value={name}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='email'>Email</label>
<input
type='email'
name='email'
value={email}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='password'>Password</label>
<input
type='password'
name='password'
value={password}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='password2'>Password</label>
<input
type='password'
name='password2'
value={password2}
onChange={onFormDataChange}
/>
</div>
<input type='submit' value='Signup' />
<p>
Already have an account? <Link to='/signin'>Sign In</Link>
</p>
</form>
</div>
);
};
const mapStateToProps = (state) => {
const { isAuthenticated } = state.auth.isAuthenticated;
const { auth } = state.auth;
return {
isAuthenticated,
};
};
export default connect(mapStateToProps, { register })(Register);
index.js
import { combineReducers } from "redux";
import auth from "./auth";
export default combineReducers({
auth,
});
/auth/types.js
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const USER_LOADED = "USER_LOADED";
export const AUTH_ERROR = "AUTH_ERROR";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT = "LOGOUT";
export const CLEAR_ERRORS = "CLEAR_ERRORS";
actions/auth.js
import axios from "axios";
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "./types";
import { setAuthToken } from "../utils/setAuthToken";
export const loadUser = () => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (error) {
dispatch({
type: AUTH_ERROR,
});
}
};
export const register =
({ name, email, password }) =>
async (dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post("/api/users", body, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (error) {
dispatch({
type: REGISTER_FAIL,
});
}
};
export const login = (email, password) => async (dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post("/api/auth", body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (error) {
dispatch({
type: LOGIN_FAIL,
});
}
};
reducers/auth.js
/* eslint-disable default-case */
/* eslint-disable import/no-anonymous-default-export */
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "../actions/types";
const initalState = {
token: localStorage.getItem("token"),
isAuthenticated: false,
loading: true,
user: null,
};
export default function (state = initalState, action) {
const { type, payload } = action;
switch (type) {
case USER_LOADED:
return {
...state,
user: payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem("token", payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGIN_FAIL:
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: true,
};
default:
return state;
}
}
store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const intialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
intialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
I might be wrong, but in you Register.js, in mapStateToProps, you doing following thing:
const { isAuthenticated } = state.auth.isAuthenticated;
const { auth } = state.auth;
It seems like it's not correct, unless your state has following structure:
{
auth: {
isAuthenticated: { isAuthenticated: *value here* },
auth: *value here*
}
}
I think, here might be mistake, and you should destructure values following way:
const { isAuthenticated } = state.auth;
const { auth } = state;
I have created two route components, one for authenticated users and the other for guest. each of the routes checks on the redux state for the authenticated property to determine if a user is logged in or not in order to make the appropriate redirection. Now, the issues is that the checking and redirection should only happen once when a user changes the route but instead each of the route components re-renders when the authenticated property changes on the redux state. This is affecting my login flow because the guest route components automatically handles the redirection instead of the login component.
Below is code:
App.js component:
import './App.css'
import { lazy, useEffect } from 'react'
import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'
import Layout from './components/Layout'
import AuthRoute from './components/AuthRoute'
import Error from './components/ErrorPage'
import configData from './config/configData.json'
import axios from 'axios'
// import { useEffect } from 'react'
// Import Css
import './assets/css/materialdesignicons.min.css'
import './Apps.scss'
import './assets/css/colors/default.css'
import SetupBusiness from './components/Business/SetupBusiness'
import Preview from './components/Business/Preview'
import About from './components/Business/About'
import Media from './components/Business/Media'
import { logoutAction } from './state/action-creators/AuthAction'
import { connect } from 'react-redux'
import PublicRoute from './components/PublicRoute'
const Register = lazy(() => import('./controller/Register'))
const Login = lazy(() => import('./controller/Login'))
function App(props) {
useEffect(() => {
async function checkUserAuth() {
const token = localStorage.getItem('token')
if (token) {
axios
.get(`${configData.SERVER_URL}/user/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then(() => {})
.catch((error) => {
console.log('nbvhg')
props.logout()
})
}
}
checkUserAuth()
setInterval(checkUserAuth, 15000)
}, [props])
return (
<div className="App">
<Router>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<h1>Homepage</h1>} />
<Route path="/" element={<PublicRoute />}>
<Route path="/register" element={<Register />} />
<Route path="/login" element={<Login />} />
</Route>
<Route path="/business/setup" element={<AuthRoute />}>
<Route element={<SetupBusiness />}>
<Route path="about" element={<About />} />
<Route path="media" element={<Media />} />
<Route path="preview" element={<Preview />} />
</Route>
</Route>
</Route>
<Route path="*" element={<Error />} />
</Routes>
</Router>
</div>
)
}
const mapDispatchToProps = (dispatch) => {
return {
logout: () => {
return dispatch(logoutAction())
},
}
}
export default connect(null, mapDispatchToProps)(App)
PublicRoute component:
import { connect } from 'react-redux'
import { Navigate, Outlet } from 'react-router-dom'
import React from 'react'
class PublicRoute extends React.Component {
render() {
const { authenticated } = this.props
if (authenticated) {
return <Navigate to="/" />
}
return <Outlet />
}
}
const mapStateToProps = (state) => ({
authenticated: state.auth.authenticated,
})
export default connect(mapStateToProps, null)(PublicRoute)
Login component:
// React Basic and Bootstrap
import React from 'react'
import { connect } from 'react-redux'
import Cookies from 'js-cookie'
import { loginAction } from '../state/action-creators/AuthAction'
import View from '../components/Login'
class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
values: {
email_address: '',
password: '',
},
errors: {},
isSubmitting: false,
redirect: null,
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event) {
let values = Object.assign({}, this.state.values)
let target = event.target
let name = target.name
let value = target.value
if (typeof values[name] !== 'undefined') {
if (name === 'checked') {
value = target.checked
}
values[name] = value
}
this.setState({ values })
}
validation() {
const errors = {}
let values = this.state.values
//email validation
if (!values.email_address.trim()) {
errors.email_address = 'Email is required'
} else if (!/\S+#\S+\.\S+/.test(values.email_address)) {
errors.email_address = 'Email is invalid'
}
//password validation
if (!values.password.trim()) {
errors.password = 'password is required'
} else if (values.password < 8) {
errors.password = 'PassWord need to be 8 characters or more'
}
return errors
}
async handleSubmit(event) {
if (event) {
event.preventDefault()
}
this.setState(
{ isSubmitting: true, errors: this.validation() },
async () => {
if (Object.keys(this.state.errors).length === 0) {
try {
const response = await this.props.login(this.state.values)
if (response) {
const redirect = Cookies.get('redirect')
if (redirect) {
this.setState({ redirect })
Cookies.remove('redirect')
} else {
this.setState({ redirect: '/' })
}
}
} catch (error) {
console.log(error)
} finally {
// this.setState({ isSubmitting: false })
}
} else {
this.setState({ isSubmitting: false })
}
}
)
}
render() {
return (
<View
onChange={this.handleChange}
values={this.state.values}
errors={this.state.errors}
errorMessage={this.props.errorMessage}
onSubmit={this.handleSubmit}
isSubmitting={this.state.isSubmitting}
redirect={this.state.redirect}
/>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: (values) => {
return dispatch(loginAction(values))
},
}
}
const mapStateToProps = (state) => {
return {
errorMessage: state.auth.errorMessage,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
AuthRoute component
import { logoutAction } from '../state/action-creators/AuthAction'
import { connect } from 'react-redux'
import { Navigate, Outlet } from 'react-router-dom'
import React from 'react'
import Cookies from 'js-cookie'
class AuthRoute extends React.Component {
render() {
const { authenticated, logout } = this.props
if (!authenticated) {
Cookies.set('redirect', window.location.pathname, { path: '/' })
logout()
return <Navigate to="/login" />
}
return <Outlet />
}
}
const mapDispatchToProps = (dispatch) => {
return {
logout: () => {
return dispatch(logoutAction())
},
}
}
const mapStateToProps = (state) => ({
authenticated: state.auth.authenticated,
})
export default connect(mapStateToProps, mapDispatchToProps)(AuthRoute)
Login action
import { formatError, login } from './AuthService'
export const CLEAR_ERROR_MESSAGE = '[register action] clear error message'
export const LOGIN_CONFIRMED_ACTION = '[login action] confirmed login'
export const LOGIN_FAILED_ACTION = '[login action] failed login'
export const LOGOUT_ACTION = '[logout action] logout action'
function clearErrorAction() {
return {
type: CLEAR_ERROR_MESSAGE,
}
}
function confirmedLoginAction(payload) {
return {
type: LOGIN_CONFIRMED_ACTION,
payload,
}
}
function failedLoginAction(message) {
return {
type: LOGIN_FAILED_ACTION,
payload: message,
}
}
export function loginAction({ email_address, password }) {
return (dispatch) => {
return login({
email_address,
password,
})
.then((response) => {
const token = response.data.access_token
localStorage.setItem('token', token)
dispatch(confirmedLoginAction(response))
return response
})
.catch((error) => {
if (!error.response) {
dispatch(failedLoginAction('Server error'))
} else {
const errorMessage = formatError(error.response.data)
dispatch(failedLoginAction(errorMessage))
}
throw error
})
}
}
function confirmedLogoutAction() {
return {
type: LOGOUT_ACTION,
}
}
export function logoutAction() {
return (dispatch) => {
localStorage.removeItem('token')
dispatch(confirmedLogoutAction())
}
}
AuthReducer
import {
LOGIN_CONFIRMED_ACTION,
LOGIN_FAILED_ACTION,
CLEAR_ERROR_MESSAGE,
LOGOUT_ACTION,
} from '../action-creators/AuthAction'
const initialState = {
user: {
first_name: '',
last_name: '',
user_name: '',
email_address: '',
password: '',
id: '',
is_archived: '',
projects: '',
date_created: '',
},
access_token: '',
errorMessage: '',
authenticated: !!localStorage.getItem('token'),
}
export function AuthReducer(state = initialState, action) {
if (action.type === CLEAR_ERROR_MESSAGE) {
return {
...state,
errorMessage: '',
}
}
if (action.type === LOGIN_CONFIRMED_ACTION) {
return {
...state,
authenticated: true,
user: action.payload,
}
}
if (action.type === LOGIN_FAILED_ACTION) {
return {
...state,
errorMessage: action.payload,
}
}
if (action.type === LOGOUT_ACTION) {
return {
...state,
authenticated: false,
user: {
first_name: '',
last_name: '',
user_name: '',
email_address: '',
password: '',
id: '',
is_archived: '',
projects: '',
date_created: '',
},
}
}
return state
}
I am getting error "react_devtools_backend.js:3973 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method." from react because I am trying to setState after login but the the guest route component have already redirected out from the Login component before setting the state hence the error.
Thank you in advance.
It seems the issue is that the loginAction action creator dispatches a login success action and the redux state is updated, which triggers a rerender. This rerender and updated authenticated state is used by the PublicRoute component and the user is bounced to the home route "/" before the redirect state update occurs in handleSubmit of the Login component.
As far as I can tell it appears you are using the redirect state in Login to pass a redirect prop to the View component to, I assume, render a Navigate component to redirect back to the referrer route.
This extra step/state update/rerender is completely extraneous and unnecessary. It is far more common to issue an imperative navigate from the login handler upon successful authentication. For this the navigate function is used to redirect back. Since the Login component is a class component it can't use the useNavigate hook. The options here are to either convert to function component or create a custom withNavigate HOC.
Example:
import { useNavigate } from 'react-router-dom';
const withNavigate = Component => props => {
const navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
};
export default withNavigate;
Decorate the Login component with the withNavigate HOC and access the navigate function from props and issue the imperative redirect.
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import Cookies from 'js-cookie';
import { loginAction } from '../state/action-creators/AuthAction';
import View from '../components/Login';
import withNavigate from '../path/to/withNavigate';
class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
values: {
email_address: '',
password: '',
},
errors: {},
isSubmitting: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
...
async handleSubmit(event) {
if (event) {
event.preventDefault();
}
this.setState(
{ isSubmitting: true, errors: this.validation() },
async () => {
if (!Object.keys(this.state.errors).length) {
try {
const response = await this.props.login(this.state.values);
if (response) {
const redirect = Cookies.get('redirect');
this.props.navigate(redirect || "/", { replace: true });
}
} catch (error) {
console.log(error);
// didn't authenticate, clear submitting
this.setState({ isSubmitting: false });
}
} else {
this.setState({ isSubmitting: false });
}
}
);
}
render() {
return (
<View
onChange={this.handleChange}
values={this.state.values}
errors={this.state.errors}
errorMessage={this.props.errorMessage}
onSubmit={this.handleSubmit}
isSubmitting={this.state.isSubmitting}
/>
)
}
}
const mapDispatchToProps = {
login: loginAction,
};
const mapStateToProps = (state) => ({
errorMessage: state.auth.errorMessage,
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withNavigate,
)(Login);
I'm pretty new to Redux, but am trying to add it to my existing project and am getting an infinite refresh loop back to my /login page. I don't see an immediate error and can't locate where the issue might be coming from. I think the loop might be coming from the render in App.js that pulls in the Login component, but can't seem to pinpoint it. I'd really appreciate any help with this one!
App.js:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import MuiThemeProvider from '#material-ui/core/styles/MuiThemeProvider';
import createMuiTheme from '#material-ui/core/styles/createMuiTheme';
import themeFile from './util/theme';
import jwtDecode from 'jwt-decode';
// Redux
import { Provider } from 'react-redux';
import store from './redux/store';
// Components
import Navbar from './components/Navbar';
import AuthRoute from './util/AuthRoute';
// Pages
import home from './pages/home';
import login from './pages/login';
import signup from './pages/signup';
const theme = createMuiTheme(themeFile);
let authenticated;
const token = localStorage.FBIdToken;
if(token){
const decodedToken = jwtDecode(token);
if(decodedToken.exp * 1000 < Date.now()){
window.location.href='/login'
authenticated = false;
} else {
authenticated = true;
};
}
class App extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<Router>
<Navbar/>
<div className="container">
<Switch>
<Route exact path="/" component={home}/>
<AuthRoute exact path="/login" component={login} authenticated={authenticated}/>
<AuthRoute exact path="/signup" component={signup} authenticated={authenticated}/>
</Switch>
</div>
</Router>
</Provider>
</MuiThemeProvider>
);
}
}
export default App;
Login page:
import React, { Component } from 'react'
import withStyles from '#material-ui/core/styles/withStyles'
import PropTypes from 'prop-types'
import AppIcon from '../images/micrologo.png'
import { Link } from 'react-router-dom'
//MUI Stuff
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography'
import TextField from '#material-ui/core/TextField'
import Button from '#material-ui/core/Button'
import CircularProgress from '#material-ui/core/CircularProgress'
//Redux stuff
import { connect } from 'react-redux';
import { loginUser } from '../redux/actions/userActions';
const styles = (theme) => ({
...theme.spreadThis
})
class login extends Component {
constructor(){
super();
this.state = {
email: '',
password: '',
errors: {}
}
}
componentWillReceiveProps(nextProps){
if(nextProps.UI.errors){
this.setState({ errors: nextProps.UI.errors })
}
}
handleSubmit = (event) => {
event.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password
};
this.props.loginUser(userData, this.props.history)
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
const { classes, UI: { loading } } = this.props;
const { errors } = this.state;
return (
<Grid container className={classes.form}>
<Grid item sm/>
<Grid item sm>
<img src={AppIcon} alt="micrologo" className={classes.image}/>
<Typography variant="h3" className={classes.pageTitle}>
Login
</Typography>
<form noValidate onSubmit={this.handleSubmit}>
<TextField
id="email"
name="email"
type="email"
label="Email"
className={classes.textField}
helperText={errors.email}
error={errors.email ? true : false}
value={this.state.email}
onChange={this.handleChange}
fullWidth
/>
<TextField
id="password"
name="password"
type="password"
label="Password"
className={classes.textField}
helperText={errors.password}
error={errors.password ? true : false}
value={this.state.password}
onChange={this.handleChange}
fullWidth
/>
{errors.general && (
<Typography variant="body2" className={classes.customError}>
{errors.general}
</Typography>
)}
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
disabled={loading}
>
Login
{loading && (
<CircularProgress size={30} className={classes.progress}/>
)}
</Button>
<br />
<small>Don't have an account? Sign up <Link to="/signup">here</Link>
</small>
</form>
</Grid>
<Grid item sm/>
</Grid>
)
}
}
login.propTypes = {
classes: PropTypes.object.isRequired,
loginUser: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
UI: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
user: state.user,
UI: state.UI
});
const mapActionsToProps = {
loginUser
}
export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(login));
User Details:
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../type';
import axios from 'axios'
export const loginUser = (userData, history) => (dispatch) => {
dispatch({ type: LOADING_UI});
axios
.post('/login', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
dispatch(getUserData());
dispatch({ type: CLEAR_ERRORS });
history.push('/');
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
})
})
}
export const getUserData = () => (dispatch) => {
axios.get('/user')
.then((res) => {
dispatch({
type: SET_USER,
payload: res.data
})
})
.catch((err) =>
console.log(err)
)
}
EDIT: After making changes to my User Actions file that references my User Reducer, could it possibly be something to do with the User Reducer code?
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI, SET_AUTHENTICATED, SET_UNAUTHENTICATED } from '../type';
const initialState = {
authenticated: false,
credentials: {},
likes: [],
notifications: []
};
export default function(state = initialState, action){
switch(action.type){
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState
case SET_USER:
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
EDIT 2: Adding copy > copy fetch code from network tab:
fetch("http://localhost:3000/login", {
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-US,en;q=0.9",
"if-none-match": "W/\"717-3FVndTj2FHm3TgZjXTrLARSY62Q\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "same-origin",
"upgrade-insecure-requests": "1"
},
"referrer": "http://localhost:3000/login",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "omit"
});
You can try the following:
export const loginUser = (userData, history) => (
dispatch
) => {
dispatch({ type: LOADING_UI });
axios
.post("/login", userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem("FBIdToken", FBIdToken);
axios.defaults.headers.common[
"Authorization"
] = FBIdToken;
//wait for getUserData to finish
return getUserData()(dispatch);
})
.then(() => {
//getUserData is finished
dispatch({ type: CLEAR_ERRORS });
history.push("/");
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data,
});
});
};
export const getUserData = () => (dispatch) => {
//return a promise so loginUser action can wait for
// it to finish
return axios.get("/user").then((res) => {
dispatch({
type: SET_USER,
payload: res.data,
});
});
//if you catch it then the catch that set errors is
// pointless
// .catch((err) => console.log(err));
};
Two components are rendered with conditional rendering with a redux state but after changing redux state from one component warning appears that can't perform state update on unmounted component. In LoginWrapper.js Login and Redirect are two components with conditional rendering using isLoggedIn state of redux.
LoginWrapper.js
import React from 'react';
import Login from 'containers/Login';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { PropTypes } from 'prop-types';
const LoginWrapper = ({ isLoggedIn }) => {
return (
<div>
{
!isLoggedIn
?
<Login />
:
<Redirect to="/profile" />
}
</div>
)
}
LoginWrapper.defaultProps = {
isLoggedIn: false
}
LoginWrapper.propTypes = {
isLoggedIn: PropTypes.bool
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.auth.isLoggedIn
}
}
export default connect(mapStateToProps)(LoginWrapper);
Login.js
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter, Link } from 'react-router-dom';
import { withFormik, Form, Field } from 'formik';
import { connect } from 'react-redux';
import { logInUser } from 'actions/auth';
import { logInUrl } from "apis";
import ModalLayout from "shared/ModalLayout";
import * as Yup from 'yup';
const loginPost = (history, values, setSubmitting, setErrors, resetForm, logIn) => {
const { username, password } = values;
window.fetch(logInUrl, {
method: 'POST',
credentials: "same-origin",
headers: {
'Content-Type': "application/json"
},
body: JSON.stringify({
"username": username,
"password": password
})
})
.then((results) => {
return results.json();
})
.then((data) => {
if(data.errors) {
setErrors({ 'username': data.errors[0].msg });
} else {
logIn(data.user, history);
resetForm();
}
setSubmitting(false);
})
.catch((err) => {
console.log(err);
})
}
const LogInForm = ({
touched,
errors,
isSubmitting,
}) => (
<ModalLayout>
<Form className="login-form">
{touched.username && errors.username && <p className="login-error">{errors.username}</p>}
<div className="login-username">
<Field type="input" placeholder="Username" name="username" />
</div>
{touched.password && errors.password && <p className="login-error">{errors.password}</p>}
<div className="login-password">
<Field type="password" placeholder="Password" name="password" />
</div>
<div className="login-button">
<button className="modal-button login-button" type="submit" disabled={isSubmitting}>
Log in
</button>
</div>
<div className="login-redirect">
<Link to="/signup">Don't have an account.Create one</Link>
</div>
</Form>
</ModalLayout>
);
LogInForm.propTypes = {
isSubmitting: PropTypes.bool.isRequired,
errors: PropTypes.object.isRequired,
touched: PropTypes.object.isRequired,
}
const FormikApp = withFormik({
mapPropsToValues() {
return {
username: '',
password: '',
}
},
handleSubmit(values, { resetForm, setErrors, setSubmitting, props }) {
const { logIn, history } = props;
loginPost(history, values, setSubmitting, setErrors, resetForm, logIn);
},
validationSchema: Yup.object().shape({
username: Yup.string().required('Username is required'),
password: Yup.string().required('Password is required'),
})
})(LogInForm);
export default withRouter(connect(null, { logIn: logInUser })(FormikApp));
actions
import {
LOG_IN,
LOG_OUT,
} from 'actions/types';
import { logInUrl } from 'apis';
export const logInUser = (user) => {
return (dispatch) => {
dispatch({
type: LOG_IN,
payload: user
})
}
}
export const logOutUser = () => {
return {
type: LOG_OUT
}
}
reducers
import { LOG_IN, LOG_OUT } from 'actions/types';
const INITIAL_STATE = {
isloggedIn: null,
user: null,
uid: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case LOG_IN:
return { ...state, isLoggedIn: true, user: action.payload, uid: action.payload.id }
case LOG_OUT:
return { ...state, isLoggedIn: false, user: null, uid: null };
default:
return state;
}
};
Help me out, I am new to React and Javascript
Getting this error:"TypeError: _this.props.onCreate is not a function" although the function has been passed in the props and has been bound.
Here is my current code in react.
UserCreate.js
import React, { Component } from 'react';
class UserCreate extends Component {
constructor(props){
super(props);
this.state = {
email: ''
};
}
handleChange = email => event => {
this.setState(
{
[email]: event.target.value,
}
)
}
handleCreate = () => {
console.log('create', this.state.email);
this.props.onCreate({'email': this.state.email});
}
render() {
let userData = this.props.user && this.props.user.email;
return (
<div>
<h3> New User Form </h3>
<input onChange={this.handleChange('email')} placeholder="Email"/>
<button onClick={this.handleCreate}>Create</button>
</div>
);
}
}
export default UserCreate;
App.js
const USerCreateWithData = compose(
graphql(UserCreateMutation, {
props: (props) => ({
onCreate: (user) => {
props.mutate({
variables: { ...user },
optimisticResponse: () => ({ createUser: { ...user, __typename: 'User'}})
})
}
}
),
options: {
update: (dataProxy, { data: { createUser }}) => {
}
}
})
)(UserCreate);
UserCreateMutation
export default gql`
mutation UserCreateMutation($email: String!){
createUser(
email: $email
) {
__typename
id
email
}
}
`;
What I am doing wrong in here? I have tried every solutions that I have seen on google, stackoverflow but haven't found a solution yet.