I have been working on a complex application using REACT.js for the past few months and have run into a problem that I have been unable to solve despite extensive research and experimentation.
The issue is that I am trying to implement dynamic routing in my application, where the routes are determined by data that is fetched from an API. I have been using the react-router-dom library to handle my routing, and have successfully implemented static routes. However, when I try to use dynamic routes based on the data fetched from the API, the application throws an error.
I have tried several different approaches to resolving this issue, including using the useEffect hook to fetch the data and update the routes, and also using a library such as react-dynamic-route to handle the dynamic routing. However, I have not been able to get any of these approaches to work.
Here is an example of the code I am currently using to fetch the data and update the routes:
import { useEffect, useState } from 'react';
import { fetchData } from './api';
import {
BrowserRouter as Router,
Route,
Switch,
useLocation
} from 'react-router-dom';
const App = () => {
const [data, setData] = useState([]);
const location = useLocation();
useEffect(() => {
const fetchAPI = async () => {
setData(await fetchData());
};
fetchAPI();
}, [location]);
return (
<Router>
<Switch>
{data.map((item) => (
<Route key={item.id} path={`/${item.id}`} component={DetailPage} />
))}
<Route component={NotFound} />
</Switch>
</Router>
);
};
export default App;
I would greatly appreciate any insight or advice on how to properly implement dynamic routing in a REACT.js application using data fetched from an API. Any help would be greatly appreciated. Thank you.
There are several things in your code that I believe are incorrect. Firstly Switch is outdated, if you are using an older version of react-router-dom you may be able yo use it, instead use Routes. The component attribute was deprecated before v5.1 I believe and cannot be used in newer versions of react, instead use the element attribute. Also it is important to note that this attribute need to have the name in tags.
<Route
key={item.id}
path={`/${item.id}`}
element={<DetailPage />}
/>
Also useLocation may only be used in the context of a router, meaning that it can be used in one of the element that you are routing to but unless you have a route for the App component it's not going to work.
I've made a small example to show what your code could look like to make it work.
import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
const App = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch('http://localhost:3000/testItems')
.then((res) => res.json())
.then((res) => setData(res));
}, []);
return (
<Router>
<Routes>
{data.map((item: { id: number; idk: string }) => (
<Route
key={item.id}
path={`/${item.id}`}
element={<Comp />}
/>
))}
<Route element={<p>This seems wrong</p>} />
</Routes>
</Router>
);
};
export default App;
function Comp() {
return <div>Hello</div>;
}
I've tested the code with a local server for the fetch. The server has the following data.
{
"testItems": [
{
"id": 1,
"idk": "other stuff"
},
{
"id": 2,
"idk": "not good at naming stuff"
}
]
}
<Route path="*">
<Redirect to="/" />
</Route>
The code above is what I am using to handle all not found routes. I have all routes in switch and for the most part this is working fine <Switch>. I was wondering how I would go about sending a windows alert saying something along the lines of "this page doesn't exist" when the unknown route is called. I tried using render and calling a function, but that didn't seem to work. Any thoughts?
edit: to make it clear, I am using React framework, javascript, react-router.
Your current implementation simply redirects back to (/) when the route is not found.
However, to display a window.alert() you should consider making a new NotFound component and handle the display of the alert on that page.
Afterward, use the useHistory hook to redirect back to '/'
See example code below
NotFound Component
import { useEffect } from "react";
import { useHistory } from "react-router-dom";
const NotFound = () => {
let history = useHistory();
useEffect(() => {
window.alert('Page Not found');
history.push('/');
}, []);
return (
<div>404</div>
)
}
export default NotFound;
Existing Routes
<Route path="*">
<NotFound />
</Route>
I can see in this file (https://github.com/ReactTraining/react-router/blob/v0.13.3/modules/createRouter.js) that there is a refresh function but I have no idea how to call it. I'm fairly new to react-router, I've only used it to move between some pages a couple times using hashHistory.
Right now I am trying to use it so that when an install fails, the user is given the option to 'retry' which I plan to execute by refreshing the page where the install happens (the page the user would be currently on). Any help would be appreciated.
This is a node app that runs on electron, not a web app.
firstly, add react-router as a dependency
yarn add react-router or npm install react-router
Then (for react-router v5)
import { useHistory } from 'react-router'
const history = useHistory()
// then add this to the function that is called for re-rendering
history.go(0)
This causes your page to re-render automatically
For react-router v6 use the useNavigate hook instead:
import { useNavigate } from 'react-router'
const navigate = useNavigate()
// refresh
navigate(0)
If you're using react-router v6
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
const refreshPage = () => {
navigate(0);
}
You can use this to refresh Current route:
import createHistory from 'history/createBrowserHistory'
const history = createHistory();
history.go(0)
You don't really need react-router for this. You can just use location.reload:
location.reload();
Also that version of react-router you linked to is very old, I think it's linking to v1 when it's currently on v4.
I guess that you're using react-router.
I'll copy my answer from another post.
So you have few possibilities to do that, currently my favorite way to do that is using anonymous function in component prop:
<Switch>
<Route exact path="/" component={()=><HomeContainer/>} />
<Route exact path="/file/:itemPath/:refHash" component={()=><File/>} />
<Route exact path="/:folderName" component ={()=><Folder/>}/>
</Switch>
Or if you want to refresh with current url params, you'll need extra route (reload), and play a little with router stack:
reload = ()=>{
const current = props.location.pathname;
this.props.history.replace(`/reload`);
setTimeout(() => {
this.props.history.replace(current);
});
}
<Switch>
<Route path="/reload" component={null} key="reload" />
<Route exact path="/" component={HomeContainer} />
<Route exact path="/file/:itemPath/:refHash" component={File} />
<Route exact path="/:folderName" component ={Folder}/>
</Switch>
<div onClick={this.reload}>Reload</div>
React
window.location.reload();
working
if you want to re-fetch the data just do the below:
import { useLocation } from 'react-router'
const location = useLocation()
useEffect(() => {
fetchData()
}, [location.key])
I know that this is old, but I found a simple solution according to the documentation of react-router.
Just put that attribute on your Router, and whenever you are on a new Path it will force the page to reload itself.
<Router forceRefresh={true}>
Source:
https://reactrouter.com/web/api/BrowserRouter/forcerefresh-bool
This solution won't cause the undesired full page reload but requires you to make this modification to each page that needs refreshing:
export const Page = () => {
const location = useLocation();
return <PageImpl key={location.key} />
}
So the idea is: create a wrapper around your page and make React re-create the actual page every time the location key changes.
Now it's enough to call history.push(/this-page-route) again and the page refreshes.
If you want to use <Link/> to reload some route, or simply have single history push, you can setup <Redirect/> route under <Switch/> like this:
<Switch>
<Route exact path="/some-route" component={SomeRoute} />
<Redirect exact from="/some-route/reload" to="/some-route" />
</Switch>
And then <Link to="/some-route/reload" /> or push("/some-route/reload")
If you don't want to reload all scripts again you can replace the current path with a fake/empty path and replace it again with the current path like this
// ...
let currentPath = window.location.pathname;
history.replace('/your-empty-route');
setTimeout(() => {
history.replace(currentPath)
}, 0)
// ...
Update:
If the changing of the address bar bothering, you can add a patterned route like this:
<Route path="/*/reload" component={null}/>
and add /replace to the end of currentPath to replace the router with null component. like this:
// ...
let currentPath = window.location.pathname;
history.replace(`${currentPath}/replace`);
setTimeout(() => {
history.replace(currentPath)
}, 0)
// ...
In this way, the reload keyword will add to the end of your current path and I think it's more user friendly.
Notice: If you already have a route that ends with replace It will cause conflict. To solve that you should change the path of the patterned route to something else.
You could try this workaround:
// I just wanted to reload a /messages page
history.pushState(null, '/');
history.pushState(null, '/messages');
You can use this function.
function reloadPage(){
window.location.reload();
}
<input type="button" onClick={ reloadPage } value="reload"/>
May be you are trying to push in history object, then bind your component with withrouter or use window.location.href = url to redirect ..
With React Router 6 you can simply write :
import {useNavigate} from "react-router-dom";
const navigate = useNavigate()
const goToPageOnClick = () =>{
navigate(target_url)
navigate(0)
}
You can achieve that with React Router v6.
import React from 'react';
import { useNavigation, useLocation } from 'react-router-dom';
const Component = () => {
const history = useNavigation();
const location = useLocation();
const reload = () => {
navigate(location.pathname);
};
return (
...
);
};
and then put your reload function inside a useEffect hook.
PS: but this is a weird question, since react-router reloads the page automatically.
If you are needing an asynchronous reload, use history.go(0) (it wraps the History.go() method).
If you need to reload the page synchronously, use history.push(location.pathname) (it wraps the History.pushState() method).
Since there are already examples here using history.go(0), here's an example using history.push(location.pathname):
import React from 'react';
import { useHistory, useLocation } from 'react-router-dom';
const Component = () => {
const history = useHistory();
const location = useLocation();
const reload = () => {
history.push(location.pathname);
};
return (
...
);
};
update webpacker.yml
devServer: {
historyApiFallback: true,
}
Well, the easiest way is to first identify a route for reload and thereafter call the window.location.reload() function on the route like so:
<Switch>
<Route exact exact path="/" component={SomeComponent} />
<Route path="/reload" render= {(props)=>window.location.reload()} />
</Switch>
I recently had the same problem and created this(https://github.com/skt-t1-byungi/react-router-refreshable).
<Refreshable>
<Switch>
<Route path="/home">
<HomePage />
</Route>
<Route path="/post">
<PostPage />
</Route>
{/* ... */}
</Switch>
</Refreshable>
I'm wondering that there is a way I can make an api call with an interval and update my view whenever there is a change in api data.
part of my code looks like..
index.ios.js
....
import App from './src/components/app';
import reducers from './src/reducers';
import { getData } from './src/actions/index';
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancer(applyMiddleware(ReduxPromise, ReduxThunk)));
store.dispatch(getData());
class MyApp extends Component {
render() {
return (
<Provider store={store}>
<App />
</Provider>
);
}
}
AppRegistry.registerComponent.....
So the store.dispatch(getData()) initially make an api call and I can display the data using redux (this.props.data..something like this) in my containers.
I tried to make an api call with an interval like store.dispatch(setInterval(getData()), 30000) but it gives me an error saying undefined is not an object(evaluating 'func.apply')
If anyone has had this kind of issue before or knows what to do in this situation, please let me know.
Thanks for your support in advanced.
I have a universal react app that's using redux and react-router.
I have several routes as follows:
/2016
/2015
/2014
/2013
etc.
Each route requires data from an API. Currently, i have the <Link> elements in the Navigation component dispatch an async action onClick, which populates the store with data from the API for that route.
For MVP, i'm just overwriting the post: {} contents in the store with the new post contents when the route changes, that way we get any new content that was on the API.
I've realise that having the action dispatchers on the <Link> buttons isn't optimal, as hitting the back button does not re-trigger the action dispatch to get the content for the previous route.
Is there a way to get React Router to trigger the dispatch action anytime a route change occurs? (Limiting it to listen to a specific set of routes would be a bonus).
I realise i should be getting the history from the store, but for now, it's easier to hit the API again by triggering an action dispatch in order to get the new content.
Cheers.
The 'lifecycle' hook onEnter and onChange has been removed in React-router 4 which makes most of the other answers to this question out-dated.
Whilst I recommend you to use your components lifecycle methods to achieve your goal, here is an answer to your question which works on React-router 4.
What works today is listen to the history change using History library created by the developers of React router themself and dispatch async actions from there.
// history.js
import createHistory from "history/createBrowserHistory"
const history = createHistory()
// Get the current location.
const location = history.location
// Listen for changes to the current location.
const unlisten = history.listen((location, action) => {
//Do your logic here and dispatch if needed
})
export default history
Then import the history in your application
// App.js
import { Router, Route } from 'react-router-dom';
import Home from './components/Home';
import Login from './components/Login';
import history from './history';
class App extends Component {
render() {
return (
<Router history={history}>
<div>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
)
}
}
Source: History library
React router docs
Yeah React Router has onEnter and onLeave hooks. You could build your routes to take your store instance, so you can access it in those helpers:
const createRoutes = (store) => {
const fetchPosts = () => store.dispatch({
types: ['FETCH_POSTS', 'FETCH_POSTS_SUCCESS', 'FETCH_POSTS_FAIL',
url: '/posts'
});
return (
<Route path="/" component={App}>
<Route path="posts" component={PostList} onEnter={fetchPosts}/>
<Route path="posts/:id" component={PostDetail} />
</Route>
)
}
A better solution is to use something like redial or redux-async-connect. This allows you to co-locate your component's data dependencies with your components, while retaining the ability to test your components without touching the network.
Edit: This applies to an old, no longer supported version of react-router.
I prefer to have actions dispatched from the render prop itself:
<Route to="path" render={ props => {
this.props.toggleInfoLayer(true);
return <UserInfo />;
}} />
This is assuming you are using Redux's mapDispatchToProps argument.
I tried using the history change event handler as mentioned in the accepted answer, but I found it undesirable to be dispatching actions from a rogue file. One more place I had to think about, when Redux already provides plenty too many.