I'm trying to separate my links by using one link with parameter for multiple links.
For example:
instead of.
User/login
User/Profile
User/home
I'm trying to use.
User/:id
When someone click on the User/login link the login component will be selected.
If someone click on the User/Profile link the Profile component will be selected.
I got it to work, but react router seem to be firing twice for each click.
Router
const AppRouter = (props) => (
<BrowserRouter >
<div className={`site-grid-container ${props.siteBgBodyColor}`}>
<div className='site-grid-nav-body'>
<NavigationBar />
<Switch>
<Route path="/" component={Home} exact={true} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route path="/user/:id" component={User} />
<Route component={PageNotFound} />
</Switch>
</div>
</div>
</BrowserRouter>
);
export default AppRouter;
Select User Component Function
const UserViews= ({ match }) => {
console.log('------- called twice -----')
const id = match.params.id;
const SelectedView = getSelectedView(id);
return (
<SelectedView />
);
}
export default UserViews;
Get Selected View
const getSelectedView= (id) => {
switch (id) {
case "login":
return Login;
case "profile":
return Profile;
case "home":
return Home;
default:
return PageNotFound;
}
}
The code above works, but react router seem to be firing twice for each click.
Is there a correct way to do what i'm trying to do?
Thanks,
Related
Let's say we have a component Home.jsx
const Home = () => {
return (
<div>
<h1>Home</h1>
</div>
);
};
export default Home;
and our router goes as
const Router = () => (
<>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</>
);
That's works fine! But what if we want to import the element as below in the Route component
views.js
export { default as Home } from "./home";
and modify our routes as follows
import * as views from "../views";
<Routes>
<Route path="/" element={views.Home} />
</Routes>
this piece of code makes following error:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
What's the way out?
Well, I come up with a workaround, and that is use variable as method.
Here it is:
<Route path="/" element={views.Home()} />
I have a React project that has a HeaderComponent that exists for all routes in project like this:
function App() {
return (
<Fragment>
<Router>
<HeaderComponent />
<Routes>
<Route path="/login" element={<Login />}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/" element={<LandingPage />}></Route>
</Routes>
<FooterComponent />
</Router>
</Fragment>
);
}
And my problem is that the <HeaderComponent> is rendered when the website first loads but when the user logs in, the <HeaderComponent> is not aware of the changes because the component has already mounted.
So in my <HeaderComponent>, the componentDidMount function looks like this:
componentDidMount() {
AuthService.authorizeUser()
.then((r) => {
this.setState({ loggedIn: true });
})
.catch((error) => {
this.setState({ loggedIn: false });
});
}
This only works if I refresh the page.
Basically, if a user successfully logs in (from the <Login> component), what is the proper way of making my HeaderComponent aware of this?
You can use Context API to make AuthContext to share global state within your app:
// AuthContext.js
export const AuthContext = React.createContext({});
export const AuthProvider = ({
children,
}) => {
// your context logic
return (
<AuthContext.Provider value={yourAuthValue}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => React.useContext(AuthContext);
// Layout.js
import { Outlet } from 'react-router-dom'
// Using `Outlet` to render the view within layout
export const Layout = () => {
return (
<>
<HeaderComponent />
<Outlet />
<FooterComponent />
</>
)
}
// HeaderComponent.js
import { useAuth } from './AuthContext'
export const HeaderComponent = () => {
// get state from auth context
const { isLoggedIn } = useAuth()
return // rest of your code
}
// App.js
function App() {
return (
<Fragment>
<-- Wrap your app with AuthContext let other components within your app can access auth state !-->
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<LandingPage />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Route>
</Routes>
</BrowserRouter>
</AuthProvider>
</Fragment>
);
}
There are a couple of ways to do so.
When you're facing a situation where you need to share the same state between multiple components, lifting the state up should be the first thing to try Check this codesandbox.
And some great blogposts to read, KCD - Prop Drilling, KCD - State Management with React
Such approach may cause "prop drilling" when you need the same state in deeply nested components and that's where the context API comes in handy.
codesandbox
Please see sandbox.
I'm having this code:
<Router history={createBrowserHistory()}>
<Switch>
<Route path="/items/:itemId">
<Item />
</Route>
<Route exact path="/items">
<Items />
</Route>
<Redirect to="/items" />
</Switch>
</Router>
Expecting /items to be default route. Items is loading links for available items:
const { url } = useRouteMatch();
return (
<div>
<h1>Items</h1>
{items.map((item) => (
<Link to={`${url}/${item}`}>{item}</Link>
))}
</div>
);
Once an item link is clicked, I'm expecting the url to have the item in it, and to render Item.
url is /items, so everything should work out for Switch to render:
<Route path="/items/:itemId">
<Item />
</Route>
However, it's staying at /items, and not even rendering Items component anymore. No error is shown. Any idea why this is happening?
For router you are missing an exact props which is really important for your case
<Route exact path="/items/:itemId" />
and inside your Item component you misspelled the param name, because in router it's itemId where the component has a different one item, so fixing it will make the param accessible(they should be exact match)
export const Item = () => {
const { itemId } = useParams();
return (
<div>
<h1>Item</h1>
{itemId}
</div>
);
};
That because when it reaches the /items/:itemId Page the component get redirect back to /items , So the solution to this is just remove the redirect component
<Switch>
<Route path="/items/:itemId">
<Item />
</Route>
<Route exact path="/items">
<Items />
</Route>
</Switch>
Also there few other mistake which i saw in your code.In Item.jsx , your using
const { item } = useParams();
This isn't going to the render the anything since it is undefined , that is because your are passing the /:itemId in Route. So try this instead
const { itemId:item } = useParams();
here we are renaming the itemId to item inorder to accessable down in the code.
I'm trying to create a protected/private route with react-router-dom v5, as in the link here, I use only the code I needed - Protected routes and authentication with React Router v5.
Now I need both components private from each other
The problem: after Getting Home component transferred fromSignUp components I can go back to SignUp and thats not what I want.
Signup corrently is the first page (as sign-in). I don't want the user to go back to sigh-up components if he already signed up and present at the Home component.
The working website project is here - CodeSandbox
Anyone has idea how can I make both component protected/private?
GuardedRoute.jsx
function GuardedRoute({ children, auth, ...rest }) {
return (
<Route
{...rest}
render={() => {
return auth === true ? children : <Redirect to="/signup" />;
}}
/>
);
}
export default GuardedRoute;
App.js
const App = () => {
const [userID, setUserID] = useState('');
const [userSignedUp, setIfSignUp] = useState(false);
return (
<div className="App-div">
<GuardedRoute exact path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Switch>
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
</Switch>
</div>
);
};
export default App;
Please try any of your solutions at my codesandbox before posting your answer so we will not try solutions in theory only and in vain :)
You could make the signup route only exist if the user is not logged in, and then use a catch-all that will redirect to /home
<div className="App-div">
<Switch>
{!userSignedUp && (
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
)}
<GuardedRoute path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Route path="/">
<Redirect to="/home" />
</Route>
</Switch>
</div>
Updated sample: https://codesandbox.io/s/jolly-https-t7dmj?file=/src/containers/App.js
or you could encapsulate the logic to another component like GuardedRoute, let say UnguardedRoute that will redirect somewhere if user is logged in.
I'm practicing nested routing using react-router-dom.
In Room001Container, there is a tab navigation bar that will be displayed on all components, and there is a button that navigates to a detailed post page when the button is pressed.
When I enter the room, I should see the post page first by default.
However, if I output postId as useParams in Room001Container component, undefined is returned.
In the PostDetail component, params are printed well, but why is it not output in the Room001Container? I know that the container is unconditionally rendered even if I move to each component, but it's really strange. Is there any way to know the params from the top container?
function Container() {
return (
<Switch>
<Route
path="/channel/:channelId/lobby/:lobbyId"
component={LobbyContainer}
/>
</Switch>
);
}
function LobbyContainer() {
return (
<Switch>
<Route
exact
path="/channel/:channelId/lobby/001"
component={Room001Container}
/>
<Route
exact
path="/channel/:channelId/lobby/002"
component={Room002Container}
/>
<Route
exact
path="/channel/:channelId/lobby/002"
component={Room003Container}
/>
</Switch>
);
}
function Room001Container() {
const history = useHistory();
const { pathname } = history.location;
const { channelId, postId } = useParams();
console.log(postId) // undefined
return (
<>
<S_TabBar>
<Link to="/channel/:channelId/lobby/001/post">
<S_TabCell>post</S_TabCell>
</Link>
<Link to="/channel/:channelId/lobby/001/chat">
<S_TabCell>chat</S_TabCell>
</Link>
</S_TabBar>
<S_Button onClick={() => history.push('/channel/:channelId/lobby/001/post/1')}>
go to detail
</S_Button>
<Switch>
<Route
exact
path="/channel/:channelId/lobby/001/post"
component={Post}
/>
<Route
exact
path="/channel/:channelId/lobby/001/post/:postId"
component={PostDetail}
/>
<Route
exact
path="/channel/:channelId/lobby/001/chat"
component={Chat}
/>
<Redirect
exact
from="/channel/:channelId/lobby/001"
to="/channel/:channelId/lobby/001/post"
/>
</Switch>
</>
);
}
There is no postId parameter in the route that mounts Room001Container.
You'll want e. g.
<Route
exact
path="/channel/:channelId/lobby/:postId"
component={Room001Container}
/>