I've been getting this error for so long and I don't know how to fix this
PrivateRoute Component which is getting an undefined Value from store while using mapstatetoprops
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ isAuthenticated, component: Component, ...rest}) => (
<Route {...rest} component={(props) => (
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to="/" />
)
)} />
);
const mapStateToProps = (state) => {
return {
isAuthenticated: !!state.auth.uname
};
};
export default connect(mapStateToProps)(PrivateRoute);
AppRouter Component Which is using PrivateRoute for hiding some secret routes from unauthorized access.
import React from 'react';
import { Router, Route, Switch} from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import Nav from '../components/navigation/nav';
import Index from '../components/index';
import SignIn from '../components/signin/signin';
import Register from '../components/register/register';
import Home from '../components/home/home';
import { PrivateRoute } from './PrivateRoute';
export const history = createHistory();
const AppRouter = () => {
return(
<Router history={history}>
<div>
<Nav />
<Switch>
<Route exact path='/' component={Index} />
<Route path='/signin' component={SignIn}/>
<Route path='/register' component={Register}/>
<PrivateRoute path='/home' component={Home}/>
</Switch>
</div>
</Router>
);
}
export default AppRouter;
Index.js wrapping store in provider
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { Provider } from 'react-redux';
import 'tachyons';
import './index.css';
import App from './App';
import configureStore from './store/configStore';
import * as serviceWorker from './serviceWorker';
const store = configureStore();
const jsx = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(jsx, document.getElementById('root'));
serviceWorker.unregister();
Auth Reducer
const INIIAL_STATE = {isLoginSuccess: true, uname: '' }
export default (state = INIIAL_STATE, action) => {
switch (action.type) {
case 'LOGIN':
return {
uname: action.uname
};
case 'LOGIN_SUCCESS':
return {
isLoginSuccess: action.isLoginSuccess
};
case 'LOGOUT':
return {};
default:
return state;
}
};
Action Creator
import configureStore from '../store/configStore';
import { history } from '../routers/AppRouter';
const store = configureStore();
export const setLoginSuccess = (isLoginSuccess) => {
return {
type: 'LOGIN_SUCCESS',
isLoginSuccess
};
}
export const login = (uname) => {
return{
type: 'LOGIN',
uname
};
}
export const logout = () => {
return{
type: 'LOGOUT'
};
}
export const startLogin = (username, password) => {
return() => {
fetch('http://localhost:3000/signin' , {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: username,
password: password
})
}).then(response => response.json())
.then(
user => {
if(user) {
store.dispatch(login(user.username));
store.dispatch(setLoginSuccess(true));
if(history.location.pathname === '/signin')
{
history.push('/home');
}
}
})
.catch(err => {console.log(err)});
}
}
Related
An unauthenticated user can still see the "/account" page for a few seconds I mean there is still a delay of a few seconds before the unauthenticated user is completely redirected to another page. even though I have used ProtectedRoute, how can I make unauthenticated users unable to see the "/account" page at all without delay? i use react-router-dom 6.6.1 and firebase auth
here is for the app.js
import { ThemeProvider } from '#mui/material/styles';
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom'
import Home from './components/Home'
import Account from './components/Account';
import Signin from './components/Home/Signin';
import theme from './theme';
import { AuthContextProvider } from './components/Context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
<Route path='/account' element={<ProtectedRoute><Account /></ProtectedRoute>} />
</Routes>
</Router>
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
and this is for the ProtectedRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
import { UserAuth } from './Context/AuthContext';
const ProtectedRoute = ({ children }) => {
const { user } = UserAuth();
if (!user) {
return <Navigate to='/' />;
}
return children;
};
export default ProtectedRoute;
this if for the UserAuth :
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
} from 'firebase/auth';
import { auth } from '../../firebase';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<UserContext.Provider value={{ createUser, user, signIn }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
I want to redirect unauthenticated users to the main page without delay
it’s probably due to the fact that your guard is validating off of !user however your initial auth state has user as {}. In this case an empty object will validate as truthy.
You could either set the initial state of user as null or you could update th guard validation to check for a value on user like if (!user.id) …
Render Protected and Base routes conditional so unauthenticated user never have access of Protected routes.
Use the following code.
Always make separate file for routing for better code readability and Separation of concerns.
BaseRoutes.js
import React from 'react'
import { Route } from 'react-router-dom'
import Home from './components/Home'
import Signin from './components/Home/Signin';
const BaseRoutes = (
<>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
</>
)
export default BaseRoutes
ProtectedRoutes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Account from './components/Account';
const ProtectedRoutes = () => {
return (
<>
<Route path='/account' element={<Account />} />
</>
)
};
export default ProtectedRoutes;
Routes.js
import React from 'react'
import { Navigate, Route, Routes, BrowserRouter } from 'react-router-dom'
import { UserAuth } from './Context/AuthContext';
import ProtectedRoutes from './components/ProtectedRoutes';
import BaseRoutes from './components/BaseRoutes';
const Router = () => {
const { user } = UserAuth();
const isUser = Boolean(user)
const redirectUrl = isUser ? '/account' : '/signin'
return (
<BrowserRouter>
<Routes>
{isUser ? <BaseRoutes /> : <ProtectedRoutes />}
<Route
path="*"
element={<Navigate to={redirectUrl} />}
/>
</Routes>
</BrowserRouter>
)
}
export default Router
app.js
import React from 'react';
import { AuthContextProvider } from './components/Context/AuthContext';
import { ThemeProvider } from '#mui/material/styles';
import theme from './theme';
import Router from './routes'
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router />
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
I am trying to make a chat website, and i was following along to this YouTube tutoriel and I came across these two errors:
"Renderingdirectly" is not supported and "A context consumer was rendered with multiple children"
Here is my App code:
import React from 'react';
import ApolloProvider from './ApolloProvider';
import {Container} from 'react-bootstrap'
import Register from './pages/register'
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom'
import './App.scss';
import Login from './pages/Login';
import Home from './pages/Home'
import {AuthProvider} from './context/auth';
function App() {
return (
<ApolloProvider >
<AuthProvider>
<Router>
<Container className='pt-5'>
<Routes>
<Route exact path='/' element={<Home />}/>
<Route path='/register' element={<Register />}/>
<Route path='/login' element={<Login />}/>
</Routes>
</Container>
</Router>
</AuthProvider>
</ApolloProvider>
);
}
export default App;
And here is my context code (
I am setting the token in my LOGIN case, which is being accessed from my login.js):
import React, {createContext, useReducer, useContext} from 'react'
const AuthStateContext = createContext()
const AuthDispatchContext = createContext()
const authReducer = (state, action) =>{
switch(action.type){
case 'LOGIN':
localStorage.setItem("token", action.payload.token)
return {
...state,
user: action.payload,
}
case 'LOGOUT':
return {
...state,
user: null
}
default:
throw new Error(`Unknown action type: ${action.type}`)
}
}
export const AuthProvider = (props) => {
const [state, dispatch] = useReducer(authReducer, {user: null})
return (
<AuthDispatchContext.Provider value={dispatch}>
<AuthStateContext value={state}>
{props.children}
</AuthStateContext>
</AuthDispatchContext.Provider>
)
}
export const useAuthState = () => useContext(AuthStateContext)
export const useAuthDispatch = () => useContext(AuthDispatchContext)
Here is a snippet of my login code where we pass the token to the context file:
import { useAuthDispatch } from '../context/auth';
const LOGIN_USER = gql`
query login(
$username: String!
$password: String!
) {
login(
username: $username
password: $password
) {
username email createdAt token
}
}
`;
export default function Login() {
const [variables, setVariables]= useState({
username: '',
password: ''
})
const Navigate = useNavigate()
const [errors,setErrors] = useState({})
const dispatch = useAuthDispatch()
const [loginUser, {loading}] = useLazyQuery(LOGIN_USER,{
onError: (err)=>{
setErrors(err.graphQLErrors[0].extensions.errors)
},
onCompleted: (data)=>{
dispatch({type:'LOGIN', payload: data.login})
Navigate('/')
}
})
const submitLogin = (e) =>{
e.preventDefault()
loginUser({variables})
}
You are rendering AuthStateContext while it should be AuthStateContext.Provider, like so:
export const AuthProvider = (props) => {
const [state, dispatch] = useReducer(authReducer, { user: null });
return (
<AuthDispatchContext.Provider value={dispatch}>
<AuthStateContext.Provider value={state}>{props.children}</AuthStateContext.Provider>
</AuthDispatchContext.Provider>
);
};
I am trying to make it so that when a user clicks on a certain Link (Upload Component), it will redirect them to to the login component and am not sure how to accomplish this task in React. I was directed to another answered question, but it hasn't helped me as I am still confused on what I need to do with my own set up. I understand I need to make my own protected route (maybe), but I saw others accessing useContext and I do not have any file with context. I am using Version 6 in react dom. I am also using React router and redux in my project so I know I need to access the state somehow, just not sure how to wrap my mind around it so if anyone could help, I would appreciate it. The user is being stored in local storage with JWT authentication.
App.js:
import React, {useState, useEffect, useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; //Switch was replaced by Routes in react-router-dom v6
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './components/pages/Home';
import Uploads from './components/pages/Uploads';
import Account from './components/pages/Account';
import Login from './components/pages/Login';
import LogOut from './components/Logout';
import Register from './components/pages/Register';
import Profile from './components/pages/Profile';
import EditProfile from './components/pages/EditProfile';
import BoardAdmin from './components/pages/BoardAdmin';
import BoardModerator from './components/pages/BoardModerator';
import BoardUser from './components/pages/BoardUser';
import {logout} from './slices/auth';
import EventBus from './common/EventBus';
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const {user: currentUser} = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (currentUser) {
setShowModeratorBoard(currentUser.roles.includes('ROLE_MODERATOR'));
setShowAdminBoard(currentUser.roles.includes('ROLE_ADMIN'));
} else {
setShowModeratorBoard(false);
setShowAdminBoard(false);
}
}, [currentUser]);
return (
<>
<Router>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/create/upload' element={<Uploads />} />
<Route path='/my-account' element={<Account />} />
<Route path='/login' element={<Login />} />
<Route path='/logout' element={<LogOut />} />
<Route path='/register' element={<Register />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/edit' element={<EditProfile />} />
<Route path='/user' element={<BoardUser />} />
<Route path='/mod' element={<BoardModerator />} />
<Route path='/admin' element={<BoardAdmin />} />
</Routes>
<Footer />
</Router>
</>
);
};
export default App;
Auth.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
It looks like adding the if statement in my upload file is making it so that a logged in user can access the h1 element that is in the file, but not an unauthenticated user. So its working to an extent, just not redirecting back to login.
Upload.jsx:
import React from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Uploads() {
const {user: currentUser} = useSelector((state) => state.auth);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Rendering the Link doesn't imperatively navigate, use the Navigation component.
Example:
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function Uploads() {
const { user: currentUser } = useSelector((state) => state.auth);
if (!currentUser) {
return <Navigate to='/login' replace />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Typically you would protect the route instead. If you wanted to do that:
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoutes = () => {
const { user: currentUser } = useSelector((state) => state.auth);
return currentUser
? <Outlet />
: <Navigate to="/login" replace />;
}
...
<Routes>
<Route path='/' element={<Home />} />
<Route element={<PrivateRoutes />}>
<Route path='/create/upload' element={<Uploads />} />
... any other protected routes ...
</Route>
... other unprotected routes ...
</Routes>
Hi I am managing my react app state with Context API, and I am able to destructure of the state and functions of my Context in all files except my app.js file.
Here I created a reusable file called 'createDataContext' to prevent writing duplicate code when creating a Context and Provider for each Context.
import React, { createContext, useReducer } from 'react';
export default (reducer, actions, initialState) => {
const Context = createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for(let key in actions){
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{ children }
</Context.Provider>
)
}
return { Context, Provider }
}
Here is my AuthContext which uses createDataContext file
import createDataContext from "./createDataContext";
import api from '../api/api'
const authReducer = (state, action) => {
switch(action.type){
case 'signin':
return { errorMessage: '', token: action.payload };
case 'add_error':
return { ...state, errorMessage: action.payload };
default:
return state;
}
}
const login = dispatch => {
return async (email, password) => {
try {
const res = await api.post('/users/login', { email, password });
localStorage.setItem('token', res.data.token);
dispatch({ type: 'signin', payload: res.data.token });
}catch(err){
dispatch({ type: 'add_error', payload: 'signin failed' })
}
}
}
export const { Context, Provider } = createDataContext(authReducer, { login }, {
token: null,
errorMessage: ''
});
I am able to use the context and destructor off the state and function like login in my pages and components but unable to do so in my app.js file.
import React, { useContext } from 'react';
import Home from './pages/home/Home';
import Login from './pages/login/Login';
import Profile from './pages/profile/Profile';
import Register from './pages/register/Register';
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';
import Navbar from './components/navbar/Navbar';
import { Provider as AuthProvider } from './context/AuthContext';
import { Context } from './context/AuthContext';
function App() {
const { state, login } = useContext(Context);
return (
<AuthProvider>
<Router>
<Navbar />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register />
</Route>
<Route path="/profile/:id">
<Profile />
</Route>
</Switch>
</Router>
</AuthProvider>
);
}
export default App;
I am getting error TypeError: Cannot destructure property 'state' of 'Object(...)(...)' as it is undefined. Can someone please point out what I'm or where I'm going wrong. Thanks.
You cannot use the context in the same component where it is being provided. To get around this create another new component that is a wrapper for the rest of the children of the Provider
// AppWrapper.jsx
import React from 'react'
import App from './app'
import { Provider as AuthProvider } from './context/AuthContext'
const AppWrapper = () => {
return (
<AuthProvider>
<App />
</AuthProvider>
)
}
default export AppWrapper
Then remove the <AuthProvider> from your app.js component, and wherever you were calling <App /> in the tree, call the <AppWrapper /> instead. Then inside of the App you can use the Context like you are trying to do.
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));
});
};