Async throws error in React Functional Component - reactjs

I am trying to use use async in following functional component but throws error
const RouteConfig = async ({ component: Component, fullLayout, user, auth, ...rest}) => (
<Route
{...rest}
render={props => {
return (
<ContextLayout.Consumer>
{context => {
let LayoutTag =
fullLayout === true
? context.fullLayout
: context.state.activeLayout === 'horizontal'
? context.horizontalLayout
: context.VerticalLayout
const verified = await verifyToken(auth.values)
return (auth.values !== undefined && auth.values.isSignedIn && verified) ? (
<LayoutTag {...props} permission='{user}'>
<Suspense fallback={<Spinner />}>
<Component {...props}></Component>
</Suspense>
</LayoutTag>
) : (
<context.fullLayout {...props} permission={user}>
<Suspense fallback={<Spinner />}>
<Login {...props} />
</Suspense>
</context.fullLayout>
)
}}
</ContextLayout.Consumer>
)
}}
/>
)
const mapStateToProps = state => {
return {
user: state.auth.login.userRole,
auth: state.auth.login
}
}
const AppRoute = connect(mapStateToProps)(RouteConfig)
below is the verifyToken Function which return true or false
const verifyToken = async props => {
if (props.accessToken !== undefined) {
//if (props !== undefined) {
if (assertAlive(jwt.decode(props.accessToken))) {
const verified = await refreshToken(props)
console.log(verified)
if (verified){
console.log('Authorized')
return true
} else {
console.log('Unauthorized')
return false
}
} else {
return false
}
}else
return false
}
function assertAlive (decoded) {
const now = Date.now().valueOf() / 1000
if (typeof decoded.exp !== 'undefined' && decoded.exp < now) {
//throw new Error(`token expired: ${JSON.stringify(decoded)}`)
return false
}
if (typeof decoded.nbf !== 'undefined' && decoded.nbf > now) {
//throw new Error(`token not yet valid: ${JSON.stringify(decoded)}`)
return false
}
return true
}
above used refreshToken has a functional which gets response from an API call
export const refreshToken = async () => {
const options = { withCredentials: true };
const resp = await axios.post('http://localhost:4000/api/auth/verifyToken',{}, options).catch(err => {console.log(err); return false;});
//console.log(resp.status)
if (resp.status === 200){
//console.log('200 passed')
return true
}else if (resp.status === 401){
//console.log('401 failed')
return false
}else{
//console.log('204 failed')
return false
}
}
Any suggestions would be grateful to fix this issue. If someone has better way to code this please let me know. I am bit new into ReactJS and open for suggestions. FIrst function is used to check on each route if Token is authorized or not. If its is authorized it allows to access page and if not redirects to Login Page.
After suggestions from #Mordechai, I have made below changes but it throws an error
./src/Router.js
Line 193:40: React Hook "useState" is called in function "verified" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
Line 194:3: React Hook "useEffect" is called in function "verified" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
function verified(auth){
const [verified, setVerifiedValue] = useState(verifyToken(auth.values));
useEffect(() => { setVerifiedValue(verifyToken(auth.values) )})
return verified;
}
const RouteConfig = ({ component: Component, fullLayout, user, auth, ...rest}) => (
<Route
{...rest}
render={props => {
//useEffect(() => {const verified = verifyToken(auth.values) });
return (
<ContextLayout.Consumer>
{context => {
let LayoutTag =
fullLayout === true
? context.fullLayout
: context.state.activeLayout === 'horizontal'
? context.horizontalLayout
: context.VerticalLayout
console.log(VerifiedToken)
return (auth.values !== undefined && auth.values.isSignedIn && VerifiedToken) ? (
<LayoutTag {...props} permission='{user}'>
<Suspense fallback={<Spinner />}>
<Component {...props}></Component>
</Suspense>
</LayoutTag>
) : (
<context.fullLayout {...props} permission={user}>
<Suspense fallback={<Spinner />}>
<Login {...props} />
</Suspense>
</context.fullLayout>
)
}}
</ContextLayout.Consumer>
)
}}
/>
)
const mapStateToProps = state => {
return {
user: state.auth.login.userRole,
auth: state.auth.login
}
}
const AppRoute = connect(mapStateToProps)(RouteConfig)
const VerifiedToken = connect(mapStateToProps)(verified)

