How to pass state to React JS High Order Component - reactjs

I am using OIDC redux connector for user state. I have a few components that require authentication. I would like to use something like export default connect(mapStateToProps, mapDispatchToProps)(withAuth(Component)); and request data from state inside my authentication service.
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
export const withAuth = (Component) => {
return props => {
return <Component {...props} />
}
}
Is it possible to get state in the render function? So I can check the user beinig logged in and redirect to the sign-in page if there is no user signed in?
BTW: How would I redirect? I have tried using redirect-router-dom and <Redirect /> But then it complains about set state being changed too often ... But that might be my mistake. I get this error when I render a Redirect: Error: Maximum update depth exceeded.

If I understand correctly you want to "decorate" your component with additional logic to handle an authentication redirect?
I suggest using a "decorator" pattern here e.g.:
export const withAuth = (Component) => {
const mapStateToProps = (state, ownProps) => ({
authenticated: state.authenticated // Or what you need to do to determine this
});
return connect(mapStateToProps)(class extends React.Component {
render() {
const { authenticated, ...componentProps } = props;
if (authenticated) {
return <Component {...componentProps }>;
}
return <Redirect to="/login" />;
}
});
}
Then when you need this you can do things like:
export default withAuth(connect(yourOwnMapStateToProps)(YourComponent))

Just figured it out, I changed the store so instead of returning a function, it returns the object. So I can load in all js files. It might not be the best solution. If there is a better way to get the store in code, I would love to hear about how to do that. The configurestore function is what I found in quite a lot of examples.
import { store } from '../configureStore';
Using store.getState() I can get the current state.
The redirect issue I am having is similar to: How to use react-transition-group with react-router-dom

Related

Why is #auth0/nextjs-auth0 invoking /me api everytime I change route?

I just started using auth0 sdk for nextjs projects. Everything seems to be ok, but I have one little problem. Everytime I change route (while I am logged in) there is invoked the /me api. This means that the user that I got through useUser ctx becomes undefined and there is a little flash of the pages rendered only if logged in.
This is the general structure of my project
_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { appWithTranslation } from "next-i18next";
import { UserProvider } from "#auth0/nextjs-auth0/client";
import AppMenu from "../components/AppMenu";
function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider>
<AppMenu>
<Component {...pageProps} />
</AppMenu>
</UserProvider>
);
}
export default appWithTranslation(MyApp);
AppMenu is a component that I render only if I have the user. It's something like:
import { NextPage } from "next";
import { useUser } from "#auth0/nextjs-auth0/client";
interface Props {
children: JSX.Element;
}
const AppMenu: NextPage<Props> = ({ children }) => {
const { user } = useUser();
return (
<div>
{user && (<nav>{/* more stuff...*/}</nav>) }
{children}
</div>
);
};
export default AppMenu;
In this component I have the app's menu.
As I said before, when I switch from a route to another I can see in the network tab that there is called the /me endpoint and "user" is undefined. This implies that the app's menu (and all the protected components) is not rendered and there is a nasty flash of the page waiting for the response.
Is there a way to fix this? I was looking for a isLoggedIn prop but I haven't found anything.
What do you suggest?
p.s. Possibly, I would avoid a "loading" component
useUser() is a hooks call. Therefore, you need to use useEffect() with a Promise and wait until load the user. However, by the end of the day loading component is a must.
In the meantime, A feature called isLoading comes up with the useUser() state. You can improve your code like the below.
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
Source

How to get stored data from store.getState()

I have using react with redux for the first time.
In my app's render, if I console log store.getState() I can see the stored data {username: 'foo', password: 'bar'}
but when I want to get the data like store.getState().password
I get undefined error. I am trying to pass this data to
my components for private route as:
inside PrivateRoute I then try to check if the user is logged in or not to send to dashboard
so, how do I get data to pass to my props?
even const { isLoggedIn, username } = store.getState() doesn't work, it shows
Property isLoggedIn does not exist on type {}
btw I know this might be bad but it's my first react app so I am trying to learn redux
if you are calling the store from the react application you have to use provider and pass the store to the react app, and then bind state, actions and/or methods of the store to the react props as shown in this link
connect https://react-redux.js.org/5.x/api/connect
but if you are using redux in normal javascript then it will work fine.
example in react
first
import { Provider } from 'react-redux'
import { store } from "store";
ReactDOM.render(
<Provider store={store}>
<YourReactApp/> // e.g <Container />
</Provider>, document.getElementById('root')
);
then in you can bind anything from your store to react component like this
import { connect } from "react-redux";
const mapStateToProps = (state) => {
const { isLoggedIn, username }= state
return {
isLoggedIn,
username
};
};
const mapDispatchToProps = (dispatch :any) => {
return {
login: ()=> {
return dispatch(your action creator)
}
}
}
const Containter = connect(mapStateToProps,mapDispatchToProps)(AnyReactComponentYouWantToPassThisStore);
export default Containter;
the you can use it in your page like this
function AnyReactComponentYouWantToPassThisStore (props){
return(
<div> {props.username} </div>
)
}
then instead of calling <AnyReactComponentYouWantToPassThisStore />
now use <Container />

