I'm trying to port my project from using v3 of react-router to v4 of what is now called react-router-dom. Now the problems arise when I have a MenuBar component, which is completely separate from the routing logic (as you'd expect), because it will show the exact same links whatever the current path may be. Now that worked all nicely with v3, but now when I'm using NavLink, which has the same activeClassName property, the active route does not update on the NavBar, only on refresh. That seems a bit dumb, so there must be a way around this.
export default #inject('ui') #observer class App extends Component {
render() {
return (
<Router>
<div className={ styles.wrapper }>
<Sidebar />
<main className={ `${styles.main} ${!this.props.ui.menuOpen && styles.closed}` }>
<Route exact path="/" component={ HomePage } />
<Route path="/signup" component={ SignUpPage } />
<Route path="/login" component={ LoginPage } />
<Route path="/about" component={ AboutPage } />
</main>
<footer className="site-footer"></footer>
</div>
</Router>
);
}
}
The above is my main App logic and as you can see the routes are nested, but the Router itself wraps around the whole component.
What should I add to make them work again? (They do work properly on page refresh)
Based on your usage of the #observer decorator, it appears that you are using mobx-react. The thing to understand about observer is that it implements shouldComponentUpdate to optimize rendering performance. That sCU call will look at the current and next props, and if there is no difference, it will not re-render. This is a problem because by default, React Router uses the context to pass data and relies on elements re-rendering to get the updated values, but observer's sCU has no way to detect context changes.
The way that you can get around this is by passing the location object as a prop to the component that is wrapped by observer. Then, when the location changes, observer's shouldComponentUpdate will detect the difference and re-render.
You can see the blocked updates guide for more information.
Related
This question already has answers here:
is there a way to set a default route with React-Router v6
(5 answers)
Closed 12 months ago.
I have tried to look through some docs for this one but can't seem to find what I'm looking for. I have some routes below.
I want to do the following: I want to be able to click on to the dotfile route but have the aliase route open as default.
The app is similar to this:
link - link -> page
link - link -> page
link - link
If that helps....I want the first page to be open when the 2nd link is clicked in the 2nd row. I am not sure if I am explaining it correctly.
My current routes are below these work as intended, I am just struggling to figure out how to open a child route initially
<Routes>
<Route path="/" element={<App />} />
<Route path="dotfiles" element={<Dotfiles />}>
<Route path="aliases" element={<Aliases />} /> // open when dotfiles is clicked
<Route path="variables" element={<Variables />} />
...
I believe you should be able to use history.push(url).
Sol 1:
This is a react package you should be able to use to navigate between web pages. I would do it in the componentDidMount of Dotfiles.js. This would make it so that you navigate to aliases.js when you load into dotfiles.js.
Use this to install history: npm i history
https://www.npmjs.com/package/history
Sol 2: (I am not sure if this will work for sure in your case)
you can import Aliases in to dotfiles and render Aliases in dotfiles. This is the better solution in my opinion but I'm not sure if it applies in your case.
I am trying to figure out how to configure my React router to load two overlaying components. I have a route /user/testUser/item/1. If item:id is in the url, an overlay component opens. However, I want the underlying component /user/testUser/ to load as well. When I close my overlay, that is, when I switch from /user/testUser/item/1 to /user/testUser/, the underlying user component is loaded initially. If the /user/testUser/item/1 overlay is open, the underlying user component is not loaded.
<Switch>
<PrivateRoute
exact
path="/user/:id*"
component={ProfileView}
/>
<PrivateRoute
exact
path="*/item/:id"
component={ShowItem}
/>
</Switch>
You can not render two routes at a time. You can manage it via optional params and render that component on a particular route.
Example :
<Switch>
<PrivateRoute
exact
path="/user/testUser/item/:id?"
component={ProfileView}
/>
</Switch>
const ProfileView = () => {
const { id } = useParams();
return (
<>
{id && <ShowItem />}
...
...
<OtherComponent />
</>
)
};
You can check conditionally id and based on that render the other components you want.
You can't really do it using routes, Switch selects only one Route's component to be rendered. You could probably experiment with having two Switch'es in parallel, but it sounds like the easiest way would be to handle displaying the overlay in the underlaying component.
I have a Navbar that has buttons that change the language of the website. Instead of using different htmls, I'm just replacing the text with what I have in a database. However, doing anything in a component only updates that component and I need a way to update every other one
I haven't tried anything useful yet, since there's a complicated layout for usual solutions.
App.js
function App() {
return (
<div className="App">
<Router>
<div className="container" id="main">
<Navbar />
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/gallery" component={Gallery}></Route>
</Switch>
</div>
</Router>
</div>
);
}
export default App;
My Navbar is outside of Switch that contains all the different pages I need to update.
All I need to do is to somehow pass an update request to every other component. Would be nice if it'd happen without reloading the page, though that would somewhat work.
You don't mention using Redux, so I assuming you are just using React state. Keep the language in the state of the top-level component, and pass the language to the components that need it. Give Switch a call-back function that changes the top-level state when the language is changed.
I have the following code:
<View>
<Header />
<List />
</View>
If the user clicks on an edit button in a list item, react-router changes the page from /list to /list/{itemId}. This is a new page. After the user filled in some information and clicks on save, react-router changes back to /list. Now the full /list-page is rerendered!
Is there a way to cache a page, so react can detect the changes and rerender only them instead of the full page again?
It should also be possible to render the page directly (not over /list -> /list/{itemId}) so no modal solution I think.
I'm using react-router-redux v5.0.0-alpha.9 so the solution should be compatible with it. It should also be compatible with react-native and react-dom.
If you are working with react-router
Component can not be cached while going forward or back which lead to losing data and interaction while using Route
Component would be unmounted when Route was unmatched
After reading source code of Route we found that using children prop as a function could help to control rendering behavior.
Hiding instead of Removing would fix this issue.
I am already fixed it with my tools react-router-cache-route
Usage
Replace <Route> with <CacheRoute>
Replace <Switch> with <CacheSwitch>
If you want real <KeepAlive /> for React
I have my implementation react-activation
Online Demo
Usage
import KeepAlive, { AliveScope } from 'react-activation'
function App() {
const [show, setShow] = useState(true)
return (
<AliveScope>
<button onClick={() => setShow(show => !show)}>Toggle</button>
{show && (
<KeepAlive>
<Test />
</KeepAlive>
)}
</AliveScope>
)
}
The implementation principle is easy to say.
Because React will unload components that are in the intrinsic component hierarchy, we need to extract the components in <KeepAlive>, that is, their children props, and render them into a component that will not be unloaded.
Got into same problem recently and my initial nudge was to use something like react-router-cache-route or another package.
But main disadvantage of third-party packages is that they are usually slow to support new react-router versions and source code seemed to me overly complex for this problem.
So I researched further and stumbled on this suggestion from Dan Abramov and ended up with solution like this:
function App() {
const itemId = useParams<{ itemId: string }>();
return (
<Switch>
...
<Route
exact
path={['some-list-route', 'some-list-route/:itemId']}
>
<List style={itemId ? { display: 'none' } : undefined} />
{itemId && <ListItem />}
</Route>
...
</Switch>
)
}
We keep both list and item routes under single Route component to have control over their mounting behaviour. When we get item route — we hide our <List /> (but it stays mounted, "kept-alive") and conditionally mount <ListItem />.
We can also pass itemId to <List /> as a prop, if we need more control, like conditionally trigger data fetching only for list route.
When using React Router, how can I change the state of another component when the page is being routed?
For example, I have an app structured like this:
Header
==Link1 Link2==
Footer: "You are now at Home", button1, button2
When the user is routed to Link1, I would like to update the state of the footer so that it knows that it is currently on link1. For example, the footer is a toolbar, and some buttons will be disabled on Link1, but all buttons will be available on both Link2 and Home. Like this:
==Header==
==Link1 Link2==
==Content of Link1==
Footer: "You are now at Link1", button2
The solution I can think of is to wrap both the footer and content of the links into a big, single component, and set up routes to that whole component. But I'm not sure if this is a good practice, since I guess it kind of defeats the purpose of a "footer".
Another thought is that maybe I can change the state of the footer component when routing is in action, so the footer will re-render when it's necessary. The <Footer /> component thus sits outside of the main contents, on the same level like the <Header />. However, I have no idea how to do this.
Is there a better way to achieve this?
Here is the code snippet on CodeSandbox, I hope it could illustrate my problem better.
CodeSandbox demo
One way to go would be to use the withRouter HOC on the Footer so that it receives the props when the route is changed.
Changes to code
import { BrowserRouter, Route, Link, withRouter } from "react-router-dom";
then
<BrowserRouter>
<div>
<Route path="/" exact component={Home} />
<Route path="/section1" component={SectionOne} />
<WrappedFooter />
</div>
</BrowserRouter>
and then
const WrappedFooter = withRouter(Footer);
You can now use the this.props.location.pathname inside the footer to access the currently matched route from the router. You should use the actual path in your switch
let text,
{location} = this.props
switch (location.pathname) {
case '/': {
text = 'Home'
break
}
case '/section1': {
text = 'Section One'
break
}
}
https://codesandbox.io/s/8y051k9qoj