Hooks must be named useXxx otherwise eslint will complain. Also, hooks must be called on the top-level of a function component.
You call verifyToken() in both the useState() default value param and in the effect. If it's a long process you should only do it in the effect.
If you want to call verifyToken() in useVerify() just once in the lifetime of the component, you should add an empty array in the useEffect() dependency array
function useVerified(auth){
const [verified, setVerifiedValue] = useState();
useEffect(() => {
const doVerify = async () => {
setVerifiedValue(await verifyToken(auth.values))
}
doVerify()
}, [])
return verified;
}
const RouteConfig = ({ component: Component, fullLayout, user, auth, ...rest}) => {
const verified = useVerified(auth);
return <Route
{...rest}
render={props => {
return (
<ContextLayout.Consumer>
{context => {
let LayoutTag =
fullLayout === true
? context.fullLayout
: context.state.activeLayout === 'horizontal'
? context.horizontalLayout
: context.VerticalLayout
console.log(VerifiedToken)
return (auth.values !== undefined && auth.values.isSignedIn && VerifiedToken) ? (
<LayoutTag {...props} permission='{user}'>
<Suspense fallback={<Spinner />}>
<Component {...props}></Component>
</Suspense>
</LayoutTag>
) : (
<context.fullLayout {...props} permission={user}>
<Suspense fallback={<Spinner />}>
<Login {...props} />
</Suspense>
</context.fullLayout>
)
}}
</ContextLayout.Consumer>
)
}}
/>
}
const mapStateToProps = state => {
return {
user: state.auth.login.userRole,
auth: state.auth.login
}
}
const AppRoute = connect(mapStateToProps)(RouteConfig)
const VerifiedToken = connect(mapStateToProps)(verified)

Does the following work?
const NONE = {};
const RouteConfig = ({
component: Component,
fullLayout,
user,
auth,
...rest
}) => (
<Route
{...rest}
render={(props) => {
const [verified, setVerified] = React.useState(NONE);
React.useEffect(
() => verifyToken(auth.values).then(setVerified),
[]
);
if (verified === NONE) {
return null;
}
return (
<ContextLayout.Consumer>
{(context) => {
let LayoutTag =
fullLayout === true
? context.fullLayout
: context.state.activeLayout ===
'horizontal'
? context.horizontalLayout
: context.VerticalLayout;
return auth.values !== undefined &&
auth.values.isSignedIn &&
verified ? (
<LayoutTag {...props} permission="{user}">
<Suspense fallback={<Spinner />}>
<Component {...props}></Component>
</Suspense>
</LayoutTag>
) : (
<context.fullLayout
{...props}
permission={user}
>
<Suspense fallback={<Spinner />}>
<Login {...props} />
</Suspense>
</context.fullLayout>
);
}}
</ContextLayout.Consumer>
);
}}
/>
);

Related

Open a modal and update url without reloading the main page