How to get access to state with higher order component

I want to create a higher order component that checks if a user has been logged in. If they have, I show the component if not, I want to redirect them to the login page.
can someone explain what I'm doing wrong here?
Here's the HOC:
import React from 'react';
import { connect } from 'react-redux';
const withAuthentication = (Component) => {
class WithAuthentication extends React.Component {
componentDidMount() {
console.log(this.props.sessionId);
}
render() {
return this.props.sessionId ? <Component {...this.props} /> : null;
}
}
const mapStateToProps = (state) => ({
sessionId: state.auth.userInfo.authUserInfo.sessionId
})
return connect(mapStateToProps, null)(WithAuthentication);
}
export default withAuthentication;
then I call it like this:
....
import withAuthentication from './containers/withAuthentication';
const Hello = () => <h1> Hello</h1>;
....
<Route path="/" component={ withAuthentication(Hello) }/>
I stripped more code that I think is unrelated to this...
TIA for you help!
Update: The code that is causing the propblem seems to be this:
const mapStateToProps = (state) => ({
sessionId: state.auth.userInfo.authUserInfo.sessionId
})
The error: TypeError: Cannot read property 'sessionId' of undefined
So basically, what you are doing in nesting one component into another i.e. a Functional component returning another component after some logical level verification.
What you are doing seems good. You should be able to access the props(not State), in a functional component like this
let aComponent(props)=>{
return <p>{props.name}'s logged in status is {props.loggedIn}</p>
}
if you are using the component like this
<aComponent title="Hello" loggedIn={this.props.isLoggedIn}/> //isLoggedIn from redux store
Also, If the Logical/Authentication verification fails in withAuthentication, you should call the router API to navigate to your desired page.
i.e.
you should call this, if you are using react-router
this.props.router.push('/login')

Route authorization HOC cause to remount children 3-times

