React router with browserHistory goes to server on every URL change - reactjs

I am doing something like:
<Router history={browserHistory}>{routes}</Router>
When I do above whenever URL in address bar changes call is going to server but this is not what I want, I want first time page to load from server but after that whenever route change component should load in client side only. Am I missing something here?
In client side I am doing something like :
ReactDOM.render(
<Provider store={app.store}>
<Router history={browserHistory}>{routes}</Router>
</Provider>,
document.getElementById("app")
);
and my routes look like:
const routes = (
<Route path="/" component={DJSAppContainer}>
<Route path="page" component={DJSPage}>
<Route path="/page/:pageName" component={PageContainer} />
</Route>
</Route>
);
Now whenever I do location.href = "/page/xyz" it goes to server and load the content.

You shouldn't change location.href directly. You should send the new path to React using:
ReactRouter.browserHistory.push(newPath);
If you have anchor tags, you should use the <Link> component mentioned in #ahutch's answer.

React router exposes as props the history used in the current views. See their docs for all the props that are injected here.
If you want to redirect from DJSAppContainer or any of the two children views DJSPage or PageContainer you could do it by accessing the history property:
const {history} = this.props;
history.pushState('/path/to/new/state');
If on the other hand you do some fancy stuff and want to redirect from outside the components, try this (taken from react router docs)
// somewhere like a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
This two approaches assume you are trying to redirect based on some complex logic, not as a consequence of a click event. If you just want to handle click events, try using the <Link/> component of react-router.
It seems that the history property is now deprecated, and seems to be replaced by the router context property as seen in the implementation of the link component. Basically, if you really want your code to be future proof, you should ask for the contextType:
contextTypes: {
router: object
},
And then use it from the context of the component:
this.context.router.push('/some/path')

Assuming you're using node on the back-end, make sure that you have it set up according to the react-router docs.
// send all requests to index.html so browserHistory in React Router works
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'))
})
Also, when you're linking between components and you want to ensure that you are using react-router for the routing instead of the server, make sure to use Link. Hope that answers your question.

I was having a similar issue, while hashHistory worked without issue browserHistory would always load from the server. I got things working by remembering to call preventDefault(). Here is my function:
handleSubmit: function (e) {
e.preventDefault();
var username = this.usernameRef.value;
this.usernameRef.value = '';
this.context.router.push(`/profile/${username}/`);
}
handleSubmit is called on a form onSubmit.

Related

How can I know if specific parent React component exists?

So I've got a component that is dependent on the context of BrowserRouter to hook into
<BrowserRouter>
<...>
<.../>
<.../>
<MyRedirectComponent/>
<.../>
</...>
</BrowserRouter>
I would love to simply include BrowserRouter inside my MyRedirectComponent that way I wouldn't need to wrap it all the time.
const MyRedirectComponent = () => {
const browserRouterParentExists = // I dunno
return browserRouterParentExists ? (
<NormalStuff/>
) : (
<BrowserRouter>
<NormalStuff/>
</BrowserRouter>
)
}
Is correctly populating browserRouterParentExists possible?
Writing conditional logic that tries to access parent component is an anti-pattern in React IMO and I don't think there is a public API you can use for that purpose.
I didn't get what you are trying to achieve but BrowserRouter is defined as
A <Router> that uses the HTML5 history API (pushState, replaceState
and the popstate event) to keep your UI in sync with the URL.
and most of the time you only need one BrowserRouter in your app in a top level component like App considering its usage. So wrapping a component with it and using that component throughout your app is not quite reasonable. If you are trying to redirect user to another route based on some condition, you can use any data coming from props, state, Context API or a state management lib. like Redux etc. to implement your logic and render Redirect component together with that conditional logic.

React router two URL parameters &more

