How connect Context with Redirect - reactjs

I want to send information to second page if the user is logged in . I would like use Context to that.
Something about my code :
const Login = () => {
...
const [logged, setLogged] = React.useState(0);
...
const log = () => {
if (tempLogin.login === "Login" && tempLogin.password == "Haslo") {
setLogged(1);
}
...
return (
{logged == 1 && (
<Redirect to="/page" />
)}
I want to send logged to /page but i don't know how . None guide help me .Page is actually empty React file.

There are 2 ways handle that:
Passing state to route(as described in docs):
{logged == 1 && (
<Redirect to={{ path: "/page", state: { isLoggedIn: true } }} />
)}
And your component under /page route will access that flag as this.props.location.state.isLoggedIn
Utilize some global app state(Redux, Context API with <Provider> at root level or anything alike).
To me second option is better for keeping auth information:
Probably not only one target component will want to check if user is authorized
I'd expect you will need to store some authorization data to send with new requests(like JWT token) so just boolean flah accessible in single component would not be enough.
some operation on auth information like say logout() or refreshToken() will be probably needed in different components not in single one.
But finally it's up to you.

Thanks skyboyer
I solved this problem with Context method .I will try tell you how i do this becouse maybe someone will have the same problem
I created new file
import React from "react";
import { Redirect } from "react-router";
const LoginInfo = React.createContext();
export const LoginInfoProvider = props => {
const [infoLog, setInfoLog] = React.useState("");
const login = name => {
setInfoLog(name);
};
const logout = () => {
setInfoLog("old");
};
const { children } = props;
return (
<LoginInfo.Provider
value={{
login: login,
logout: logout,
infolog: infoLog
}}
>
{children}
</LoginInfo.Provider>
);
};
export const LoginInfoConsumer = LoginInfo.Consumer;
In App.js add LoginInfoProvider
<Router>
<LoginInfoProvider>
<Route exact path="/" component={Login} />
<Route path="/register" component={Register} />
<Route path="/page" component={Page} />
</LoginInfoProvider>
</Router>
In page with login (parts of code in my question) i added LoginInfoConsumer
<LoginInfoConsumer>

Related

React state not changing in component

I'm trying to create protected routes that are only viable while user is logged in, but I have trouble getting loggedIn state in ProtectedRoutes component, it's always set to false thus redirecting to "/login". What am I not getting correctly here?
App.tsx
interface loginContextInterface {
loggedIn: boolean;
setLoggedIn: (value: (((prevState: boolean) => boolean) | boolean)) => void;
}
export const LoginContext = createContext({} as loginContextInterface);
export default function App() {
const [ loggedIn, setLoggedIn ] = useState(false)
useEffect(() => {
console.log("before", loggedIn)
isLoggedIn().then((r) => {
console.log("R", r)
setLoggedIn(r)
})
console.log("after", loggedIn)
}, [loggedIn])
return (
<LoginContext.Provider value={{loggedIn, setLoggedIn}}>
<Router>
<MenuHeader />
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/tasks" element={<ProtectedRoutes/>}>
<Route path="/" element={<Tasks/>}/>
</Route>
<Route path="/login" element={<Login />}/>
<Route path="/logout" element={<Logout />}/>
<Route path="/register" element={<Register/>}/>
</Routes>
</Router>
</LoginContext.Provider>
);
}
ProtectedRoutes.tsx
export const ProtectedRoutes = () =>{
const location = useLocation();
const {loggedIn} = useContext(LoginContext)
console.log("protected", loggedIn)
return (
loggedIn ? <Outlet/> : <Navigate to={"/login"} replace state={{location}}/>
);
}
Edit:
isLoggedIn just authenticates that the user is logged in via cookie using api on the server side. Added logging
Produces these after trying to access /tasks route and redirecting me to /login again
VM109:236 protected false
App.tsx:21 before false
App.tsx:26 after false
App.tsx:21 before false
App.tsx:26 after false
2App.tsx:23 R true
App.tsx:21 before true
App.tsx:26 after true
App.tsx:23 R true
There is an issue with the useEffect hook, using the loggedIn state value as the dependency. You should not use dependencies that are unconditionally updated by the hook callback. My guess here is that you wanted to do an initial authentication check when the app mounts. You can remove loggedIn from the dependency since it's not referenced at all.
useEffect(() => {
isLoggedIn().then(setLoggedIn);
}, []);
I suggest also using an initial loggedIn state value that doesn't match either the authenticated or unauthenticated states, i.e. something other than true|false. This is so the ProtectedRoutes can conditionally render null or some loading indicator while any pending authentication checks are in-flight and there isn't any confirmed authentication state already saved in state.
Update the context to declare loggedIn optional.
interface loginContextInterface {
loggedIn?: boolean;
setLoggedIn: React.Dispatch<boolean>;
}
Update App to have an initially undefined loggedIn state value.
const [loggedIn, setLoggedIn] = useState<boolean | undefined>();
Update ProtectedRoutes to check for the undefined loggedIn state to render a loading indicator and not immediately bounce the user to the login route.
const ProtectedRoutes = () => {
const location = useLocation();
const { loggedIn } = useContext(LoginContext);
if (loggedIn === undefined) {
return <>Checking authentication...</>; // example
}
return loggedIn ? (
<Outlet />
) : (
<Navigate to={"/login"} replace state={{ location }} />
);
};
Also in App, remove/move the "/tasks" path from the layout route rendering the ProtectedRoutes component to the nested route rendering the Tasks component. The reason is that it's invalid to nest the absolute path "/" in "/tasks".
<Route element={<ProtectedRoutes />}>
<Route path="/tasks" element={<Tasks />} />
</Route>
It's not recommended to reset the dependency inside the useEffect(), it may cause an infinite loop.
useEffect(() => {
// loggedIn will be update here and trigger the useEffect agan
isLoggedIn().then((r) => setLoggedIn(r))
}, [loggedIn])
What does the console.log(loggedIn) and console.log(r) show? I'm guessing isLoggedIn returns false, loggedIn is set to false initially so useEffect not being triggered again and it remains as false

Public/private routing. React has detected a change in the order of Hooks called

I have component AppRouter which is placed inside <BrowserRouter> and returns public or private <Route> components depending on whether the user is authenticated. Private routes are returning inside <PageWrapper> component. It contains header and sidebar, so routes are rendering in main part of this wrapper.
I am having those exceptions: React has detected a change in the order of Hooks called by AppRouter. & Rendered more hooks than previous render
Console
This is AppRouter:
export const AppRouter = () => {
const user = useAppSelector(state => state.authReducer.user)
const dispath = useAppDispatch();
useEffect(() => {
const userData = getUser()
if (userData !== null) {
dispath(authSlice.actions.updateUser(userData))
}
}, [])
const routes: IRoute[] = user === undefined ? publicRoutes : privateRoutes
let indexElement: IRoute | undefined
const routeComponents = (
<Routes>
{routes.map<React.ReactNode>((route: IRoute) => {
if (route.index === true) {
indexElement = route
}
return <Route
path={route.path}
element={route.component()}
key={route.path}
/>
})}
{
indexElement !== undefined && <Route path='*' element={<Navigate to={indexElement.path} replace />} />
}
</Routes>
)
if (user === undefined) {
return routeComponents
}
return (
<PageWrapper>
{routeComponents}
</PageWrapper>
)
}
This exception is thrown when user is authenticated and react renders component from private route(private route is only one now). It started to throw when I added useEffect(arrow function with console.log and empty dependecies array) to this private component. If i remove useEffect from this component - exceptions will not be thrown. I tried to change routes.map to privateRoutes.map - then exceptions does not throw, but I can't understand the reason why it works so.
Project is react + typescript + redux toolkit

form using React hook form and redux toolkit. then routes in react router dom

I am trying to make a multi step form using react hook form and redux toolkit.
My forms are working fine. They are sending data. My 1st form is at "/" path. This gets completed then 2nd form comes at "/step2" path. But when 2nd form completes then routes becomes "/step2/step3". Why it is not "/step3" ? 3rd form comes at "/step3" . I am using useNavigate hook.
export const FormStep1 = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const email = useSelector((state) => state.email);
const password = useSelector((state) => state.password);
const {
handleSubmit,
register,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
defaultValues: { email, password },
});
const submit = (data) => {
dispatch(chooseEmail(data.email));
dispatch(choosePassowrd(data.password));
navigate("./step2");
};
Below is how i setup routes
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<FormStep1 />} />
<Route path="/step2" element={<FormStep2 />} />
<Route path="/step3" element={<FormStep3 />} />
</Routes>
</BrowserRouter>
);
}
please guide where i am doing wrong.
From the react-router-dom docs:
Either pass a To value (same type as <Link to>) with an **optional second { replace, state } arg or**
I believe the issue is that you are not passing the second argument to useNavigate() when routing to /step3.

