useNavigate() issue in micro frontend app - reactjs

I'm designing a MFE app called header. Implementation is something like below,
header.js
const headerApp = () => {
const navigate = useNavigate();
const logoClickHandler = () => {
navigate('/some-route'); // router v6
}
return(
...
<Logo onClick={logoClickHandler} />
)
}
App.js
I want to keep/use it like below
const App = () = {
return(
<div>
<HeaderApp /> // This component just uses useNavigation or <NavLink to='/some-route' />
</div
)
}
Problem is Header app doesn't have its own routing mechanism in it. It is just a separate app and to be more specific standalone component and just provides navigations among different MFE apps using useNavigate() OR <NavLink /> router feature.
Since, I'm using useNaviage() OR <NavLink />, react is asking me to wrap the component inside <Routes> (as shown below) which is unnecessary for my header app.
React Error
useNavigate() may be used only in the context of a <Router> component.
don't want to end up like below,
const App = () = {
return(
<div>
<Routes>
<Route path='/' element={ <HeaderApp /> } />
</Routes>
</div
)
}
NOTE : Routing is handled in separate app called container. Header only provides links for navigations.

React Router uses React Context, which is a way of passing information down through the React tree. Because of this, you only need to make sure you have at least one <Router> as a parent of whatever component is rendering <headerApp /> for this to work.
If this is not acceptable to you - you want your application to be used in non-React router contexts, for example - you may want to refactor your header application such that it either provides its own React Router instance or accepts the required methods and attributes through props.
It is not possible to use <NavLink /> or useNavigate() without one of the parents of <headerApp /> using Router />.

Related

useNavigate() may be used only in the context of a <Router> component is the error that I'm getting in the console

I think it's because I'm using the latest version of react and they have changed many components, since. All I want to do is navigate to another page.
I tried wrapping it it
<Routes>
<button
className="whatevr"
onClick={() => {
navigate("../FeaturePage");
}}
>
Features
</button>
</Routes>
Based on both errors regarding the use of the useNavigate and useRoutes hooks both needing to be used within a routing context, i.e. called with in a <Router> component, it seems that you are not actually doing this.
Render the app, or these specific components within a router.
Example:
const App = () => (
<>
<Navbar />
<Routes>
<Route path="/" .... />
... other routes ...
</Routes>
</>
);
Parent component rendering App.
<BrowserRouter>
</App />
</BrowserRouter>

Control conditional rendering of router in next.js with feature flag

I am migrating my React app from react-router-dom to next.js. Previously in react router, I was using a hook useFeatureFlag in my top level component to would return a boolean which I could use to condtionally render a page.
const App = (): JSX.Element => {
const { showCalendar } = useFeatureFlags();
return (
<Switch>
{showCalendar && <AppRoute path="/calendar" component={Calendar} />}
</Switch>
);
};
How can I do the same thing in next.js? Next.js doesn't have one component where all the pages are defined, but rather uses the pages/ folder structure to infer routes, so I'm not sure how to put a boolean infront of pages/calendar to control the rendering of the Calendar component.

Recommended approach for route-based tests within routes of react-router