I'm using react-router-dom and this is currently how it looks:
<Switch>
<Route path={"/layouts/:layoutID"} component={Layouts} />
<Route
path={"/dashboard/:dashboardID"}
component={Dashboards}
/>
</Switch>
When the user navigates to "/dashboard/:dashboardID" inside this component he can choose a sub-page onClick, and I want the URL structure will be "/dashboard/:dashboardID/:pageID" pageID will navigate to 'PageIdComponent' this component will get a 'match' props and will show pageID
Please see the attached file that shows the necessary structure.
What is the best way to implement it?
If you have the child route inside your Dashboard component, then you can use the match.path value to build up a sub route:
<Route path={`${props.match.path}/:dashboardId`}>} component={SubDashboard} />
in order to make the path match what the parent's had and then add a new variable on top of that.
Then you can navigate to it using the match.url to make a link to the current URL and add the subPage to it:
<Link to={`${props.match.url}/pageFour`}>Page four</Link>
Instead of prop driling, React Router offers ways to get the relevant props to whatever components you desire via withRouter HOC or various hooks. The hooks were introduced in version 5.1
To get the match via a hook you can use const match = useRouteMatch()
To get the relevant props via the HOC:
const Example = useRouteMatch(function Example({ match, location, history }) {
return <div>{match.url}</div>;
});
For react router v6 (that's upcoming):
https://reacttraining.com/blog/react-router-v6-pre/#nested-routes-and-layouts

Redirect to another component after form submission in reactjs

I have the following method to handle form submission-
handleSubmit(event) {
event.preventDefault();
axios.get('url').then(response => {
if(response.data.issuccess){
this.props.history.push("/SuccessComponent");
}
});
}
It changes the URL in addressbar, but UI remains the same.
How to solve this?
This method will not work perfectly.
If you're using react-router-dom package, you need to initially set the route of your successCompoment in the root file mostly app.js.
import SuccessComponent from .....link to the file...
<Route path='/SuccessComponent' component={SuccessComponent} exact/>
Applying this will work.
If you didn't define the route to a component, you cannot Make a navigate to a URL expecting to render different component not initially defined

ReactJS: How can I programmatically redirect a user to another page?

I have a basic ReactJS 4.0.1 app set up with routes using react-router-dom 5.0.1.
I also have a middleware that checks if the user's session time has expired. From there, I want to simply redirect the user to the sign in page.
I literally just want to change pages. If this was in .net, I would just call:
return RedirectToAction("Index", model);
Very simple.
Previously, to change content in different components, I had an if statement in the render section that would redirect by returning:
<Redirect to={{ pathname: '/signin' }}/>
to the user. This worked if the user took the action that triggered code within the component, but the logic I'm handling is in middleware.
I've tried
history.push, goto, location.push, store.push, etc...
I can't get any of that to work.
From my middleware, I have access to the Store from redux. What can I do with that?
You can with history object. Just export history object and call history.push('some-page'). I made a demo here https://codesandbox.io/s/serverless-fire-1vihp
export const history = createBrowserHistory()
and pass history props to Router
<Router history={history}>
in middleware file just call history.push('some-page').
Another way is use library like react-router-redux,

Where and How to request data asynchronously to be passed down as props with React Router (v 1)

After reading many questions regarding this topic I am still unsure as to which is the best way to asynchronously fetch data which later will be passed down as props to the child routes with React Router v1.0.0 and up.
My route config looks something like this:
import { render } from 'react-dom';
// more imports ...
...
render(
<Router>
<Route path="/" component={App} />
<IndexRoute component={Dashboard}/>
<Route path="userpanel" component={UserPanel}/>
</Router>,
document.getElementById('container')
)
In my App component I have code which asynchronously fetches data from the backend and will incorporate it into its state, if fetching was successful. I use componentDidMount for this within App.
The state of App will look like this contrived example:
{
user: {
name: 'Mike Smith',
email: 'mike#smith.com'
}
}
I would want to pass the user part of state as props to my IndexRoute and the userpanel route. However I am not sure how I should do this.
A few questions come to mind:
Should I place the async data request somewhere else within my code?
Should I use the React Router api (like onEnter) instead of React lifecycle methods for the data fetching?
How can I pass the state (user) of App to the Dashboard and UserPanel components as props?
I am unsure how to do this with React.cloneElement as seen in other answers.
Thanks for the help in advance.
What you are asking for is persistent data between routes and that's not the job of the router.
You should create a store (in flux terms), or a model/collection (in MVC terms) - the usual approach with react is something flux-like. I recommend redux.
In the redux docs it has an example of fetching a reddit user:
componentDidMount() {
const { dispatch, selectedReddit } = this.props
dispatch(fetchPostsIfNeeded(selectedReddit))
}
Personally I don't think flux/redux is the easiest approach to implement, but it scales well. The essential concept is even if you decide to use something else:
You are correct, as Facebook suggests, async fetching goes best in componentDidMount.
If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.
Next you need to set this data in a store/model which can be accessed from other components.
The nice thing about redux (with react-redux) is that for each component you can say "Here are the actions this component is interested in" and then that component can simply call the action like UserActions.fetchUserIfNeeded() and the action will figure out whether it already has the user or if it should be fetched, and afterwards it will re-render and the prop will be available.
Answer to Q4: What are you trying to clone and why? If it's a child see this answer.
You can do one thing when your application start at that time you will call the API and fetch the data and register your Route like
my index.js is entry file then
here I have used React-Router 0.13.3 you can change the syntax as per new Router
fetchData(config.url+'/Tasks.json?TenantId='+config.TenantId).then(function(items)
{
var TaskData=JSON.parse(JSON.stringify(items.json.Tasks));
var Data=[];
Object.keys(TaskData).map(function(task){
if(TaskData[task].PageName !=='' && TaskData[task].PageUrl !=='')
{
Data.push({PageName:TaskData[task].PageName,path:TaskData[task].PageName+'/?:RelationId',PageUrl:TaskData[task].PageUrl});
}
});
Data.push({PageName:'ContainerPage',path:'/ContainerPage/?:RelationId',PageUrl:'./pages/ContainerPage'});
var routes=require('./routes')(Data);
$("#root").empty();
Router.run(routes,function(Handler){
React.render(<Handler />,document.getElementById('root'));
});
React.render(<UserRoles />, document.getElementById("userrole"));
}).catch(function(response)
{
showError(response);
});
I have pass the data to routes.js file like var routes=require('./routes')(Data); and my routes.js file look like
export default (data =>
<Route name="App" path="/" handler={App}>
<NotFoundRoute handler={require('./pages/PageNotFound')} />
<Route handler={TaskList} data={data} >
</Route>
{ data.map(task =>
<Route name={task.PageName} path={task.path} handler={require(task.PageUrl)}>
</Route>
) }
</Route>
);
I am not entirely sure I understand the question, but I just recently passed properties to the children of my routes as well. Pardon me if this is not the best way of doing it, but you'll have to clone your children and edit them and then pass down the copies not the children. I'm not sure why react and the react router make you do this, but try this:
let children (or whatever you want to name it) = React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {name of property: property value});
});
Afterwards, you should be able to access those properties in this.props in the sub routes. Please ask if you have any questions because this is pretty confusing.

Resources