Redux resets after dispatching an action - javascript

so I've been working on this small cinema app and I'm facing a weird bug where every time I try to log in to access the home page it doesn't redirect and then the redux store gets reset, but when I commented useEffect in the HomePage.js component It worked and also when I tried to conosole.log(action.payload) in another reducer file for the movies I got the payload from the AuthReducer so I don't know what's the problem, is it with my whole redux setup or because I'm using the render method in my ProtecdedRoute.js
AuthSaga.js
function* loginUserSaga({ payload }) {
yield delay(1500);
yield put(LoginUserSuccessAction(payload));
}
export default function* AuthSaga() {
yield takeLatest(actionTypes.LOGIN_USER, loginUserSaga);
}
protectedRoute.js
import React from 'react';
import { useSelector } from 'react-redux';
import { Route, Redirect, useLocation } from 'react-router';
const ProtectedRoute = ({ component: Component, layout: Layout, ...rest }) => {
const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
if (!isLoggedIn) {
return (
<Redirect
to={{
pathname: '/login',
}}
/>
);
}
const renderComponent = () => {
return (
<Layout>
<Component />
</Layout>
);
};
return <Route {...rest} render={renderComponent} />;
};
export default ProtectedRoute;
Routes.js
const Routes = () => {
const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
console.log(isLoggedIn);
return (
<Switch>
<ProtectedRoute path="/" exact component={Home} layout={Layout} />
<AuthRoute path="/login" exact component={Login} layout={AuthLayout} />
</Switch>
);
};
export default Routes;
AuthReducer
const authReducer = (state = initialState, action) => {
const { type, payload } = action;
console.log(payload)
switch (type) {
case actionTypes.LOGIN_USER: {
return {
...state,
loading: true,
};
}
case actionTypes.LOGIN_USER_SUCCESS: {
return {
...state,
userName: payload.userName,
token: `${payload.password}${payload.userName}`,
isLoggedIn: true,
loading: false,
};
}
case actionTypes.LOGIN_USER_FAIL: {
return {
...state,
loading: false,
};
}
case actionTypes.LOGOUT_USER: {
return {
...state,
userName: null,
token: null,
isLoggedIn: false,
loading: false,
};
}
default:
return initialState;
}
};
HomePage.jsx
const Home = () => {
const dispatch = useDispatch();
const upcomingMoviesSelector = useSelector(state => state.upcomingMovies.items)
useEffect(() => {
dispatch(fetchUpcomingMoviesAction());
console.log('')
}, []);
return (
<div>
<MovieSlider items={upcomingMoviesSelector} />
</div>
);
};
export default Home;

Related

How to render react routes after auth response?