I have primereact datatable (it is separate component InvoiceTable and it is part from another bigger copmonent AccountDetails) with many columns- every column has a selection mode and a button. When I click the button, it opens a new modal and changes the url.
The url of the main page (AccountDetails) is customers/customerCode and when I open the modal (I click on the icon from the InvoiceTable) it becomes customers/customerCode/invoiceNumber/changelog.
The issue is that when I select any invoices and open the modal, the main page is refreshed and the selected invoices are unselected.
These are the methods which I use:
openHistoryModal = (rowData, e) => {
e.stopPropagation();
this.props.history.push(`/customers/${customerCode}/number/changelog`);
};
discardHistoryModal = (e) => {
this.props.history.push(`/customers/${customerCode}`);
};
I tried with history.replace and window.history.replaceState but the result is the same. I use only class components.
I have the following Routes as well:
<ProtectedRoute
path="/customers/:customerCode/"
component={(props: any) => <AccountDetails {...props} />}
authority={[userRoles.user]}
feature={"Collection module"}
rootStore={this.props.rootStore}
exact={true}
/>
<ProtectedRoute
path="/customers/:customerCode/:number/changelog"
component={(props: any) => <AccountDetails {...props} />}
authority={
[userRoles.userAudit.toString()] || [userRoles.admin]
}
feature={"Collection module"}
rootStore={this.props.rootStore}
exact={true}
/>
These are the methods in the ProtectedRoute component:
checkAuthority = (roles) =>{
return this.props.rootStore.userStore.hasRoles(roles)
}
checkFeature = () => {
let featureActive: boolean = false;
const foundFeature =
this.props.rootStore.managementStore.featureFlags.filter(
(flag: { name: string; value: string; type: string }) =>
flag.name === this.props.feature && flag.value === "true"
);
if (
(foundFeature && foundFeature.length > 0) ||
this.props.feature === "alwaysActive"
) {
featureActive = true;
}
return featureActive
};
render() {
const { component: Component, ...props } = this.props;
return (
<Route
{...props}
render={(props) =>
this.checkAuthority(this.props.authority) && this.checkFeature() ? (
<Component {...props} rootStore={this.props.rootStore} />
) : (
<Redirect to='/404'></Redirect>
)
}
exact={this.props.exact}
/>
);
I will be very grateful if you could give me some ideas and help me out!

How can I avoid infinite loops in my React Router private routes?

In my App.js, I have some authenticated pages I protect with <PrivateRoute>, like so:
<PrivateRoute path="/dashboard">
<Dashboard />
</PrivateRoute>
I implement <PrivateRoute> like so:
function PrivateRoute({ children, ...rest }) {
return (
<Route {...rest} render={() => <CheckRedirect children={children} />} />
);
}
The problem is, the <CheckRedirect> function calls out to an endpoint on my server which dynamically tells you where to redirect.
Here's the function:
export const CheckRedirect = ({ children }) => {
const [isChecking, setIsChecking] = React.useState(true);
const [target, setTarget] = React.useState(null);
const url = "https://example.com/get-redirect"
useEffect(() =>{
async function getPage() {
axios.get(url)
.then(function (response) {
setTarget(response.data.message)
}).finally(() => setIsChecking(false))
}
getPage();
}, []);
if (isChecking) {
return "... Checking";
}
return {target} ? (
<Redirect to={target} />
) : (
<Redirect to='/404' />
);
};
If you're not logged in, it will send back "/login" in the message field. If you're logged in, it will send "/dashboard".
If it sends back "/dashboard", then React Router produces an infinite loop! It tries the same <PrivateRoute> again, which calls out to the endpoint again, which will once again return "/dashboard", and so on...
Is there a way I can tell my <PrivateRoute> to not do the <CheckRedirect> function if this is already the result of a redirect?
I haven't tested it myself, but have you tried passing path as a prop to CheckRedirect and only do the setTarget in your getPage fetch if it returns a different route?
function PrivateRoute({ children, path, ...rest }) {
return (
<Route {...rest} render={() => <CheckRedirect children={children} path={path} />} />
);
}
export const CheckRedirect = ({ children, path }) => {
const [isChecking, setIsChecking] = React.useState(true);
const [target, setTarget] = React.useState(null);
const url = "https://example.com/get-redirect"
useEffect(() =>{
async function getPage() {
axios.get(url)
.then(function (response) {
const newPath = response.data.message
if (path !== newPath) {
setTarget(newPath)
}
}).finally(() => setIsChecking(false))
}
getPage();
}, []);
if (isChecking) {
return "... Checking";
}
return {target} ? (
<Redirect to={target} />
) : (
<Redirect to='/404' />
);
};
To avoid CheckRedirect to do any redirect if everything is ok (ie. it's a valid request for that route), ensure CheckRedirect actually returns null in that case. If you have control over the server response, I'd return a different value (not null, but -1 for example) for non-existent routes (ie. to redirect to 404), and keep null for when you really just want to return null.
In CheckRedirect component, you don't even use children prop. It renders a string and then redirects to a page. It's normal that it loops forever. Pass path as a prop to CheckRedirect component and if it's same as server response, render the children.
Add path prop and pass it:
export const CheckRedirect = ({ children, path }) => {
Add your conditional before redirecting:
if (target === path) {
return children
}
Just change your PrivateRoute Logic to something like this
const PrivateRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render = { props =>
user.isOnline ? ( <Component {...props} /> ) :
(
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
)
}
then
<PrivateRoute exact path="/dashboard" component={Dashboard} />

Problem when dynamically registering routes in an application with microfrontends concept

I have an Typescript + Redux (with RTK) application using the microfrontends concept. All the steps for the construction came from this tutorial: Microfrontends tutorial.
The main component is Microfrontend.tsx (omitted imports):
interface Manifest {
files: {
'main.js': string
'main.js.map': string
'index.html': string
}
entrypoints: string[]
}
const MicroFrontend = ({
name,
host,
module
}: {
name: string
host: string | undefined
module: string
}) => {
const history = useHistory()
useEffect(() => {
const renderMicroFrontend = () => {
// #ts-ignore
window[`render${name}`] && window[`render${name}`](`${name}-container`, history)
}
if (document.getElementById(name)) {
renderMicroFrontend()
return
}
const manifestUrl = `${
isDevProfile ? host : ''
}/${module}/view/asset-manifest.json`
fetch(manifestUrl)
.then(res => res.json())
.then((manifest: Manifest) => {
const script = document.createElement('script')
script.id = name
script.crossOrigin = ''
script.src = `${host}${manifest.files['main.js']}`
script.onload = () => {
renderMicroFrontend()
}
document.head.appendChild(script)
})
return () => {
// #ts-ignore
window[`unmount${name}`] && window[`unmount${name}`](`${name}-container`)
}
})
return (
<main id={`${name}-container`} style={{ height: '100%' }} />
)
}
MicroFrontend.defaultProps = {
document,
window
}
export default MicroFrontend
I'm trying to render the routes of the child components in a dynamic way, however, when I do this, I have a very strange effect: Bug.
The code snippet that generates this effect is this (omitted imports):
const App = () => {
const dispatch = useAppDispatch()
const { loadWithSuccess } = useSelector(moduleSelectors)
const avaibleModuleLinks = useSelector(avaibleModuleLinksWhitoutHome)
useEffect(() => {
dispatch(fetchAvaibleModules()).then(response =>
dispatch(fetchAvaibleModuleLinks(response.payload as string[]))
)
}, [dispatch])
return (
<BrowserRouter>
<Template>
<Switch>
<Route exact={true} path="/" component={Home} />
{loadWithSuccess ? avaibleModuleLinks?.map(
(subMenuPath: SubMenuPath | undefined, index: number) => {
const subMenuPathKey = subMenuPath ? subMenuPath.key : ''
let micro = () => (
<MicroFrontend
module={subMenuPathKey}
host="127.0.0.1"
name={subMenuPath ? subMenuPath.key.charAt(0).toUpperCase() : ''}
/>
)
return (
<Route
key={index}
path={`/dfe/view/${subMenuPathKey}`}
component={micro}
/>
)
}
): <></>}
</Switch>
</Template>
</BrowserRouter>
)
}
export default App
Only when I don't render routes dynamically do I have the desired effect: desired behavior
The code snippet that generates this effect is this (omitted imports):
const ModuleNfe = () => (
<MicroFrontend host="127.0.0.1" name="Nfe" module="nfe" />
)
const App = () => {
const dispatch = useAppDispatch()
const { loadWithSuccess } = useSelector(moduleSelectors)
const avaibleModuleLinks = useSelector(avaibleModuleLinksWhitoutHome)
useEffect(() => {
dispatch(fetchAvaibleModules()).then(response =>
dispatch(fetchAvaibleModuleLinks(response.payload as string[]))
)
}, [dispatch])
return (
<BrowserRouter>
<Template>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/dfe/view/nfe" component={ModuleNfe} />
</Switch>
</Template>
</BrowserRouter>
)
}
export default App
As you may have noticed, the desired behavior is for my page to be rendered inside the Template component. But for some reason, this is not the case.

React navigation createStackNavigator switch between routes

I'm using React native with Expo and i'm trying to make a dynamic redirect navigation in my component.
When the user come to my page I want to check an async value and with the result of this value I want the user to be redirected to one of the Screen.
I'm using AppLoading from expo and redux state. But it's seems that I can't use navigate at this moment on my Stack.
Here is my code :
const Stack = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const dispatch = useDispatch()
const { appLoading } = useSelector(userStateSelector)
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
if (goToPage2) {
navigation.navigate('Page2')
} else {
navigation.navigate('Page1')
}
}
return (
appLoading ? (
<Stack.Navigator initialRouteName="Page1" headerMode="none">
<Stack.Screen name="Page1" component={Page1}/>
<Stack.Screen name="Page2" component={Page2}/>
</Stack.Navigator>
) : (
<AppLoading
startAsync={ navigateToRoute }
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}
Did I forgot something ?
I Implemented another solution with two different Stack navigation, but I don't know witch version is better to use ?
const Stack1 = createStackNavigator();
const Stack2 = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const dispatch = useDispatch()
const [check, setCheck] = useState(false)
const { appLoading } = useSelector(userStateSelector)
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
setCheck(goToPage2)
}
return (
appLoading ? (
check ? (
<Stack1.Navigator initialRouteName="Page1" headerMode="none">
<Stack1.Screen name="Page1" component={Page1Stack1}/>
<Stack1.Screen name="Page2" component={Page2Stack1}/>
</Stack1.Navigator>
) : (
<Stack2.Navigator initialRouteName="Page1" headerMode="none">
<Stack2.Screen name="Page1" component={Page1Stack2}/>
<Stack2.Screen name="Page2" component={Page2Stack2}/>
</Stack2.Navigator>
)
) : (
<AppLoading
startAsync={ navigateToRoute }
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}
Thanks for your help
You are right. You can't use navigation props at this stage.
Remove it from the component props and try doing it like this:
const Stack = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const [initialRouteName, setInitialRouteName] = React.useState('');
const [loading, setLoading] = React.useState(true);
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
if (goToPage2) {
setInitialRouteName('Page2')
} else {
setInitialRouteName('Page1')
}
setLoading(false);
}
return (
loading === false && initialRouteName.length > 0 ? (
<Stack.Navigator initialRouteName={initialRouteName} headerMode="none">
<Stack.Screen name="Page1" component={Page1} />
<Stack.Screen name="Page2" component={Page2} />
</Stack.Navigator>
) : (
<AppLoading
startAsync={navigateToRoute}
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}

Add propTypes to function

I would like add propTypes to my function in React for passing data.
I use createContainer (for Meteor Data) and i would like passing my Data for test if user is logged and if is admin for render my component.
My AdminLayout (using in my React Router) :
const AdminLayout = ({component: Component, ...rest}) => {
console.log(AdminContainer)
if (AdminLayout === true) {
return (
<Route {...rest} render={matchProps => (
<div className="app-container">
<HeaderAdmin />
<main className="l-main">
<Component {...matchProps} />
</main>
<FooterAdmin />
</div>
)} />
)
} else {
return (
<Redirect push to="/connexion"/>
)
}
};
AdminLayout.propTypes = {
isLogged: React.PropTypes.bool,
isAdmin: React.PropTypes.bool
}
AdminContainer = createContainer(() => {
const isLogged = Meteor.userId();
const isAdmin = Meteor.call('is-admin', Meteor.userId(), function(err, data) {
if (err) {
return console.log(err);
} else {
return data;
}
});
return {
isLogged,
isAdmin
};
}, AdminLayout);
export default AdminLayout;
My console.log() return juste function ReactMeteorData() :/ I don't know how i can passing my data in my function.
Anyone can help me ?
Thank you community !

Resources