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
Related
I'm trying to understand why when refreshing the page, the component is called multiple times:
MainLayout.tsx: (routes component)
import { FC, ReactElement, useEffect } from 'react'
import { Routes, Route, BrowserRouter } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import { useLocation } from 'react-router-dom'
import { Navigate } from 'react-router-dom'
import { Outlet } from 'react-router-dom'
import { IntroductionPage } from '../pages/introduction/introduction-page'
import { useTranslation } from 'react-i18next'
import { storage } from '../utils/storage'
export const history = createBrowserHistory()
export const MainLayout: FC = () => {
const { t } = useTranslation()
useEffect(() => {
console.log('MainLayout:: constructor')
}, [])
const RequireAuth = (): ReactElement => {
const { token } = storage.getState().authReducer
let location = useLocation()
if (!token) return <Navigate to="/login" state={{ from: location }} />
return <Outlet />
}
return (
<BrowserRouter history={history}>
<div className="main-wrapper">
<div className="content-wrapper">
<div className="main-content">
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} />
<Route element={<RequireAuth />}>
<Route path="/acceptance" element={<AcceptancePage />} />
<Route path="/introduction/:page" element={<IntroductionPage />} />
</Route>
</Routes>
</div>
</div>
</div>
</BrowserRouter>
)
}
introduction page
I put the following code in introduction page:
useEffect(() => {
console.log('IntroductionPage:: constructor')
setIntroduction(introductions[+page - 1])
}, [])
I'm refreshing the introduction page, and see in the console:
IntroductionPage:: constructor
MainLayout.tsx:23 MainLayout:: constructor
IntroductionPage:: constructor
IntroductionPage:: constructor
Appreciate any help
Ah, I see why the IntroductionPage component is mounted twice. The RequireAuth component is declared inside another React component. Since it is redeclared each render cycle it's a new React component reference so React unmounts the instance from the previous render cycle and mounts a new instance. All children it renders will also be new instances.
It should be declared out on its own, outside of any other React component.
Example:
const RequireAuth = (): ReactElement => {
const { token } = storage.getState().authReducer;
const location = useLocation();
if (!token) return <Navigate to="/login" state={{ from: location }} />;
return <Outlet />;
};
export const MainLayout: FC = () => {
const { t } = useTranslation();
useEffect(() => {
console.log('MainLayout:: constructor');
}, []);
return (
<BrowserRouter history={history}>
<div className="main-wrapper">
<div className="content-wrapper">
<div className="main-content">
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} />
<Route element={<RequireAuth />}>
<Route path="/acceptance" element={<AcceptancePage />} />
<Route path="/introduction/:page" element={<IntroductionPage />} />
</Route>
</Routes>
</div>
</div>
</div>
</BrowserRouter>
);
};
I have 2 pages both are childrens from App.
_ NavbarComp.js
_ Home.js
Right now I have a functionnal SearchBar in my Home, I'd like to pass the values between NavbarComp & Home.
When I search from my NavbarComp, I'd like to have a render only on the Home page.
This is what it looks like so far.
I now need to remove my input button from Home, and pass the values between both pages Navbar & Home.
I dont think I can use props there, tho.. looking for solutions !
NavbarComp.js
import { Layout } from "antd";
import { Link, useHistory } from "react-router-dom";
import Cookies from "js-cookie";
import { useDispatch } from "react-redux";
import { logout } from "redux/auth/authActions";
import { Nav, Navbar, NavDropdown, Form, FormControl, Button, Row, Col, Container } from "react-bootstrap";
const { Header } = Layout;
export const NavbarComp = ({input, setInput}) => {
const history = useHistory();
const cookie = Cookies.get('token');
const dispatch = useDispatch();
const logMeOut = (e) => {
e.preventDefault();
dispatch(logout());
history.push('/');
}
return (
<>
<Navbar bg="light" expand="lg">
<Form className="d-flex">
<FormControl
type="search"
placeholder="Search"
className="mr-2"
aria-label="Search"
input={input}
/>
</Navar>
</>
)
}
Home.js
import React from 'react';
import { Link } from "react-router-dom";
import { useSelector } from 'react-redux';
import { v4 as uuid_v4 } from "uuid";
export const Home = () => {
const connected = useSelector(state => state.auth.user)
const [input, setInput] = React.useState('');
const [flats, setFlats] = React.useState([]);
const [flatsDefault, setFlatsDefault] = React.useState([]);
React.useEffect(() => {
getListing('real_estates')
}, [])
const getListing = async (url) => {
const config = {
method: 'GET',
};
const response = await fetch(`${process.env.REACT_APP_API_URL}/${url}`, config);
const data = await response.json();
setFlats(data)
setFlatsDefault(data)
};
const updateInput = async (input) => {
const filtered = flatsDefault.filter(flat => {
return flat.title.toLowerCase().includes(input.toLowerCase())
})
setInput(input);
setFlats(filtered);
}
return (
<>
<input
type='text'
input={input}
placeholder={"Search for properties"}
onChange={(e) => updateInput(e.target.value)}
/>
<div className="home-header">
<div className="bg-img-desc">
<h1>List of real estates</h1>
</div>
</div>
<div className="container" style={{ padding: '0 3.5rem' }}>
<ul className="row">
{flats ? (
flats.map(flat => (
<li className="col-12 col-sm-6 col-md-4 col-lg-3 mb-4" key={uuid_v4()}>
<div className="card h-100">
{ flat.images_url && <img src={`${process.env.REACT_APP_API_URL}${flat.images_url[0]}`} className="card-img-top" alt=""/> }
<div className="card-body">
<h5>{flat.title}</h5>
<p>Price : {flat.price} €</p>
<p>location : {flat.location}</p>
{connected && <Link to={`/real_estates/${flat.id}`} className="btn btn-primary">Details</Link>}
</div>
</div>
</li>
))
) : (
<h2>Loading...</h2>
)}
</ul>
</div>
</>)
}
App.js
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { Layout } from 'antd';
import { NavbarComp } from "components/NavbarComp";
import { Home } from "pages/Home";
import Realestate from "pages/Realestate";
import NewRealestate from "pages/NewRealestate";
import { Other } from "pages/Other";
import { Login } from "pages/Login";
import { Register } from "pages/Register";
import Profile from "pages/Profile";
import { useDispatch, useSelector } from 'react-redux';
import { getUser } from 'redux/auth/authActions';
import Cookies from 'js-cookie';
function App() {
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user);
React.useEffect(() => {
dispatch(getUser(Cookies.get('id')))
console.log(user)
}, [])
return (
<Layout className="layout" style={{ backgroundColor: 'transparent' }}>
<Router>
<NavbarComp />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/real_estates/:id">
<Realestate />
</Route>
<Route path="/new_real_estate">
<NewRealestate />
</Route>
<Route path="/other">
<Other />
</Route>
<Route exact path="/login" >
<Login />
</Route>
<Route exact path="/register" >
<Register />
</Route>
<Route exact path="/profile" >
<Profile user={user} />
</Route>
</Switch>
</Router>
</Layout>
);
}
export default App;
I can see three ways that you can implement to resolve this.
The first one is creating a context, so your state is going to be share through all your app. So when you change it inside your NavbarComp you will be able to get this value in another component.
The second one is similar to context but using redux. I saw that you are using redux in your project, you can share those values through redux.
The other way is to create a useState() inside your app component and pass it as props to your NavbarComp and your home. So this state will be shared between those two.
function App() {
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user);
const [value, setValue] = useState()
React.useEffect(() => {
dispatch(getUser(Cookies.get('id')))
console.log(user)
}, [])
return (
<Layout className="layout" style={{ backgroundColor: 'transparent' }}>
<Router>
<NavbarComp value={value} setValue={setValue}/>
<Switch>
<Route exact path="/">
<Home value={value} setValue={setValue}/>
</Route>
<Route exact path="/real_estates/:id">
<Realestate />
</Route>
<Route path="/new_real_estate">
<NewRealestate />
</Route>
<Route path="/other">
<Other />
</Route>
<Route exact path="/login" >
<Login />
</Route>
<Route exact path="/register" >
<Register />
</Route>
<Route exact path="/profile" >
<Profile user={user} />
</Route>
</Switch>
</Router>
</Layout>
);
}
export default App;
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'm trying to pass several props in a private route. What's the correct way to write this and what am I missing? Here is the code I have. My app works with this code, in that the user is able to login and see the dashboard. However, the props aren't passing. Is there a way to pass props to a private route?
<PrivateRoute exact path="/dashboard" component={Dashboard} render={routeProps =>
<Dashboard
handleUpdate={this.handleUpdate}
book={this.state.book}
info={this.state.info}
{...routeProps} />}
/>
Dashboard Component
class Dashboard extends Component {
state = {
book: this.props.book,
info: this.props.info,
error: '',
}
onLogoutClick = e => {
e.preventDefault();
this.props.logoutUser();
};
render() {
console.log(`BOOK STATE IN DB: ${this.state.book}`)
const { user } = this.props.auth;
return(
<div>
<h4>
<b>This is your page</b> {user.name}
</h4>
<button onClick={this.onLogoutClick}>Logout</button>
<h2>Search Book</h2>
<Search
handleUpdate={this.props.handleUpdate}
/>
<h4>Book Results</h4>
<div>{this.state.book}</div>
</div>
);
}
}
Dashboard.propTypes = {
logoutUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(
mapStateToProps,
{ logoutUser }
)(Dashboard);
Private Route
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
console.log(auth),
<Route
{...rest}
render={props =>
auth.isAuthenticated === false ? (
<Redirect to="/login" />
) : (
<Component {...props} />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
Can you show us the code of PrivateRouter component? You can just follow the such way
<PrivateRoute exact path="/dashboard" component={Dashboard} props = {{book: this.state.book etc}}/>
And receive this props on PrivateRoute components to put it into child component
Can you try removing the component={Dashboard} prop, and only use the render prop to render the Dashboard. Your code should look like this
<PrivateRoute exact path="/dashboard" render={routeProps =>
<Dashboard
handleUpdate={this.handleUpdate}
book={this.state.book}
info={this.state.info}
{...routeProps} />}
/>
From the docs
Warning: <Route component> takes precedence over <Route render> so don’t use both in the same .
So, remove the component={Dashboard}
After the comments and the PrivateRoute code, i suggest you rewrite your PrivateRoute to
const PrivateRoute = ({ auth, ...rest }) => {
if (!auth.isAuthenticated) {
return <Redirect to="/login" />;
}
return <Route {...rest} />
);
and remove the component={Dashboard} part.
const PrivateRoute = ({component: Component, auth, book, handleUpdate, ...rest }) => (
console.log(rest),
console.log(book),
<Route
{...rest}
render={props =>
auth.isAuthenticated === false ? (
<Redirect to="/login" />
) : (
<Component book={book} handleUpdate={handleUpdate} {...props} />
)
}
/>
)
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");}