Firebase authentication returning false when reloading the page

I'm currently working on a project with Firebase Integration and React.
I can register and login, but I do want some Guard in order to have access to the "connected" pages when I'm connected, or to be redirect to the default page for disconnected state if I'm not.
I coded the following for having the different Routes and set it in an App.tsx file :
const ConnectedRoute: React.FC<RouteProps> = ({ component, ...rest }) => {
var route = <Route />
var connected = isConnected()
console.log(connected)
if (connected){
route = <Route {...rest} component={component}/>
}
else{
route = <Route {...rest} render={() => <Redirect to ="/welcome" />} />
}
return route
}
const UnconnectedRoute: React.FC<RouteProps> = ({ component, ...rest }) => {
var route = <Route />
var connected = isConnected()
console.log(connected)
if (connected){
route = <Route {...rest} render={() => <Redirect to ="/home" />} />
}
else{
route = <Route {...rest} component={component}/>
}
return route
}
const App: React.FC = () => {
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route exact path="/" render={() => <Redirect to="/welcome" />} />
<UnconnectedRoute path="/welcome" component={UnconnectedHome} exact />
<ConnectedRoute path="/home" component={ConnectedHome} exact />
<UnconnectedRoute path="/login" component={Login} exact />
<UnconnectedRoute path="/register" component={Register} exact />
<ConnectedRoute path="/events/create" component={CreateEvent} exact />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
};
And for the firebase initialization I did the following :
export async function initializeApp(){
console.log("Checking if app is initialized...")
if (firebase.apps.length == 0){
console.log("App initializing...")
firebase.initializeApp(firebaseConfig);
await firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
}
}
And the function to know if a user is connected (as a cookie) is this one :
export function isConnected(){
const res = firebase.auth().currentUser
console.log(res)
return res !== null
}
Still, when I reload the tab, it's always returning me FALSE !
So, I was wondering how could I init the firebase server before the launch of the App ? Is this the current problem ? I currently do not have any clue about it and it frustrates me so much...
If you have already encountered such a problem, that would really help me !
Thank you !
Firebase Authentication persists the user's authentication state in local storage. But when you load a new page (or reload the existing one), it has to check with the server whether the authentication state is still valid. And this check takes some time, during which there is no current user yet.
The typical way to get around such race conditions is to use an auth state listener to detect the current user:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in, redirect to "connected" pages
} else {
// No user is signed in, probably require them to sign in
}
});
I tried to answer in comment with the code, it didn't work.
I did this for now (it's not that good but at least it works) :
const connectedPaths =
[
"/home",
"/events/create"
]
const unconnectedPaths =
[
"/welcome",
"/login",
"register"
]
firebase.initializeApp(firebaseConfig);
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
if (!connectedPaths.includes(window.location.pathname)) window.location.href = "/home"
}
else {
if (!unconnectedPaths.includes(window.location.pathname)) window.location.href = "/welcome"
}
});