I'm using an HOC component to restrict access to the route for non-logged users. The problem that this HOC remount children components while mounting or re-rendering when access this route directly from url(on the app first load). For example I have a 3 times did mount in the PaperWorkProgress component.
Route definition:
<Route path="/paperwork/progress" component={RequireAuth(PaperWorkProgress)}/>
Here the HOC code:
import React, {Component} from 'react';
import {connect} from 'react-redux';
export default function(ComposedComponent) {
class Authentication extends Component {
// check if token exists in storage
componentWillMount() {
const token = localStorage.getItem('token');
if (!token) {
const {pathname, search} = this.props.location;
this.props.history.push({
pathname: '/signin',
search: `?redirect_to=${pathname}${search}`,
});
}
}
// additional check
componentWillUpdate(nextProps) {
if (!nextProps.loggedIn) {
const {pathname, search} = this.props.location;
this.props.history.push({
pathname: '/signin',
search: `?redirect_to=${pathname}${search}`,
});
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return {loggedIn: state.session.loggedIn};
}
return connect(mapStateToProps)(Authentication);
}
Any ideas?
This question may be from a while ago already, but I just encountered the same problem.
In the end I found out that my HOC function was actually called on every route change.
What helped for me was to create the authorized component only once on initialization:
const AuthorisedDashboard = requireLogin(Dashboard);
and then later just use it
<Route path="/dashboard" component={AuthorisedDashboard} />
Or, you know, I guess you could just export the component with the HOC function already applied if it is only ever used in authorised mode...
I'm not sure this will make a difference about the re-rendering problem, but your code feels wrong.
First, you seems to have 2 source of truth, your redux store and the localStorage, which complicates things. If you want to "hydrate" your store from previous navigation information, you should use the createStore "preloadedState" argument, not checking everytime in your component. Cf Redux doc and this video from the creator of Redux himself Video for persisting and rehydrating State. Once your state comes only from your store it starts to be more simple.
Second,
When you push to the history object inside the component, It feels like you are mutating the component own props (as history is a prop). That feels weird to me and could be the root of your problem.
Why not use the Redirect component inside your render method like this instead ? cf React router docs. The component will looks like this (obviously you would need to change your Login component too, like in the docs)
import React, { Component } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
export default function(ComposedComponent) {
class Authentication extends Component {
render() {
return !this.props.loggedIn ? (
<Redirect
to={{
pathname: "/login",
state: { from: this.props.location }
}}
{...this.props}
/>
) : (
<ComposedComponent {...this.props} />
);
}
}
function mapStateToProps(state, ownProps) {
return { loggedIn: state.session.loggedIn, ...ownProps };
}
return connect(mapStateToProps)(Authentication);
}

Prevent routing in React when user manually changes url in browser tab

I am stuck in a issue that happens when user manually changes the route in browser tab and presses enter. This forces my react router to navigate to the state entered by user. I want to prevent this and allow routing only through the flow I have implemented by button clicks in my website.
Some of my screens need data that will be available only if the user navigates the site using the flow expected. If user directly tries to navigate to a particular route by manually changing the route in url then he may skip the desired flow and hence the app will break.
Other scenario, in case I want to restrict some users from accessing some routes but the user knows the path and manually enters that in browser url then he will be presented with that screen but should not be.
What I do is use a prop from previous page, if that prop is undefined(meaning user did not follow due process :) hehe ) I simply send the user back to the landing page or wherever.
You can create a route guard using HOC. For example, you don't want unauthorized user to pass route /profile, then you can do the following:
// requireAuthorized.js (HOC)
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {Redirect} from 'react-router-dom'
const connector = connect(
state => ({
isAuthorized: state.profile !== null // say, you keep user profile in redux
})
)
export default (WrappedComponent) => {
return (
connector(
class extends Component {
static propTypes = {
isAuthorized: PropTypes.bool.isRequired
}
render () {
const {isAuthorized, ...clearedProps} = this.props
if (isAuthorized) {
return <WrappedComponent {...clearedProps} />
} else {
return <Redirect to={{pathname: '/login'}} />
}
}
}
)
)
}
// ProfilePage.jsx
import React from 'react'
...
import requireAdmin from '../hocs/requireAdmin' // adjust path
class ProfilePage extends React.Component {
...
render () {
return (
<div>
...
</div>
)
}
}
export default requireAdmin(ProfilePage)
Pay attention to the export statement in my ProfilePage.js
I'd suggest using this library for cleanest solution (or at least make personal similar implementation of it).
Then you'd create authentication check HOC:
export const withAuth = connectedReduxRedirect({
redirectPath: '/login',
authenticatedSelector: state => state.user.isAuthenticated, // or whatever you use
authenticatingSelector: state => state.user.loading,
wrapperDisplayName: 'UserIsAuthenticated'
});
And you could easily create flow HOC:
export const withFlow = (step) = connectedReduxRedirect({
redirectPath: '/initial-flow-step',
authenticatedSelector: state => state.flow[step] === true,
wrapperDisplayName: 'FlowComponent'
});
Then initialize your component
const AuthenticatedComponent = withAuth(Dashboard)
const SecondStepComponent = withFlow("first-step-finished")(SecondStep)
const ThirdStepComponent = withFlow("second-step-finished")(ThirdStep)
You can easily create authenticated flow step by composing HOC:
const AuthSecondStepComponent = withAuth(withFlow("first-step-finished")(SecondStep))
Only thing that is important is that you update your redux state correctly as going through your step flow. When user finishes first step you'd set
state.flow["first-step-finished"] = true // or however you manage your state
so that when user navigates manually to specific page, he wouldn't have that redux state because its an in-memory state and would be redirected to redirectPath route.
Something like this is suitable. You make HOC Route with a wrap to function that deals with authentication/context props.
Note: this deals with direct access to the route, not to the menu items and such. That must be treated in a simmilar way on the menu / menuItem components.
import requireAuth from "../components/login/requireAuth";
class Routes extends React.Component<RoutesProps, {}> {
render() {
return (
<div>
<Switch>
<Route exact={true} path="/" component={requireAuth(Persons, ["UC52_003"])} />
<Route path="/jobs" component={requireAuth(Jobs, ["UC52_006"])} />
</Switch>
</div>
)
}
}
export default function (ComposedComponent, privileges) {
interface AuthenticateProps {
isAuthenticated: boolean
userPrivileges: string[]
}
class Authenticate extends React.Component<AuthenticateProps, {}> {
constructor(props: AuthenticateProps) {
super(props)
}
render() {
return (
isAuthorized(this.props.isAuthenticated, privileges, this.props.userPrivileges) &&
<ComposedComponent {...this.props} /> || <div>User is not authorised to access this page.</div>
);
}
}
function mapStateToProps(state) {
return {
isAuthenticated: state.userContext ? state.userContext.isAuthenticated : false,
userPrivileges: state.userContext ? state.userContext.user ? state.userContext.user.rights : [] : []
};
}
return connect(mapStateToProps, null)(Authenticate);
}
you can put the condition in useEffect of the given page/screen and push it back if it doesnt have the required values.. example below

Resources