When someone arrives to my webpage I first check if the user is authenticated or not. I need to wait for the response from my GET /auth/loggedin request before rendering the routes so that I know if I should redirect to /login or /. I have added conditional rendering to the App.js component, but it always redirects the user to /login even when the user is authenticated.
App.js:
import { useEffect, useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import AuthContext from "./contexts/authContext";
import { apiLoggedIn } from "./api/auth";
import ProtectedRoute from "./components/ProtectedRoute";
import Document from "./pages/Document";
import { Login } from "./pages/Login";
import { Signup } from "./pages/Signup";
import Layout from "./components/Layout";
import PublicRoute from "./components/PublicRoute";
export const App = () => {
const [auth, setAuth] = useState({ isAuth: undefined, user: undefined });
useEffect(() => {
apiLoggedIn()
.then((res) => {
setAuth({ isAuth: true, user: res.data.userData });
})
.catch((err) => {
if (err.response) {
setAuth({ isAuth: false, user: {} });
} else if (err.request) {
setAuth({ isAuth: false, user: {} });
console.log(err.request);
} else {
setAuth({ isAuth: false, user: {} });
console.log(err.message);
}
});
}, []);
return (
<>
{auth.isAuth !== undefined ? (
<AuthContext.Provider value={{ auth, setAuth }}>
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<ProtectedRoute redirectTo="/login">
<Layout />
</ProtectedRoute>
}
>
<Route index element={<Document />} />
</Route>
<Route
path="/signup"
element={
<PublicRoute>
<Signup />
</PublicRoute>
}
/>
<Route
path="/login"
element={
<PublicRoute>
<Login />
</PublicRoute>
}
/>
</Routes>
</BrowserRouter>
</AuthContext.Provider>
) : (
""
)}
</>
);
};
./api/auth.js
import axios from "axios";
export const apiLoggedIn = () => {
return axios.get("/auth/loggedin");
};
./components/ProtectedRoute.js
import { useContext } from "react";
import AuthContext from "../contexts/authContext";
import { Navigate } from "react-router-dom";
const ProtectedRoute = ({ children, redirectTo }) => {
const { isAuth } = useContext(AuthContext);
return isAuth ? children : <Navigate to={redirectTo} />;
};
export default ProtectedRoute;
./components/PublicRoute.js
import { useContext } from "react";
import AuthContext from "../contexts/authContext";
import { Navigate } from "react-router-dom";
const PublicRoute = ({ children }) => {
const { isAuth } = useContext(AuthContext);
return isAuth ? <Navigate to={"/"} /> : children;
};
export default PublicRoute;
./contexts/authContext.js
import { createContext } from "react";
const AuthContext = createContext({
auth: { isAuth: false, user: {} },
setAuth: () => {},
});
export default AuthContext;
The AuthContext value is an object with auth and setAuth properties
<AuthContext.Provider value={{ auth, setAuth }}>
...
</AuthContext.Provider>
But in the route wrapper you are referencing an isAuth property, which will always be undefined, i.e. falsey.
const ProtectedRoute = ({ children, redirectTo }) => {
const { isAuth } = useContext(AuthContext);
return isAuth ? children : <Navigate to={redirectTo} />;
};
This is why the redirect always occurs. To resolve, ensure you reference the same context values throughout the code.
Either specify an isAuth context value:
<AuthContext.Provider value={{ isAuth: auth.isAuth, setAuth }}>
...
</AuthContext.Provider>
Or fix the wrappers:
const ProtectedRoute = ({ children, redirectTo }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? children : <Navigate to={redirectTo} />;
};
...
const PublicRoute = ({ children }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? <Navigate to={"/"} /> : children;
};
Your context has the following shape :
{
"auth": {
"isAuth": true,
"user": {...},
},
"setAuth": function ...
}
you must update ProtectedRoute to
const ProtectedRoute = ({ children, redirectTo }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? children : <Navigate to={redirectTo} />;
};
you must update PublicRoute to
const PublicRoute = ({ children }) => {
const { auth } = useContext(AuthContext);
return auth.isAuth ? <Navigate to={"/"} /> : children;
};
the key name is not passed correctly in auth provider it should be passed as following here is sample sandbox demo : https://codesandbox.io/s/routes-4c5oh?file=/src/App.js
<AuthContext.Provider value={{ isAuth:auth, setAuth }}>
or change the key to auth in each place. in Public and Protected routes like following
const { auth:{isAuth} } = useContext(AuthContext);

How to fix, can't perform a React state update on an unmounted component?