How to implement Role based restrictions/permissions in react redux app?

I have a React-Redux-KoaJs application with multiple components. I have few user roles as well. Now i want to display few buttons, tables and div to only specific roles and hide those from others. Please remember i dont want to hide the whole component, but just a part of the components. Can anyone help me? Thanks in advance.
You can check the role or permission in every component as #Eudald Arranz proposed. Or you can write a component that will checks permissions for you. For example:
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const ShowForPermissionComponent = (props) => {
const couldShow = props.userPermissions.includes(props.permission);
return couldShow ? props.children : null;
};
ShowForPermissionComponent.propTypes = {
permission: PropTypes.string.isRequired,
userPermissions: PropTypes.array.isRequired
};
const mapStateToProps = state => ({
userPermissions: state.user.permission //<--- here you will get permissions for your user from Redux store
});
export const ShowForPermission = connect(mapStateToProps)(ShowForPermissionComponent);
and then you can use this component like this:
import React from 'react';
import { ShowForPermission } from './ShowForPermission';
cons MyComponent = props => {
return (
<div>
<ShowForPermission permission="DELETE">
<button>Delete</button>
</ShowForPermission>
</div>
);
}
Be careful with that. If the actions of some roles are important you should always validate them at your backend. It's easy to change the values stored in redux at frontend allowing malicious use of the roles if there is no proper validation.
If you want to proceed on a possible approach is this:
Save the roles at your reducer
Bind the reducer to the component:
function mapStateToProps(state) {
const { user_roles } = state;
return { user_roles };
}
export default connect(mapStateToProps)(YourComponent);
Then at your component, you can check the user_roles and render the actions accordingly:
render() {
return (
<div>
{this.props.user_roles.role === "YOUR_ROLE_TO_CHECK" && <ActionsComponent />}
</div>
);
}
This will render the ActionsComponent only when the role is equal to the desired one.
Again, always validate the roles at your backend!
The best practice to solve this Problem is, simply prevent the app to generate unnecessary routes, rather checking current user role on each route it is great to generate only the routes that user have access.
So The Normal reouting is:
To control the whole view:
const App = () => (
<BrowserRouter history={history}>
<Switch>
<Route path="/Account" component={PrivateAccount} />
<Route path="/Home" component={Home} />
</Switch>
</BrowserRouter>
export default App;
);
Routing based on user role:
import { connect } from 'react-redux'
// other imports ...
const App = () => (
<BrowserRouter history={history}>
<Switch>
{
this.props.currentUser.role === 'admin' ?
<>
<Route path="/Account" exact component={PrivateAccount} />
<Route path="/Home" exact component={Home} />
</>
:
<Route path="/Home" exact component={Home} />
}
<Route component={fourOFourErroPage} />
</Switch>
</BrowserRouter>
const mapStateToProps = (state) => {
return {
currentUser: state.currentUser,
}
}
export default connect(mapStateToProps)(App);
So the user with an Admin role will have access to the Account page and for other users will have access to the Home page Only! and if any user try to access to another route, the 404 page error will appear.
I hope I've given a helpful solution.
For advanced details about this approach you can check this repo on github: Role-based-access-control with react
To hide just a presentational component:
{this.props.currentUser.role === 'admin' && <DeleteUser id={this.props.userId} /> }
So, I have figured out there is an alternate and easy approach to implement role based access (RBAC) on frontend.
In your redux store state, create a object called permissions (or you can name it whatever you like) like this:
const InitialState = {
permissions: {}
};
Then on your login action, setup the permissions that you want to provide like this:
InitialState['permissions'] ={
canViewProfile: (role!=='visitor'),
canDeleteUser: (role === 'coordinator' || role === 'admin')
// Add more permissions as you like
}
In the first permission you are saying that you can view profile if you are not a visitor.
In the second permission you are saying that you can delete a user only if you are an admin or a coordinator.
and these variables will hold either true or false on the basis of the role of the logged in user. So in your store state u will have a permission object with keys that represent permissions and their value will be decided on the basis of what your role is.
Then in your component use the store state to get the permission object. You can do this using connect like:
const mapStateToProps = (state) => {
permissions : state.permissions
}
and then connect these props to your Component like:
export default connect(mapStateToProps,null)(ComponentName);
Then you can use these props inside your component on any particular element which you want to show conditionally like this:
{(this.props.permissions.canDeleteUser) && <button onClick={this.deleteUser}>Delete User</button>}
The above code will make sure that the delete user button is rendered only if you have permissions to delete user i.e. in your store state permissions object, the value of canDeleteUser is true.
That's it, you have appplied a role based access. You can use this approach as it is easily scalable and mutable, since you will have all permsisions according to roles at one place.
Hope, this helps! If i missed out something please help me in the comments. :-)
I have implemented this in this rbac-react-redux-aspnetcore repository.
If someone wants to use Redux with Context API, then the below code snippet can be helpful.
export const SecuedLink = ({ resource, text, url }) => {
const userContext = useSelector(state => {
return state.userContext;
});
const isAllowed = checkPermission(resource, userContext);
const isDisabled = checkIsDisabled(resource, userContext);
return (isAllowed && <Link className={isDisabled ? "disable-control" : ""} to={() => url}>{text}</Link>)
}
const getElement = (resource, userContext) => {
return userContext.resources
&& userContext.resources.length > 0
&& userContext.resources.find(element => element.name === resource);
}
export const checkPermission = (resource, userContext) => {
const element = getElement(resource, userContext);
return userContext.isAuthenticated && element != null && element.isAllowed;
}
export const checkIsDisabled = (resource, userContext) => {
const element = getElement(resource, userContext);
return userContext.isAuthenticated && element != null && element.isDisabled;
}
To use the above snippet, we can use it like below
<SecuedLink resource='link-post-edit' url={`/post-edit/${post.id}`} text='Edit'></SecuedLink>
<SecuedLink resource='link-post-delete' url={`/post-delete/${post.id}`} text='Delete'></SecuedLink>
So, depending on the role, you can not only show/hide the element, but also can enable/disable them as well. The permission management is fully decoupled from the react-client and managed in database so that you don't have to deploy the code again and again just to support new roles and new permissions.
This post is very exactly what you need without any library:
react-permissions-and-roles

Resources