I'm trying to utilize createMemoryHistory to move around without changing the url address because my app will be rendered inside an iframe. However, when I push to history, it seems to update my url. Any tips would be greatly appreciated!
//history.js
import createMemoryHistory from "history/createMemoryHistory";
const history = createMemoryHistory();
export default history;
//App.js
import history from './history/history';
...
<Router>
<Route
path={'/'}
render={(props) => <Component {...props}/>}
/>
</Router>
//component.js
...
function handleClick(history) {
history.push('somePath'); // this updates my url to be url.com/somePath
}
return (<Button onClick={() => handleClick(this.props.history)}>);
While making use of MemoryHistory, you should pass the history object on to the Router and use it directly after importing the created history like
App.js
import history from './history/history';
...
<Router history={history}>
<Route
path={'/'}
render={(props) => <Component {...props}/>}
/>
</Router>
component.js
import history from './history/history';
...
function handleClick() {
history.push('somePath'); // this updates my url to be url.com/somePath
}
return (<Button onClick={() => handleClick()}>);
Fixed the problem, I was importing something incorrectly lol. Still stuck with createMemoryHistory in the end instead of createBrowserHistory
Related
I've been messing around with React Router 6.4, but I'm having troubles with their new way of creating a routing system.
Here's the configuration:
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="/signin-redirect" element={<SignIn />} />
</Route>
)
);
const root = createRoot(container);
root.render(
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
This works perfectly, if I go to localhost:9001 (which is the homepage) I see my Home component rendered inside of my App component.
I have a link inside of this Home component that goes to /signin-redirect
import React, { HTMLProps } from "react";
import { Link } from "react-router-dom";
export interface Props extends HTMLProps<HTMLDivElement> {}
export const Home = ({ ...rest }: Props) => {
return (
<div {...rest}>
<Link to="/signin-redirect">Sign in </Link>
</div>
);
};
Whenever I click on that link, it works perfectly. The page displays without any issues.
However, whenever I refresh the page or go to the page directly nothing gets rendered...
If it helps anybody in the future. In my webpack configuration I had a devServer option in which I had to add historyApiFallback: true.
I have a fucntion that I want it to replace the components between Header and Footer components.
Where/How should I use the defaultRouter function with the history.push. I see only the URL is changed.
The the Header component is updating the pageIndex.
I tried to change defaultRouter to a useCallback function but that didn't worked at all.
What i the order of theses functions?
App.js
function App() {
const [searchResults, setSearchResults] = useState([]);
const [itemsAmount, setItemsAmount] = useState('');
const [pageIndex, setPageIndex] = useState();
const history = createBrowserHistory();
// let history = useHistory();
const defaultRouter = useMemo(() => {
console.log(pageIndex);
switch (pageIndex) {
case 1:
history.push({ pathname: '/search', state: searchResults });
break;
case 2:
history.push('/cart');
break;
default:
history.push('/');
break;
}
}, [pageIndex]);
return (
<div className='App'>
<Header
settingResults={setSearchResults}
itemsCartAmount={itemsAmount}
setPageIndex={setPageIndex}
/>
{defaultRouter}
<Footer />
<Router history={history}>
<Switch>
<Route exact path='/'>
<Home />
</Route>
<Route path='/search'>
<Search/>
</Route>
<Route path='/cart'>
<Cart />
</Route>
<Route component={PageNotFound} />;
</Switch>
</Router>
</div>
);
}
Edit
I just removed BrowserRouter from Index.js, the same problem.
Index.js
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
This way above the pageIndex is updating but history.push don't switch the components.
What I think is happening here is that history object is getting created everytime when the pageIndex is being updated. Ideally it should be created just once manually.
You can find the working solution here Sandbox.
I have updated
the logic for creating history object
removed Router from App
used useHistory to get the history object in the component.
also wrote the effect to push changes when pageIndex is updated.
update
please have a look at this, I tried to replicate your issue.
https://codesandbox.io/s/amazing-swartz-jxc5p?file=/src/App.js
You have used Router twice, one inside App.js as <Router> and other inside index.js as <BrowserRouter>.
What you need to do is; only use inside App.js and make sure it is imported as:
import {Router} from 'react-router-dom';
as Router accepts history prop, not the BrowserRouter, and also remove BrowserRouter from index.js.
For safer side, use useHistory() hook for history value.
This will fix your issue.
Problem: When I use history.push(), I can see that browser changes url, but it does not render my component listening on the path. It only renders if I refresh a page.
App.js file:
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { Provider } from "react-redux";
import PropTypes from "prop-types";
//Components
import LoginForm from "../LoginForm/LoginForm";
import PrivateRoute from "../PrivateRoute/PrivateRoute";
import ServerList from "../ServerList/ServerList";
const App = ({ store }) => {
const isLoggedIn = localStorage.getItem("userToken");
return (
<Router>
<Provider store={store}>
<div className="App">
{isLoggedIn !== true && (
<Route exact path="/login" component={LoginForm} />
)}
<PrivateRoute
isLoggedIn={!!isLoggedIn}
path="/"
component={ServerList}
/>
</div>
</Provider>
</Router>
);
};
App.propTypes = {
store: PropTypes.object.isRequired
};
export default App;
Inside my LoginForm, I am making a request to an API, and after doing my procedures, I use .then() to redirect my user:
.then(() => {
props.history.push("/");
})
What happens: Browser changes url from /login to /, but component listening on / route is not rendered, unless I reload page.
Inside my / component, I use useEffect() hook to make another request to API, which fetches data and prints it inside return(). If I console.log inside useEffect() it happens twice, I assume initial one, and when I store data from an API inside component's state using useState() hook.
EDIT: adding PrivateRoute component as requested:
import React from "react";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ component: Component, isLoggedIn, ...rest }) => {
return (
<Route
{...rest}
render={props =>
isLoggedIn === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/login" }} />
)
}
/>
);
};
export default PrivateRoute;
What I tried already:
1) Wrapping my default export with withRouter():
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginForm));
2) Creating custom history and passing it as prop to Router.
react-router-dom version is ^5.0.1. react-router is the same, 5.0.1
You have at two mistakes in your code.
You are not using <switch> component to wrap routes. So all routes are processed at every render and all components from each <route> are rendered.
You are using local store to exchange information between components. But change in local store is invisible to react, so it does not fire component re-rendering. To correct this you should use local state in App component (by converting it to class or using hooks).
So corrected code will look like
const App = ({ store }) => {
const [userToken, setUserToken] = useState(localStorage.getItem("userToken")); // You can read user token from local store. So on after token is received, user is not asked for login
return (
<Router>
<Provider store={store}>
<div className="App">
<Switch>
{!!userToken !== true && (
<Route exact path="/login"
render={props => <LoginForm {...props} setUserToken={setUserToken} />}
/>
)}
<PrivateRoute
isLoggedIn={!!userToken}
path="/"
component={ServerList}
/>
</Switch>
</div>
</Provider>
</Router>
);
};
And LoginForm should use setUserToken to change user token in App component. It also may store user token in local store so on page refresh user is not asked for login, but stored token is used.
Also be sure not to put anything between <Switch> and </Switch> except <Route>. Otherwise routing will not work.
Here is working sample
this is my first post in Stackoverflow and I hope you guys can give me some guidance.
I'm currently learning React Router and it seems that I got stucked on an error or bug that I can't find to solve. To give you a little overview about the setup, here it is:
I'm using CRA with React-Router-DOM 5.0.1.
I'm using React Hooks to practice and using latest version of React 16.8.6
This is an app that reads a local API and it renders its data on different components. Here is the code:
App.js
import React, { Fragment, useState, useEffect } from 'react';
import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
import axios from 'axios';
import Writers from './Writers';
function App() {
const [writers, setWriters] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios('http://localhost:3004/writers');
setWriters(result.data);
};
fetchData();
}, []);
return (
<BrowserRouter>
<Fragment>
<ul>
<li>
<Link to='/'>Home</Link>
</li>
<li>
<Link to='/writers'>Writers</Link>
</li>
</ul>
<Switch>
<Route exact path='/' render={() => <div>Home</div>} />
<Route
exact
path='/writers'
render={props => <Writers {...props} writers={writers} />}
/>
</Switch>
</Fragment>
</BrowserRouter>
);
}
export default App;
Writers.js
import React, { Fragment } from 'react';
import { Route, Link } from 'react-router-dom';
import Writer from './Writer/index';
const Writers = props => {
console.log(props);
return (
<Fragment>
<ul>
{props.writers.map(({ id, name }) => (
<li key={id}>
<Link to={`${props.match.url}/${id}`}>{name}</Link>
</li>
))}
</ul>
<Route
path={`${props.match.url}/:writerID`}
render={props => <Writer {...props} />}
/>
</Fragment>
);
};
export default Writers;
Writer.js
import React, { Fragment } from 'react';
const Writer = ({ match, id, name, description }) => {
console.log(match);
return <Fragment>id name description</Fragment>;
};
export default Writer;
So I'm having an issue in Writers.js where I'm passing the params "/:writerID", this params doesn't get to Writer component and actually Writer component never gets rendered at all
<Route
path={`${props.match.url}/:writerID`}
render={props => <Writer {...props} />}
/>
If I remove :writerID from the path prop, the component Writer gets rendered BUT it doesn't allow me route it the unique path that I'm looking for.
Any idea why this might be happening? Any help will be greatly appreciated.
I have tried making sure I have the correct exports on my files and they are, in fact, I don't see any errors from the CRA logs.
Also, I remove the params from the path props and it seems that the Writer components renders but if I put it again, it doesn't work.
App.js passing Writers with props and writers data
<Route
exact
path='/writers'
render={props => <Writers {...props} writers={writers} />}
/>
Writers.js receives the data and Route props so I can access Match props but this component does NOT render Writer component neither get the match.params prop.
<Route
path={`${props.match.url}/:writerID`}
render={props => <Writer {...props} />}
/>
Expected behavior: Writer.js should be render when trying to click on any individual writer and it should allow me to get the match.params prop.
Actual Behavior: Writer.js does not gets rendered and if I remove the path prop from Route, it gets render but I can't access the individual id.
In your Route path for /writers you are using the exact prop, which will lead to any nested Route not being rendered. Remove the exact prop and your Routes will work
<Route
exact
path='/writers'
render={props => <Writers {...props} writers={writers} />}
/>
Also as a thumb rule you should use match.url for Link path and match.path for a Route path
I am developing an ordinary React JS app and using a BrowserRouter. I need to know when the route changes and the only thing I found was to use the history package by the same guys (react-training).
Their example looks easy enough but does not work at all for me:
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
console.log('a')
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
console.log(`The last navigation action was ${action}`)
})
const A = props => <div>A <NavLink to="/b">b</NavLink></div>
const B = props => <div>B <NavLink to="/a">a</NavLink></div>
ReactDOM.render(
<BrowserRouter>
<div>
<Route path="/a" component={A}/>
<Route path="/b" component={B}/>
</div>
</BrowserRouter>,
document.getElementById('app')
)
The console prints "a" but the listen-callback never gets called when I click around and my URL changes.
There is not much more in their documentation so does anyone know what is missing?
If you want to listen route changes in this way, I think you should use Router not BrowserRouter and give it your new created history as a prop.
Here is the code with changes:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Link } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
console.log(`The last navigation action was ${action}`)
})
const A = props => <div>A <Link to="/b">b</Link></div>
const B = props => <div>B <Link to="/a">a</Link></div>
ReactDOM.render(
<Router history={history}>
<div>
<div>Hello!</div>
<Route path="/a" component={A}/>
<Route path="/b" component={B}/>
</div>
</Router>,
document.getElementById('root')
)
And here is the console log that I got:
Hope it helps.