I have a problem with my code. Here is my code in App.tsx
function App() {
const dispatch = useDispatch();
const auth = useSelector((state: RootStateOrAny) => state.auth);
const token = useSelector((state: RootStateOrAny) => state.token);
useEffect(() => {
const firstlogin = localStorage.getItem('firstlogin');
if(firstlogin) {
const getUserToken = async () => {
const res: any = await axios.post('/user/refresh_token', null);
dispatch({type: "GET_TOKEN", payload: res.data.access_token});
};
getUserToken();
}
}, [auth.isLogged, dispatch]);
useEffect(() => {
if(token){
const getUser = () => {
dispatch(dispatchLogin());
return fetchUser(token).then(res => {
dispatch(dispatchGetUser(res))
})
}
getUser();
}
},[token, dispatch]);
return (
<Router>
<div className="App">
<Header/>
<Body/>
</div>
</Router>
);
}
export default App;
And when I try to access the state in my Home component, which is under the Body component. It gives me an error saying: "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 a useEffect cleanup function."
This is my Home component:
import React from 'react';
import { useSelector, RootStateOrAny } from 'react-redux';
export default function Home() {
const auth = useSelector((state: RootStateOrAny) => state.auth);
const {username} = auth.user[0];
return(
<div>
Welcome {username} !
</div>
)
};
And the Body Component:
export default function Body() {
const auth = useSelector((state: RootStateOrAny) => state.auth);
const {isLogged} = auth;
return (
<Switch>
<Route path="/signup">
{isLogged ? <Notfound/> : <Signup/>}
</Route>
<Route path="/login">
{isLogged ? <Notfound/> : <Login/>}
</Route>
<Route path="/home">
{isLogged ? <Home/> : <Login/>}
</Route>
<Redirect to="/home"/>
</Switch>
)
}
These are my reducers:
const initialState = {
user: [],
isAdmin: false,
isLogged: false,
};
const authReducer = (state = initialState, action: any) => {
switch(action.type) {
case ACTIONS.LOGIN :
return {
...state,
isLogged: true
}
case ACTIONS.GET_USER :
return {
...state,
user: action.payload.user
}
default:
return state
}
}
How can I fix this? Thank you.
if you are using conditional rendering in your routes,It leads to this kind of error.Since the component mount is defined by the conditional statement.For that you can use history.push("/") with conditional statements in the useeffect hook of your Home component.
or you can check this Can't perform a React state update on an unmounted component

PrivateRoute functional component using aws-amplify as authentication

I'm trying to create a functional component equivalent of the following PrivateRoute class component (source code here):
import React, { useState, useEffect } from "react";
import { Route, Redirect, withRouter, useHistory } from "react-router-dom";
import { Auth } from "aws-amplify";
class PrivateRoute extends React.Component {
state = {
loaded: false,
isAuthenticated: false
};
componentDidMount() {
this.authenticate();
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log("user: ", user))
.catch(() => {
if (this.state.isAuthenticated)
this.setState({ isAuthenticated: false });
});
});
}
componentWillUnmount() {
this.unlisten();
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true });
})
.catch(() => this.props.history.push("/auth"));
}
render() {
const { component: Component, ...rest } = this.props;
const { loaded, isAuthenticated } = this.state;
if (!loaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/auth"
}}
/>
);
}}
/>
);
}
}
export default withRouter(PrivateRoute);
The above code works in my application when I use it like so:
<PrivateRoute
exact
path={urls.homepage}
component={Homepage}
/>
My attempt at a converting the above class component into a functional component is the following:
import React, { useState, useEffect } from "react";
import { Route, Redirect, useHistory } from "react-router-dom";
import { Auth } from "aws-amplify";
const PrivateRoute = ({ component: Component, ...rest }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
let history = useHistory();
useEffect(() => {
Auth.currentAuthenticatedUser()
.then(() => {
setIsLoaded(true);
setIsAuthenticated(true);
})
.catch(() => history.push("/auth"));
return () =>
history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log("user: ", user))
.catch(() => {
if (isAuthenticated) setIsAuthenticated(false);
});
});
}, [history, isAuthenticated]);
if (!isLoaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/auth"
}}
/>
);
}}
/>
);
};
export default PrivateRoute;
But when using my functional component in the same way, I keep getting the following error:
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 a useEffect cleanup function
It always redirects me to /auth, regardless of whether I am logged in or not. What am I doing wrong? Any help is much appreciated!
I think you are missing your unmount, the return in the useEffect should be your unlisten which is the unmount. Also, I removed useHistory and pulled history from the props and used withRouter
Try this
import React, { useState, useEffect } from "react";
import { Route, Redirect, withRouter } from "react-router-dom";
import { Auth } from "aws-amplify";
const PrivateRoute = ({ component: Component, history, ...rest }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
Auth.currentAuthenticatedUser()
.then(() => {
setIsLoaded(true);
setIsAuthenticated(true);
})
.catch(() => history.push("/auth"));
const unlisten = history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log("user: ", user))
.catch(() => {
if (isAuthenticated) setIsAuthenticated(false);
});
});
return unlisten();
}, [history, isAuthenticated]);
if (!isLoaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/auth"
}}
/>
);
}}
/>
);
};
export default withRouter(PrivateRoute);
Try this out:
useEffect(() => {
async function CheckAuth() {
await Auth.currentAuthenticatedUser()
.then((user) => {
setIsLoaded(true);
setIsAuthenticated(true);
})
.catch(() => history.push("/auth"));
}
CheckAuth();
}, []);

