I am developing an react js app using functional components.
I am trying to reuse components in the code. I have a component Frame which has Sider and Header. I am trying to add Content component to that frame to display in the middle, but its not working though.
Frame.tsx
const Frame : React.FC = (props) => {
const [collapsed, onCollapse] = useState(false);
const Content = props.content;
console.log('Content: ',Content);
return (
<Layout>
<SideBar state={{ collapsed: [collapsed, onCollapse]}}/>
<Layout>
<HeaderBar state={{ collapsed: [collapsed, onCollapse]}}/>
{Content}
<Footer>footer</Footer>
</Layout>
</Layout>
);
}
export default Frame;
PublicRoute.tsx
interface PublicRouteProps extends RouteProps {
// tslint:disable-next-line:no-any
component: any;
isAuthorized: boolean;
content: Content;
}
const PublicRoute = (props: PublicRouteProps) => {
const { component: Component, isAuthorized, content: Dummy, ...rest } = props;
return (
<Route
{...rest}
render={(routeProps) =>
isAuthorized ? (
<Component {...routeProps}/>
) : (
<Redirect
to={{
pathname: '/login',
state: { from: routeProps.location }
}}
/>
)
}
/>
);
};
export default PublicRoute;
App.tsx
return (
<BrowserRouter>
<div>
<Switch>
<PublicRoute path="/" component={Frame} exact isAuthorized={true} content={Dummy}/>
<Route path="/login" component={NewLogin} exact isAuthorized={true}/>
</Switch>
</div>
</BrowserRouter>
);
I am not able to pass contents dynamically and I am not sure whats wrong.
Thank you for your time.
You have to pass to the component with <Content /> otherwise it won't be instantiated.
Here's a full example
import React from "react";
import "./styles.css";
function Parent({content}) {
return (
<div>
{content}
</div>
)
}
function Content() {
return (
<h1>Hello</h1>
)
}
export default function App() {
return (
<div className="App">
<Parent content={<Content/>} />
</div>
);
}
You pass the components like so: .
Try something like this:
return (
<BrowserRouter>
<div>
<Switch>
<PublicRoute path="/" component={<Frame />} exact isAuthorized={true} content={<Dummy />}/>
<Route path="/login" component={<NewLogin />} exact isAuthorized={true}/>
</Switch>
</div>
</BrowserRouter>
);
I guess we can use the createElement function in place of {content}.
{React.createElement("NewLogin");}
Related
I started learning react about 15 days back. The following code adds the post correctly but does not redirect to "/". I am using react-router-dom v6.
render(){
return <div>
<Routes>
<Route exact path="/" element={
<div>
<Title title={'Arijit - Photowall'}/>
<Photowall posts={this.state.posts} onRemovePhoto={this.removePhoto} />
</div>
} >
</Route>
<Route path="/addPhotos" element={
<AddPhoto onAddPhoto={(addedPost)=>{
this.addPhoto(addedPost)
}}
>
<Navigate to="/" />
</AddPhoto>
}/>
</Routes>
</div>
}
In react-router-dom#6 the way to issue imperative navigation actions is to use the navigate function returned from the useNavigate hook. The code you've shared in the snippet is from a class component though, so you'll need to create a Higher Order Component to use the useNavigate hook and inject the navigate function as a prop.
Example:
import { useNavigate } from 'react-router-dom';
const withNavigate = Component => props => {
const navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
};
Decorate the component in your snippet with this withNavigate HOC.
export withNavigate(MyComponent);
Access the navigate function from props.
render(){
const { navigate } = this.props;
return (
<div>
<Routes>
<Route
path="/"
element={(
<div>
<Title title={'Arijit - Photowall'}/>
<Photowall posts={this.state.posts} onRemovePhoto={this.removePhoto} />
</div>
)}
/>
<Route
path="/addPhotos"
element={(
<AddPhoto
onAddPhoto={(addedPost) => {
this.addPhoto(addedPost);
navigate("/");
}}
/>
)}
/>
</Routes>
</div>
);
}
Using Typescript
interface WithRouter {
location: ReturnType<typeof useLocation>;
navigate: ReturnType<typeof useNavigate>;
params: ReturnType<typeof useParams>;
}
const withRouter = <P extends {}>(Component: React.ComponentType<P>) => (
props: Omit<P, keyof WithRouter>
) => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return <Component {...(props as P)} {...{ location, navigate, params }} />;
};
Example Usage:
interface MyComponentProps {
foo: string;
}
type MyComponentPropsWithRouter = MyComponentProps & WithRouter
class MyComponent extends React.Component<MyComponentPropsWithRouter> {
render() {
const { foo, navigate, location, params } = this.props;
const { bar } = params as { bar?: string };
return (
<>
<h1>MyComponent: {location.pathname}</h1>
<h2>Foo prop: {foo}</h2>
<h2>Param?: {bar}</h2>
<button type="button" onClick={() => navigate("/test")}>
Navigate
</button>
</>
);
}
}
const MyDecoratedComponent = withRouter(MyComponent);
Routes must be contained into a Router (Usually) BrowserRouter, so, you should put them all inside of that component, something like this:
<BrowserRouter>
<div className="App">
<Box data-testid="app-container">
<Routes>
<Route path={"/"} element={<Home />} />
<Route path={"/edit"} element={<edit/>} />
<Route path={"/whatever"} element={<whatever/>} />
</Routes>
</Box>
</div>
</BrowserRouter>
regarding to the navigate, in react-router-dom v6 you must use the hook useNavigate() and it works as:
const navigate = useNavigate();
<Button
text={ES.common.back}
onClick={() => navigate("/")}
></Button>
You'll have to import
import { useNavigate } from "react-router-dom";
Here's some documentation that you may find it helpful
I've had a problem with migration between react-router-dom v5 to v6. After update my package I do a few upgrades in the code. Below is my version of code in v5 and with v6 and also Routing component which give me a problem.
v5
export default function App() {
return (
<div className="App">
<Switch>
<AppContextProvider>
<Routing />
</AppContextProvider>
</Switch>
</div>
);
}
v6
export default function App() {
return (
<div className="App">
<AppContextProvider>
<Routes>
<Routing />
</Routes>
</AppContextProvider>
</div>
);
}
<Routing /> component
const Routing = (): JSX.Element => {
return (
<>
{publicRouting.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
{isAuthenticated() && (
<Main>
{routing.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
</Main>
)}
</>
);
};
Right now console throw me this error:
index.tsx:19 Uncaught Error: [Routing] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
can someone tell me how to solve this problem? It looks like this component has incompatible type and cannot go as children between Routes component, but why they changed it? How to correct my Routing component?
Thanks for any help!
In react-router-dom v6 the Routes component can have only Route or React.Fragment as a valid child, and the Route component can only have Routes or another Route component as parent.
The Routes component effectively replaced the RRDv5 Switch component in that it handles the route matching and rendering logic, and is now required to directly wrap the Route components it's managing.
Move the Routes component into Routing to wrap directly the Route components being mapped.
export default function App() {
return (
<div className="App">
<AppContextProvider>
<Routing />
</AppContextProvider>
</div>
);
}
...
const Routing = (): JSX.Element => {
return (
<>
<Routes>
{publicRouting.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
</Routes>
{isAuthenticated() && (
<Main>
<Routes>
{routing.map(({ path, component }, i) => (
<Route key={i} path={path} element={component} />
))}
</Routes>
</Main>
)}
</>
);
};
I have a web app which is under development which is just like google drive using firebase. I have this useParams() in Dashboard Screen which is the main page of the App with All the different Folder Routes. So for this screen i have used useParams and now when i console.log(params) it shows an empty object {} and also when i click the button it does not navigate only the URL changes
Github Code :- https://github.com/KUSHAD/RDX-Drive/
In App.js
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import PrivateRoute from './Components/Route/PrivateRoute';
import Dashboard from './Screens/Main/Dashboard';
import ViewProfile from './Screens/Profile/ViewProfile';
import Signup from './Screens/Auth/Signup';
import Login from './Screens/Auth/Login';
import ForgotPassword from './Screens/Auth/ForgotPassword';
function App() {
return (
<>
<div className='App'>
<div className='main'>
<BrowserRouter>
<Switch>
{/* Drive */}
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
{/* Profile */}
<PrivateRoute path='/profile' component={ViewProfile} />
{/* Auth */}
<Route path='/signup' component={Signup} />
<Route path='/login' component={Login} />
<Route path='/forgot-password' component={ForgotPassword} />
</Switch>
</BrowserRouter>
</div>
</div>
</>
);
}
export default App;
In Dashboard.js
import NavBar from '../../Components/Shared/NavBar';
import Container from 'react-bootstrap/Container';
import AddFolderButton from '../../Components/Main/AddFolderButton';
import { useDrive } from '../../services/hooks/useDrive';
import Folder from '../../Components/Main/Folder';
import { useParams } from 'react-router-dom';
export default function Dashboard() {
const params = useParams();
console.log(params);
const { folder, childFolders } = useDrive();
return (
<div>
<NavBar />
<Container fluid>
<AddFolderButton currentFolder={folder} />
{childFolders.length > 0 && (
<div className='d-flex flex-wrap'>
{childFolders.map(childFolder => (
<div
key={childFolder.id}
className='p-2'
style={{ maxWidth: '250px' }}>
<Folder folder={childFolder} />
</div>
))}
</div>
)}
</Container>
</div>
);
}
Issue
After scouring your repo looking for the usual suspect causes for "it does not navigate only the URL changes" I didn't find anything odd like multiple Router components, etc. I think the issue is your PrivateRoute component isn't passing the props to the Route correctly. You're destructuring a prop called rest and then spread that into the Route, but you don't pass a rest prop to the PrivateRoute
export default function PrivateRoute({ component: Component, rest }) { // <-- rest prop
const { currentUser } = useAuth();
return (
<Route
{...rest} // <-- nothing is spread/passed here
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
The routes, these are not passed any prop named rest:
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
What I believe to be occurring here is the exact and path props aren't passed to the underlying Route component and so the first nested component of the Switch is matched and rendered, the "/" one that doesn't have any route params.
Solution
The fix is to spread the rest of the passed props into rest instead of destructuring a named rest prop.
export default function PrivateRoute({ component: Component, ...rest }) {
const { currentUser } = useAuth();
return (
<Route
{...rest}
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
An improvement of your private route may be as follows:
export default function PrivateRoute(props) {
const { currentUser } = useAuth();
return currentUser ? (
<Route {...props} />
) : (
<Redirect to='/login' />
);
}
This checks your user authentication and renders either a Route or Redirect. This pattern allows you to use all the regular Route props so you aren't locked into using the render prop to render the component.
I am trying to hide the Menu component when I select log in my app. I am working with react hooks and I have no idea how to do it.
My main looks like this :
<div>
<Menu/>
<Router>
{
domainList === "error" ?
(
<ErrorMessage
message="Error"
/>
)
:
Boolean(domainList) === true ?
(
<Main
endpoint={endpoint}
callbackReFetchDomains={reFetchDomains}
domainList={domainList}
hasDomainListError={hasDomainListError}
appendDomainList={appendDomainList}
changeDomainList={changeDomainList}
/>
)
:
(
<LoadingSpinner/>
)
}
</Router>
</div>
My main looks like this :
<>
<div>
{/*Switch will only render the first matched <Route/> child.*/}
<Menu/>
<Switch>
<Route path="/topics">
<ExampleComponentStructure/>
</Route>
<Route path="/login">
<Login/>
</Route>
<Route path="/domains">
<DomainList
endpoint={props.endpoint}
callbackReFetchDomains={props.callbackReFetchDomains}
domainList={props.domainList}
hasDomainListError={props.hasDomainListError}
appendDomainList={props.appendDomainList}
changeDomainList={props.changeDomainList}
/>
</Route>
<Route path="/signup">
<Signup/>
</Route>
<Route path="/users">
<UserMaintainList
endpoint={props.endpoint}
/>
</Route>
<Route path="/">
<StickerList
endpoint={props.endpoint}
callbackReFetchDomains={props.callbackReFetchDomains}
domainList={props.domainList}
hasDomainListError={props.hasDomainListError}
changeDomainList={props.changeDomainList}
/>
</Route>
</Switch>
</div>
</>
I know that the code is not clean. I am just starting with react and need some help doing this login screen. Thank you.
If i understood correct, you want to hide component Menu (is it navbar?). First you can check url you are in, by creating some flag, for example:
const isLogin = props.match.path === "/login"
And then just render component if it is false
{!isLogin && <Menu/>}
here is how i done
<PublicRoute exact path="/welcome" component={WelcomePageView} />
<PublicRoute exact path="/signin" component={SignInView} />
<PublicRoute
exact
path="/forgotPassword"
component={ResetPasswordView}
/>
<PublicRoute
exact
path="/resetPassword"
component={SetNewPasswordView}
/>
<RouteWithLayout
component={UserDashboardView}
exact
layout={MainLayout}
noAccess={true}
path="/dashboard"
/>
<RouteWithLayout
component={ProfileView}
exact
layout={MainLayout}
path="/Profile"
/>
PublicRoute.jsx
import React, { useEffect } from "react"
import { Route, Redirect } from "react-router"
import { connect } from "react-redux"
import { isUserAuthenticated, getAuthErrors, getAccessToken, getUserID } from "../selectors"
import { validateToken, logout } from "../actions/auth"
import { getPersistStatus } from "../selectors/persist"
const PublicRoute = ({
component: Component,
isAuthenticated,
authErrors,
access_token,
uid,
persistStatus,
logoutUser,
...rest
}) => {
useEffect(() => {
if (persistStatus && !isAuthenticated) {
logoutUser()
}
}, [persistStatus, isAuthenticated, logoutUser])
return (
<Route {...rest} render={props => (
!isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: "/dashboard",
state: { from: props.location }
}} />
)
)} />
)
}
const mapStateToProps = (state) => ({
isAuthenticated: isUserAuthenticated(state),
authErrors: getAuthErrors(state),
access_token: getAccessToken(state),
uid: getUserID(state),
persistStatus: getPersistStatus(state)
})
const mapDispatchToProps = (dispatch) => ({
logoutUser: () => {
dispatch(logout())
}
})
export default connect(mapStateToProps, mapDispatchToProps)(PublicRoute)
RouteWithLayout.jsx
import React from "react"
import { Route } from "react-router-dom"
import PropTypes from "prop-types"
import { useAuthorization } from "./Permissions"
const RouteWithLayout = (props) => {
const { layout: Layout, component: Component, path, noAccess, ...rest } = props
const userAccess = useAuthorization(path)
return (
<Route
{...{ path, ...rest }}
render={(matchProps) => (
<Layout>
{noAccess || userAccess.includes("R") ? (
<Component {...matchProps} />
) : (
<p>You do not have sufficient permissions to view this page</p>
)}
</Layout>
)}
/>
)
}
RouteWithLayout.propTypes = {
component: PropTypes.any.isRequired,
layout: PropTypes.any.isRequired,
path: PropTypes.string,
}
export default RouteWithLayout
I want to use layouts with my react-router-dom, at this moment i am doing that like this
const DefaultLayout = ({children, ...rest}) => {
return (
<div className={styles.wrapper}>
<Header/>
{children}
<Footer/>
</div>
)
};
const DefaultRoute = ({component: Component, ...rest}) => {
return (
<Route {...rest} render={matchProps => (
<DefaultLayout>
<Component {...matchProps} />
</DefaultLayout>
)}/>
)
};
render(
<Provider store={store}>
<HashRouter>
<Switch>
<DefaultRoute exact path="/" component={AdvertList}/>
<DefaultRoute exact path="/user" component={UserOptions}/>
<Route path="/login" children={Login}/>
<Route render={
() => (
<div>
Not found
</div>
)
}/>
</Switch>
</HashRouter>
</Provider>,
document.querySelector('#app')
);
it works okay, both UserOptions and AdvertList components are rendered inside DefaultLayout, and Login component does not, but in official documentation i didn't find solution like that, instead there is "nested routing" where you adding new nested routes in subclasses, like
if you need default layout u make it on route /, then if you need advert list with that layout, in layout component you defined route /adverts and adding link to it, and so on, each sub component uses layout of parent one.
But in my case there is already product list on route /, and i need to change that content to other products list regarding link pressed, not to add to parent layout, but to change it part. Here is my code,
const { BrowserRouter, Route, Switch, Link } = window.ReactRouterDOM;
const { Component } = window.React;
const About = () => ('About');
const MiscProducts = () => ('Misc products');
class AdvertsList extends Component {
render() {
return (
<div className="container">
<header>Header</header>
<main>
<nav>
<Link to="/miscProducts">Misc Products</Link> #
<Link to="/about">About</Link>
</nav>
<div className="content">
Main Products
</div>
</main>
<footer>Footer</footer>
<Route path="/miscProducts" component={MiscProducts} />
</div>
)
};
};
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={AdvertsList} />
<Route path="/about" component={About} />
<Route path="*" render={
() => (
<div>
Not found
</div>
)
}/>
</Switch>
</BrowserRouter>
);
}
}
ReactDOM.render(<App />, document.querySelector("#app"));
http://jsfiddle.net/gmcke2a4/6/ here main products loaded by default, and when i press misc products, misc products must be loaded instead of main one.
p.s. And why about doesn't work?
Login Fix
<Route path="/login" children={Login}/> this seems wrong because children component expects function which return nodes i think.Try <Route path="/login" children={() => (</Login />)}
Layout
But in my case there is already product list on route /, and i need to
change that content to other products list regarding link pressed, not
to add to parent layout
You can create component which renders specific products like this.
const MainProducts = () => 'Main Products'
const GummyBearsProducts = () => 'GummyBears'
const Products = props => (
<div className="products-container">
<Switch>
<Route path={`${props.location.pathname}`} component={MainProducts}/>
<Route path={`${props.location.pathname}/gummy-bears`} components={GummyBearProducts}/>
</Switch>
</div>
)
And then use it as follows.
class AdvertsList extends Component {
render() {
return (
<div className="container">
<header>Header</header>
<main>
<nav>
<Link to="/products">Products</Link> #
<Link to="/about">About</Link>
</nav>
<div className="content">
<Route path="/products" component={Products} />
</div>
</main>
<footer>Footer</footer>
</div>
)
};
};
React router is great in rendering specific components.I hope it answers your question.Cheers!
If you are using react-router-dom v6. Then follow the below procedure to configure react-router-dom,
App.jsx:
import './App.css';
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './Components/Home'
import About from './Components/About'
import Layout from './Components/Layout'
function App() {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
</Route>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
After configuring the react router in App.jsx. I am creating 3 components Home, About and Layout. Home and About are regular components and Layout component is to handle the Layout part in react-router-dom using Outlet.
Layout.jsx
import { Outlet, Link } from 'react-router-dom'
export default function Layout() {
return (
<>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Outlet />
</>
)
}
Home.jsx
export default function Home() {
return (
<>
<p>This is Home</p>
</>
)
}
About.jsx
export default function About() {
return (
<>
<p>This is About Us</p>
</>
)
}