I'm using react-testing-library within a project of mine and am trying to write tests that validate in-app routing.
e.g. testing that a button on the AccessDenied page brings you back to the Home page.
I've been able to write these sorts of tests successfully for my App component because it defines all of the app routes. But if AccessDenied is one of those routes, how do I need to set up my tests to validate a button clicked there will route my back to Home?
Here is a contrived example:
App.tsx
<>
<Router>
<Route exact path="/" component={Home} />
<Route exact path="/access-denied" component={AccessDenied} />
</Router>
<Footer />
</>
AccessDenied.tsx
<div>
<div>Access Denied</div>
<p>You don't have permission to view the requested page</p>
<Link to="/">
<button>Go Home</button> <--- this is what i want tested
</Link>
</div>
As I said earlier the reason my tests work inside App.test.tsx is because my App component defines the routes inside itself, whereas my AccessDenied is just one of those routes. However, is it possible to leverage the router defined in my App.tsx in my AccessDenied.test.tsx tests? Perhaps I'm approaching this problem incorrectly? That's where I'm struggling. For reference, here is my working App.test.tsx tests.
App.test.tsx
describe('App', () => {
it('should allow you to navigate to login', async () => {
const history = createMemoryHistory()
const { findByTestId, getByTestId } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<AuthContext.Provider
value={{
authState: AUTH_STATES.UNAUTHENTICATED,
}}
>
<Router history={history}>
<App />
</Router>
</AuthContext.Provider>
</MockedProvider>,
)
fireEvent.click(getByTestId('sidebar-login-button'))
expect(await findByTestId('login-page-login-button')).toBeInTheDocument()
fireEvent.click(getByTestId('login-page-register-button'))
expect(await findByTestId('register-page-register-button')).toBeInTheDocument()
})
})
Any thoughts or suggestions are appreciated!
If you think about the responsibility of the AccessDenied component, it isn't really to send the user home. That's the overall behaviour you want, but the component's role in that is simply to send the user to "/". At the component unit level, therefore, the test could look something like this:
import React, { FC } from "react";
import { Link, Router } from "react-router-dom";
import { fireEvent, render, screen } from "#testing-library/react";
import { createMemoryHistory } from "history";
const AccessDenied: FC = () => (
<div>
<div>Access Denied</div>
<p>You don't have permission to view the requested page</p>
<Link to="/">
<button>Go Home</button>
</Link>
</div>
);
describe("AccessDenied", () => {
it("sends the user back home", () => {
const history = createMemoryHistory({ initialEntries: ["/access-denied"] });
render(
<Router history={history}>
<AccessDenied />
</Router>
);
fireEvent.click(screen.getByText("Go Home"));
expect(history.location.pathname).toBe("/");
});
});
Note that "/" is the default path, so if you don't provide initialEntries the test passes even if the click doesn't do anything...
At that point you might be thinking "but what if the home route changes?" If you moved the home page to "/home", for example, this test would continue to pass but the application would no longer actually work. This is a common problem with relying too much on very low-level tests and is where higher-level tests come into play, including:
Integration: render the whole App and use fireEvent to simulate navigation. This is challenging in your current setup, because the Router is at the App level; I'd move the Router to index.tsx and have a Switch in App.tsx instead, so you can render App within a MemoryRouter or use the createMemoryHistory method I show above (I've done this in this starter kit for example).
End-to-end: use a browser driver (e.g. Cypress or the various Selenium-based options) to automate actual user interactions with the app.
I haven't got as far as showing tests for routing, but do cover these different levels of test for a simple React app on my blog.
In React Router v6, you need to update the Router usage slightly (see "Cannot read properties of undefined (reading 'pathname')" when testing pages in the v6 React Router for details):
render(
<Router location={history.location} navigator={history}>
<AccessDenied />
</Router>
);

Rest API calls in REACT

I am working on existing code base for a react application. I am new to React and the developer who wrote this left.
In my current code base "request-promise-native" is pass from one component to another component using props. I don't get this. Why not just import it again in every component?
App.tsx
import rp from 'request-promise-native';
const App: React.FC = () => {
return (
<Router>
<Route path="/" render={(props) => <Main {...props} rp={rp} />}></Route>
</Router>
)
};
After reading the documentation of https://www.npmjs.com/package/request-promise it appears this is completely pointless and you are correct about imports.

Passing props to component inside router

I want to use URL paths for my app. Currently I just render a Main component in app.js:
render() {
return (
<Main
isLoggedIn={this.state.isLoggedIn}
user={this.state.user}
... />
)
}
(The props are a bunch of variables and functions)
I tried using react-router but I don't understand how to send props to the child components.
<Router history={browserHistory} >
<Route path='/' component={Main}
... where to send props? />
<Route path='join' component={Join}
... props />
</Router>
The problem is that I need some state variables of my App component (in app.js) to be available in both Main and Join. How do I achieve that with a router?
App structure:
App
- Join
- Main
- ...
It depends how you are managing your state. if you use the Redux, you don't need to pass props in router, you can simple access redux in any component using connect, However if you don't use react then you can use the Redux router's hierarchy functionality
Assume you have a route something like this
<route path="/student" component={studentHome}>
<route path="/class" component={classHome} />
</route>
when you access the path
/student/class
React router will load the classHome component as children of StudentHome and you can simple pass props to classHome component.
you student class Render method will be something like
render(){
const childrenWithProps = React.Children.map(this.props.children,
(child) => React.cloneElement(child, {
doSomething: this.doSomething
})
);
return <div> some student component functionality
<div>
{childrenWithProps}
</div>
</div>
}
more details about passing props to children:
How to pass props to {this.props.children}
You can use standard URL parameters. For example, to pass an ID:
<Route path='join/:id' component={Join}/>
You'll need to use browserHistory:
browserHistory.push(`/join/${id}`);
Then use this.props.params.id on Join.
Here's a more in-depth explanations https://stackoverflow.com/a/32901866/48869

Resources