Error - Actions must be plain objects. Use custom middleware for async actions

I am trying to implement redux and throw and object into the store. I get the correct object when I call the action but when I call store.dispatch() the store is never updated. It only contains initial state. I think I've tried everything and am missing something small. Any help would be appreciated!
repo
signUp.js
import {getUsers} from "../actions/getUsersActions";
import {bindActionCreators} from "redux";
import {connect} from "react-redux";
import store from '../store';
class SignMo extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
firstName: '',
lastName: '',
email: '',
username: '',
password: ''
}
}
async getUsers () {
await this.props.getUsers()
.then(response => {
this.setState({users: response.users.users});
})
.catch(function (error) {
console.log(error);
});
console.log('store =>', store.getState())
};
componentDidMount() {
this.getUsers = this.getUsers.bind(this);
this.getUsers();
}
render(){
return(
<stuff/>
)
}
const mapStateToProps = state => {
const { users } = state;
return {
users: users,
};
};
const mapDispatchToProps = dispatch => ({
getUsers: () => store.dispatch( getUsers() )
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(SignMo);
getUsersReducer.js
import { combineReducers } from "redux";
import {USERS_FAILURE} from "../actions/getUsersActions";
import {USERS_SUCCESS} from "../actions/getUsersActions";
let INITIAL_STATE ={
users:[],
isloading: false,
errorResponse: false,
errorMessage: null,
};
const getUsersReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
// Take all returned user info and put it in store
case USERS_SUCCESS:
return {
...state,
//type: USERS_SUCCESS,
isLoading: false,
errorResponse: false,
};
// Extract error message to display
case USERS_FAILURE:
return {
...state,
isLoading: false,
errorResponse: true,
errorMessage: action.error
};
// Don't think this gets called
default:
return state;
}
};
export default combineReducers({
users: getUsersReducer
});
getUsersReducer
import axios from "axios";
const url = "http://localhost:3333";
export const USERS_SUCCESS = "USERS_SUCCESS";
export const USERS_FAILURE = "USERS_FAILURE";
export const usersSuccess = users => {
return {
type: USERS_SUCCESS,
users
};
};
export const usersFailure = error => {
return {
type: USERS_FAILURE,
error
};
};
export const getUsers = () => {
return dispatch => {
return axios
.get(`${url}/users`)
.then(response => {
console.log('success', JSON.stringify(response));
return dispatch(usersSuccess(response.data));
})
.catch(error => {
console.log('err', error.response.data.message);
return dispatch(usersFailure(error.response.data));
});
};
};
store.js
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";
import thunk from "redux-thunk";
import reducer from "./reducer";
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(sagaMiddleware, thunk))
);
export default store;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'));
serviceWorker.unregister();
App.js
import {
BrowserRouter as Router,
Switch,
Route
} from "react-router-dom";
import { Provider } from 'react-redux'
import store from "./store";
export default function App() {
return (
<Provider store={store}>
<Router>
<div>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/home">
<div className='primaryContainer'>
<EngagementBar/>
<Home />
</div>
</Route>
<Route path="/SignUp">
<SignUp className="centerSignUp" />
</Route>
<Route path="/Tags">
<Tags />
</Route>
<Route path="/signMo">
<SignMo />
</Route>
<Route path="/search">
<div className="primaryContainer"><Search /></div>
</Route>
<Route path="/profile">
<Profile />
</Route>
<Route path="/upload">
<Upload />
</Route>
<Route path="/notifications">
<Notifications />
</Route>
<Route path="/information">
< Information />
</Route>
<Route path="/"> {/*must be last !!!*/}
<Load />
</Route>
</Switch>
</div>
</Router>
</Provider>
);
}
You have
throwInStore = () => {
store.dispatch(
this.props.getUsers()
);
};
But why are you dispatching here? this.props.getUsers() is already an action dispatched to the store as defined in your mapDispatchToProps. So its like saying dispatch( dispatch( getUsers() ) ).
Could also be an issue here:
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getUsers,
},
dispatch
);
I don't see why you need the bindActionCreators. I would rewrite like so:
const mapDispatchToProps = dispatch => ({
getUsers: () => store.dispatch( getUsers() )
})
Also, now with the dispatch within your mapDispatchToProps, you can remove all your return dispatch from the getUser function. You should really only have one dispatch keyword per action:
export const getUsers = () => {
return axios
.get(`${url}/users`)
.then(response => {
console.log('success', JSON.stringify(response));
store.dispatch(usersSuccess(response.data));
})
.catch(error => {
console.log('err', error.response.data.message);
store.dispatch(usersFailure(error.response.data));
});
};

