Update route using an uncontrolled component - reactjs

I have an uncontrolled link tag <a class="link" href="#">Click Me</a> I wish to use to invoke a route change in react.
// Slowly shifting from JQuery
$(".link").on("click", (evt) => {
evt.preventDefault();
var browserHistory = createBrowserHistory();
browserHistory.push('/detail/' + evt.target.dataset.id);
// expect react-router to update view based on route change
// ???
});
I would like to eliminate a full page reload if at all possible. Therefore, calling location.href="/detail/" or anything of similar nature I would like to avoid.
Does anyone know how do I accomplish this?

I found the solution (for me anyway). This turned out to be an issue with the way I was importing the history module into my app.
For reasons I'm not fully able to explain, creating a single history module to be referenced by the other modules in my application resolved this problem.
// history.ts
import createHistory from 'history/createBrowserHistory';
export default createHistory();
Apparently, when importing the history object across other modules, it doesn't reference the same object across these modules; or I was referencing it incorrectly. Each module was referencing a separate history object. This prevented the history.listen(...) (in one module) from detecting the history.push(...) in another module.
// app.tsx
/* FAILS: Creates multiple history objects across modules */
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
/* SUCCEEDS: This creates a single history object shared across modules */
import history from './history'; // This works!
...
ReactDOM.render(
<Router history={history}>
<App.Frame>
...
</App.Frame>
</Router>
, document.getElementById('root')
);
// config.ts
/* FAILS: Creates multiple history objects across modules */
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
/* SUCCEEDS: This creates a single history object shared across modules */
import history from './history'; // This works!
...
$('.link').each((i, e) => {
$(e).on("click", (evt) => {
evt.preventDefault();
history.push('/detail/' + evt.target.dataset.id);
});
})
Credit to the following post that led me to the solution: Routing in React, the uncomplicated way

Related

Alternative of the old CreateBrowserHistory() function in React Router v6

I want to push a new route in history of my app, and as you know, the UseHistory() hook and CreateBrowserHistory() function (to create a user-defined history) have been removed from new version of 6. I know that I can use UseNavigate hook and do this work with Navigate() function, but this hook cannot be called at the top level and must be called in a React function component. Codes below show how we could do this with React Router version 5.1:
Passing the history to the parent component and exporting the same history object for using it anywhere in Application:
import React from 'react'
import { Router } from 'react-router-dom'
import { createBrowserHistory } from 'history' // which comes along with React-Router
export const history = createBrowserHistory();
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<Router history={history}>
<App />
</Router>
)
Using the exported history object in another place of Application at the top level:
switch (status) {
case 404:
history.push('/not-found');
break
if I want to do this at the top level in the new version of 6, What do you recommend?

Proper way of accessing the `history` object from redux thunks?

I have some redux thunks that need to call history.push().
It first I was trying to avoid having to pass the history object in the thunk call, so I did the following change in my code:
From this:
index.tsx
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>
);
To this:
index.tsx
import { Router } from "react-router-dom";
import { history } from "./history";
ReactDOM.render(
<Router history={history}>
<App/>
</Router>
);
history.ts
import { createBrowserHistory } from "history";
export const history = createBrowserHistory();
But now that I've come to the SSR phase of my project, this has become a nightmare. Because I cannot call createBrowserHistory() on my server code (NodeJS).
QUESTION
The pattern above is very comfortable to use both with redux thunks and some other helper functions that need to call history.push(). I simpy have to import { history } from "#src/history"; from whatever file that needs it and use it directly.
But since this is conflicting with my SSR code, I'd like to know what is the best practice / recommended approach to this situation. Should I get the history object from the components by using const history = useHistory() and pass it to the thunks / functions that need it? Or is there a better approach?

react redux: private route not rendering layout

