Can't pass a function to a component via <Route /> - javascript

setErm is a function and is undefined in Erm component. Although the App component receives it. If I pass anything like something='foo' the ERM component get's it but not setErm={props.setErm}
const App = props => {
console.log("props in App", props);
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={props.setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};

The problem in your code is that in your Erm component props here refer to the Route component props not App props so you can change your code to use the props of App to something like this:
const App = props => {
console.log('props in App', props);
const appProps = props; // add this line to refer to App props
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={appProps.setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
Or you can use destructuring:
const App = ({ setErm }) => {
console.log('props in App', props);
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
As you can see you now able to use the top props of App component

Related

React Router v6 not working with URL Slugs

I'm trying to make react-router-dom work with a simple url: /user/{name} but for some reason cannot get it to load the page with the url slug for the name.
This is the return of my App function component:
<>
<MainNavBar navigation={navigation} />
<Routes>
<Route index={true} element={<Home />} exact />
<Route path="user" element={<User />} exact>
<Route
path=":name"
render={
({ match: { params: { name } } }) => {
console.log(name);
console.log("test2");
return (<UserPage
userName={name}
/>);
}}
/>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</>
This is the User component; a placeholder for my debugging atm.
const User = () => (
<div>
<header className="App-header">
<Outlet />
</header>
</div>
);
When I go to http://localhost:3000/user/test it loads the User component but not the children (the Outlet/UserPage elements)
I've tried lots of combinations but seem to be doing something wrong, so any help would be very appreciated. Thanks!
In react-router-dom v6 the Route components no longer have render or component props, they render their components on the element prop. Use the useParams hook to access the route match params. If UserPage is a component that can't use React hooks, then use a wrapper function component to access the route match param and pass it as a prop.
const UserPageWrapper = () => {
const { name } = useParams();
useEffect(() => {
console.log({ name }); // <-- log param in effect
}, [name]);
return <UserPage userName={name} />;
};
...
<>
<MainNavBar navigation={navigation} />
<Routes>
<Route index element={<Home />} />
<Route path="user" element={<User />}>
<Route path=":name" element={<UserPageWrapper />} />
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</>

Protected Routes and Nested Routes are not working together in react routing

My nested privateRoute is not working.
When I changed the nested routes in normal routing like /route everything works fine but as I switch to nested routes like /route/nested-route PrivateRoute component loses its functionality.
This is the main routing in my app component.
function App() {
return (
<Suspense fallback={<Loader />}>
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/login">
<ProvideAuth>
<Login />
</ProvideAuth>
</Route>
<Route path="/signup">
<SignUpProvider>
<SignUp />
</SignUpProvider>
</Route>
<PrivateRoute path="/home" component={UserHome} />
<PrivateRoute path="/profile" component={UserProfile} />
<Route path="*" component={Error} />
</Switch>
</Suspense>
);
}
PrivateRoute Component works fine in this main routing.
This is code of nested and protected route.
const UserHome = () => {
const { url } = useRouteMatch();
return (
<Wrapper>
<Header />
<Suspense fallback={<Loader />}>
<Switch>
<PrivateRoute exact path={`${url}/feed`} component={Feed} />
<PrivateRoute path={`${url}/search`} />
<PrivateRoute path={`${url}/addUser`} />
<PrivateRoute path={`${url}/notification`} />
<PrivateRoute path={`${url}/profile`} component={UserProfile} />
</Switch>
</Suspense>
<BottomNavbar />
</Wrapper>
);
};
PrivateRoute is not working in this nested routing. The redirect route is not firing when the user logs out.
Code for PrivateRoute Component.
const PrivateRoute = ({ children, ...rest }) => {
const cookie = document.cookie;
return (
<Route
{...rest}
render={() => {
return cookie ? children : <Redirect to="/login" />;
}}
/>
);
};
I think the issue here is that the cookie value is computed once, initially, when the PrivateRoute component is mounted and rendered. It's a stale value from the enclosure.
Move the cookie into the returned render function so it's accessed when the path is matched.
const PrivateRoute = ({ children, ...rest }) => (
<Route
{...rest}
render={() => document.cookie ? children : <Redirect to="/login" />}
/>
);

React component rerendering indefinitely when using Context with separate routes in Router

I have separate routes for some pages in my app, and I'm using Context to pass some props between 2 components. How the router part looks:
const NonLandingPages = () => {
return (
<div className='w-full flex flex-wrap min-h-screen bg-dark bg-main-bg 3xl:bg-main-bg-xl text-white z-10'>
<TrackedSitesProvider
value={{
trackedSites,
setTrackedSites,
updateTrackedSites
}}
>
<Switch>
<Route path='/login' component={LoginPage} />
<Route path='/signup' component={SignupPage} />
<Route path='/overview' component={OverviewPage} />
<Route path='/add_new_site' component={AddNewSite} />
<Route path='/registration_successful' component={RegistrationSuccessful} />
<Route path='/confirm_registration/:token' component={ConfirmRegistration} />
<Route path='/password_reset_successful' component={PasswordResetSuccessful} />
<Route path='/delete_successful' component={DeleteSuccessful} />
<Route exact path='/reset_password' component={ResetPassword} />
<Route path='/reset_password/:token' component={ConfirmResetPassword} />
<Route path='/delete_account' component={DeleteAccount} />
<Route path='/send_review' component={SendReview} />
<Route path='*' component={PageNotFound} />
</Switch>
</TrackedSitesProvider>
</div>
);
};
return (
<div className='app-basic-appearance'>
<Router>
<Switch>
<Route exact path='/' >
<LandingPage/>
</Route>
<Route component={NonLandingPages} />
</Switch>
</Router>
</div>
);
Context code:
import { createContext, useContext } from 'react';
import { TrackedSites } from '../types/';
interface ContextProps {
trackedSites: TrackedSites,
setTrackedSites: Function,
updateTrackedSites: Function
}
export const TrackedSitesContext = createContext<ContextProps | undefined>(undefined);
export const TrackedSitesProvider = TrackedSitesContext.Provider;
export const useTrackedSites = (): ContextProps => {
const context = useContext(TrackedSitesContext);
if (context === undefined) {
throw new Error('useTrackedSites must be used within a TrackedSitesProvider');
}
return context;
};
Function which uses context:
const trackedSitesContext = useTrackedSites();
useEffect(() => {
(async () => {
const token = await auth.checkTokenAndRefreshIfPossible();
if (!token) {
history.push('/login');
return;
}
const data = await getExistingSites();
if (data) {
trackedSitesContext.setTrackedSites(data);
}
setWaitingForServerResponse(false);
})();
}, []);
For whatever reason, component which contains useEffect hook pictured above is constantly getting re-rendered. Problem disappears if I put all of my routes in 1 place, like this:
<TrackedSitesProvider
value={{
trackedSites,
setTrackedSites,
updateTrackedSites
}}
>
<Router>
<Switch>
<Route exact path='/' component={LandingPage} />
<Route path='/login' component={LoginPage} />
<Route path='/signup' component={SignupPage} />
<Route path='/overview' component={OverviewPage} />
<Route path='/add_new_site' component={AddNewSite} />
<Route path='/registration_successful' component={RegistrationSuccessful} />
<Route path='/confirm_registration/:token' component={ConfirmRegistration} />
<Route path='/password_reset_successful' component={PasswordResetSuccessful} />
<Route path='/delete_successful' component={DeleteSuccessful} />
<Route exact path='/reset_password' component={ResetPassword} />
<Route path='/reset_password/:token' component={ConfirmResetPassword} />
<Route path='/delete_account' component={DeleteAccount} />
<Route path='/send_review' component={SendReview} />
<Route path='*' component={PageNotFound} />
</Switch>
</Router>
</TrackedSitesProvider>
Any kind of help is appreciated.

Nested route in React Router reloads the entire page

I am trying to setup nesting in the react router. I have the following code:
import React from 'react';
import DefaultSwitch from './components/DefaultSwitch/DefaultSwitch';
import './scss/App.scss';
const App = () => {
return (
<DefaultSwitch />
);
};
export default App;
With DefaultSwitch defined as:
const DefaultSwitch = () => {
return (
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
);
}
Inside the Dashboard I have the following:
const Dashboard = () => {
return (
<div>
<MyNavbar />
<DashboardSwitch />
</div>
);
};
And finally DashboardSwitch as:
const DashboardSwitch = () => {
return (
<Switch>
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
);
}
Routing appears to work and the correct components are loaded, however I have noticed that if for example I am at /dashboard and then navigate to /dashboard/bonuses the entire page is reloading including the MyNavbar component. I want the navbar to remain static and only the content below it to reload as I have defined in the Dashboard component.
What am I doing wrong here?
Consider using a layout common to all components or something like this to avoid lose MyNavbar, for example:
const App = () => (
<BrowserRouter>
<Layout>
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
</Layout>
</BrowserRouter>
)
const Layout = ({ children }) => (
<div>
{children}
<MyNavbar />
</div>
);

React-router: testing inside the `render` prop with enzyme

I'd like to test a redirection from the / path to a locale path (e.g. /en). So here's what the component looks like:
// GuessLocale is imported from a helper
const App = () => (
<Router>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/${guessLocale()}`} />
)} />
<Route exact path='/:lang' component={Home} />
</Switch>
</Router>
)
And this is the current testing function:
it('redirects to a localed path', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
)
expect(wrapper.find('Redirect')).toHaveLength(1)
})
Obviously, the test fails as the Redirect component is inside a child as a function as the render prop to the Route
In the test, I wrap the App in a memory router but in the App component, a browser router is already present so I might need to refactor that.
But even with the routes splitted in a Routes component, I don't know how to test inside the render prop.
You can test this by checking the component that should be rendered after the redirection, in this case the Home component like this:
it('redirects to a localed path', () => {
let wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/en`} />
)} />
<Route path='/en' component={Home} />
<Route render={() => "not found"} />
</Switch>
</MemoryRouter>
)
expect(wrapper.find(Home)).toHaveLength(1)
})
I had to remove <Router> to get this working since we're not using it for the browser. Another way of doing this is to check the <Route> pathname property within the location prop. see here:
it('redirects to a localed path', () => {
let wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<Switch>
<Route exact path='/' render={() => (
<Redirect to={`/en`} />
)} />
<Route path='/en' component={Home} />
<Route render={() => "not found"} />
</Switch>
</MemoryRouter>
)
expect(wrapper.find("Route").prop('location').pathname).to.equal("/en")
})

Categories

Resources