Secure pages in sign in/up AWS Cognito React

I'm doing the sign in/up pages for an app, and I'm using AWS Cognito and React for the first time. I need to secure my pages until someone signs in/up. I couldn't figure out how to send anything from the userAuth() to the export default, or how to make this work.
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom';
import App from '../App';
import { Auth } from 'aws-amplify';
//last thing 333
async function userAuth() {
let something = Boolean;
Auth.currentSession()
.then(function(fulfilled) {
console.log('worked' + fulfilled);
something === true;
return something;
})
.catch(function(error) {
console.log('didnt work' + error);
window.location.href = '/';
return error;
});
}
export default ({ component: C, ...rest }) => (
alert('this is the user auth ' + userAuth()),
(
<Route
{...rest}
render={
props =>
userAuth() === 'something' ? (
<Redirect to="/" />
) : (
<C {...props} />
)
}
/>
)
);
Auth.currentAuthenticatedUser() is an asynchronous API, so you cannot return anything from this API that can be used in a Route (there is the possibility of returning a Promise, but that is not actually necessary.)
You can change the component to a class like:
class PrivateRoute extends React.Component {
constructor(props) {
super(props);
this.state = {
authStatus: false,
loading: false,
}
}
componentDidMount() {
Auth.currentAuthenticatedUser()
.then((user)=> {
this.setState({ loading: false, authStatus: true });
})
.catch(() => {
this.setState({ loading: false });
this.props.history.push('/login');
});
}
render() {
return <Route {...rest} render={(props) => (
this.state.authStatus
? <Component {...props} />
: <div>Loading ... </div>
)} />
}
}
Just fixed the problem, and thought it may help someone having the same issue !
import React from 'react';
import {
withRouter,
Switch,
Route,
Redirect,
BrowserRouter as Router
} from 'react-router-dom';
import { Auth } from 'aws-amplify';
class AppliedRoute extends React.Component {
state = {
loaded: false,
isAuthenticated: false
};
componentDidMount() {
this.authenticate();
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log('user: ', user))
.catch(() => {
if (this.state.isAuthenticated)
this.setState({ isAuthenticated: false });
});
});
}
componentWillUnmount() {
this.unlisten();
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true });
})
.catch(() => this.props.history.push('/'));
}
render() {
const { component: Component, ...rest } = this.props;
const { loaded, isAuthenticated } = this.state;
if (!loaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/'
}}
/>
);
}}
/>
);
}
}
AppliedRoute = withRouter(AppliedRoute);
export default AppliedRoute;

Categories

Resources