Code Sandbox link:
and trying to follow this article
On successful login(/auth/login), the user should be routed to the dashboard(/admin/summary). If the login is successful, I am also storing an access token.
I have a PrivateRoute component for this. The problem is that on successful login, the URL is getting updated but the component is not getting rendered.
PS: about the dashboard, this is a single page application so, the dashboard has topbar, sidebar, and the right content and altogether these things are coupled inside <AdminLayout/>. So, in my AppRouter, I have to render the <AdminLayout/> and just any one component.
All the react and redux code is included in the code sandbox.
Since in your code you create your own history object (it happens in you history.js file, when you call createBrowserHistory()) but doesn't pass it to your Router, nothing happens.
There are 2 possible solutions:
1. Don't create a history object yourself, but use useHistory hook inside your component
Working Demo
With this approach, you should remove history.push from login.actions.js (which imports history) and use history.push in Login.js (which uses useHistory hook):
// login.actions.js
...
loginService.login(userid, password, rememberPassword).then(
(userid) => {
dispatch(success(userid, password, rememberPassword));
// history.push(from); <-- commented out!
},
(error) => { ... }
);
};
...
// Login.js
function handleSubmit(e) {
...
const { from } = {
from: { pathname: "/admin/summary" }
};
history.push(from) // <-- added!
dispatch(loginActions.login(inputs, from));
...
}
useHistory exposes the history object of BrowserRouter (I think this is implied in this official blog post).
2. Create a history object yourself, but pass it to a Router component
Working Demo
This approach would require you to make several changes:
Creating the history object on your own means you become responsible to provide it to a router component, but it can't be a BrowserRouter, but the base Router component (see these Github answers: 1, 2).
Once you import Router (instead of BrowserRouter), you need to get rid of any useLocation and useHistory imports, otherwise you'll get errors.
I also had to unify the history object export and imports, so that it is exported as the default export (i.e., export default history), and it is imported as the default import (i.e., import history from "./history"; instead of import { history } from "./history")
(P.S: this approach can be seen implemented elsewhere on SO, for example here or here (the latter explicitly installs history, but it's not needed in your case).

How to access react-router-v4 history prop from MobX store

Is there any way to create an action in your MobX store, which pushes your app to a new url using react router v4, e.g. this.props.history.push...
I constantly get a history undefined error, but am unsure how to access the history from my store.
The history push called from the component itself does work though..
many thanks! (this is driving me crazy..)
Since I stumbled across the same issue, I'll share my solution. I just put the RouterStore into its own file in my stores directory, then if I needed access to history or location or whatever, I would import the routing store into the store I was currently working in.
./stores/routing.ts
import { RouterStore } from 'mobx-react-router'
export default new RouterStore()
./stores/other-store.ts
import routing from './routing'
export class OtherStore {
#action
doSomething = () => {
routing.push('/new-route')
}
}
export default new OtherStore()
./index.ts
import { Router } from 'react-router-dom'
import { Provider } from 'mobx-react'
import createBrowserHistory from 'history/createBrowserHistory'
import { syncHistoryWithStore } from 'mobx-react-router'
import otherStore from './stores/other-store'
import routing from './stores/routing'
const browserHistory = createBrowserHistory()
const stores = {
otherStore,
routing,
}
const history = syncHistoryWithStore(browserHistory, routing)
ReactDOM.render(
<Provider {...stores}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('root'),
)
You can use mobx-react-router to put react-router in a mobx store and then use it by injecting it in components.
You can also pass the router store as a constructor argument to your other stores that need it. This way you have the router history instance available in your mobx store.
I would like to add a simpler solution that does not require any additional libraries. React Router version is 5.2
Among my stores i've created a HistoryStore.js with the following code:
import { createBrowserHistory } from 'history';
export class HistoryStore {
history = createBrowserHistory();
}
Then I create an instance of it in my contexts.js file but you could do it right away.
export const history = new HistoryStore();
After that you import it in your index.js and pass it as a history prop to the Router.
That's it. Now you could import this store into any other and use it there. When you use useHistory hook in your component it gets this history object, so your history in synchronized.

hashHistory vs context.router

What is the difference between hashHistory and context.router? Both of them route url and seem to work the same.
hashHistory is an instance of a history object created by the history module. It works by modifying the hash of a URL.
In React Router, a <Router> must be passed a history object. You can create and configure your own object, but for convenience, they also create hashHistory and browserHistory objects for you. Those objects can be imported and used anywhere throughout your project, including within your components. The downside to using them is that you cannot configure them yourself. If you need to use any of the history configuration options, then you will need to create your own history object.
import { hashHistory, Router } from 'react-router'
render((
<Router history={hashHistory}>...</Router>
), holder)
// or
import { Router } from 'react-router'
import { createHashHistory } from 'history'
const history = createHashHistory({ /* configuration options */ })
render((
<Router history={history}>...</Router>
), holder)
Within the components rendered by the <Router>, you can access the context.router object. That includes a number of methods from your history object. Caling those methods is the same as importing hashHistory within that file and calling whatever navigation function that you need.
const MyComponent = (props, context) => (
<div onClick={() => { context.router.push('/other-page') }}>Click Me!</div>
)
const MyComponent = (props) => (
<div onClick={() => { hashHistory.push('/other-page') }}>Click Me!</div>
)
The downside of this is that your components are less portable. Whereas using context.router will use whatever history object you passed to the <Router>, you would need to modify the component if you decided to switch from hashHistory to browserHistory.

Resources