I have my app.js file which looks like this:
class App extends Component {
render() {
const {classes} = this.props;
return (
<React.Fragment>
<AppBar/>
<BrowserRouter>
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition
key={location.key}
timeout={100}
classNames="someanimation"
>
<Switch location={location}>
<Route exact path="/" component={HomePage} />
<Route exact path="/contact" component={ContactPage} />
<Route exact path="/customer/:id" component={CustomerPage} />
<Route component={ErrorPage} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</BrowserRouter>
</React.Fragment>
);
}
}
This component has an appbar and a router with routes underneath. The point of the appbar being here is that the application then only has one appbar at all times throughout the application. Only the pages underneath change.
Inside my contact page I have a button that goes to a custom page with a param passed:
<Button component={Link} to={'/customer/' + customerID[99]}>
When the application goes to this customer page I want the appbar to show a back button. So I have to somehow notify the appbar to show this button and then also know which page to go back to (it should be the last page). I have googled some examples but I can't find one that fits this case.
React router contains withRouter higher-order component that can provide relevant props to application bar component.
React router is powered by history.js library which is an abstraction over browser history API. While it can be used to navigate over browser history with history.goBack(), it won't work well with navigating over application alone because window history may contain other websites.
The component can look like this (a demo) and should be a child of router component to get router props:
#withRouter
class AppBar extends Component {
state = {
locations: [this.props.location]
};
componentDidMount() {
this.props.history.listen((location, action) => {
if (action === 'REPLACE')
return;
this.setState({
locations: [location, ...this.state.locations]
})
});
}
back = () => {
const [location, ...locations] = this.state.locations;
this.setState({ locations });
this.props.history.replace(location);
}
render() {
return (
<>
{this.state.locations.length > 1 && <button onClick={this.back}>Back</button>}
</>
);
}
}
It keeps track on location changes and navigates through them. It would be more complex task to keep it in sync with browser history navigation buttons (back and forward).
Related
This is a simple version of the problem not the actual problem.
React code:
<Router>
<Navbar/>
<Routes>
<Route path="/:genreId" element={<MovieList/>} />
<Routes>
<Router>
This problem is that in the Navbar, I have several button to go the particular genre.
I want to highlight active button in the Navbar (when I click "comedy" the "comedy" button should be lit up)
some solutions
State => will not work if I reload
Session Storage => will not work if I come be website for the first time
Extract the params using the useParams Hook => this will not work since param "genreId" is available to the movieList component not the navbar
Extract from the window.location => Don't want to do it since it look too ad-hock
What is the right method?
I would suggest #3 "Extract the params using the useParams Hook => this will not work since param "genreId" is available to the movieList component not the navbar". You are correct though, this doesn't work since the Navbar component is rendered outside the Routes component and won't have access to the currently matched route. To resolve this you should move the Navbar component inside the Routes component. To make this work you'll create a layout route component that renders the Navbar component and an Outlet component for nested routes to render their element prop into.
Example:
import { Outlet } from 'react-router-dom';
const Layout = () => (
<>
<Navbar />
<Outlet />
</>
);
Then render the MovieList route as a nested route of the layout route.
<Routes>
<Route element={<Layout />}>
<Route path="/:genreId" element={<MovieList />} />
</Route>
</Routes>
The Navbar component can now safely access the genreId route path param and use it to apply any logic necessary to mark a button as "active. Here's a simple example:
const genres = ["action", "comedy", "drama"];
const Navbar = () => {
const navigate = useNavigate();
const { genreId } = useParams();
const navigateTo = (genreId) =>
navigate(generatePath("/:genreId", { genreId }));
return (
<>
{genres.map((genre) => (
<button
key={genre}
className={["genreButton", genreId === genre && "active"]
.filter(Boolean)
.join(" ")}
type="button"
onClick={() => navigateTo(genre)}
>
{genre}
</button>
))}
</>
);
};
I usually use IndexDB for that kind of stuff. To manage the IndexDB perfectly you can use localforage. You can check this link.localforage
You can use NavLink instead of Link to design the active route. Here is the documentation.
<Router>
<Navbar/>
<Routes>
<Route path="/:genreId" element={<MovieList/>} />
<Routes>
<Router>
In the Navbar component you can write like this to have the active link:
<nav>
<NavLink to="/:genreId">Genre Name</NavLink>
</nav>
I have a router which has a link to a product details page, problem is, the application is being displayed in a android webview so ... if am viewing a product details page and clicked back, the whole application will exit.
is there a way to make the use go back to the home page ? or I have to implement a back button ?
class App extends React.Component {
render() {
return (
<div>
<Router>
<Switch>
{/* <PrivateRoute exact path="/" component={HomePage} /> */}
<Route exact path="/" component={Home} />
<Template>
<Route path="/:id" component={PropertyTemplate} />
</Template>
</Switch>
</Router>
</div>)
}
}
It's not neccessary to implement backbutton - just this code make work:
history.goBack()
Here is implemented BackButton - just add this in common folder in architecture & reuse.
import React from 'react';
import { withRouter } from 'react-router-dom';
import ReturnIcon from '../../img/tech/return-white-icon.svg'
const GoBack = ({ history }) => <img src={ReturnIcon} onClick={() => history.goBack()} alt="Go back" />;
export default withRouter(GoBack);
My server is a bit slow and when displaying even small size images, you can see them loading on screen, and I would like them to be displayed right away.
Thankfully I have a landing page which then after pressing a button, redirects to the main page which contains the images.
This routing is controlled in my app.js which is like the root file. This file routes to the landing page and to the main page. I thought that I could just load the images there and then pass them as props via the router, to the main page:
import projectImage1 from './assets/1.png';
import projectImage2 from './assets/2.png';
import projectImage3 from './assets/3.png';
class App extends Component {
render() {
return (
<Route render={({location}) => (
<TransitionGroup>
<CSSTransition
key={location.pathname === '/' ? location.key : null} // Use transition only if on landing page
timeout={800}
classNames="fade"
>
<Switch location={location}>
<Route path="/" exact component={Landingpage} />
<Route path="/app/:name" exact render={(props) => <MainApp
projectImage1={projectImage1}
projectImage2={projectImage2}
projectImage3={projectImage3}
/>} />
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
);
}
}
As you can see my MainApp.js received the 3 images as props. This is technically working without errors but I'm still seeing the images loading on screen. Why is it so? How can I load them in advance so that they can be displayed smoothly?
Thanks
The browser doesn't load resources before it needs by default. For images this is when the image itself is visible. This is due to performance, but there is a way for you to load these images beforehand.
If you do this in your componentDidMount lifecycle method in your App component, the browser will load the images:
class App extends Component {
componentDidMount() {
const imagesToBePreloaded = [projectImage1, projectImage2, projectImage3]
imagesToBePreloaded.forEach(image => { new Image().src = image })
}
render() {
// [...] Removed for abbreviation.
}
}
i want redirect to "/user". i write but this not work.
how to correctly redirect to the right page
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
<Redirect to="/user"/>
});
}
My router list. Among them there is a router with the path "/ user"
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
UPADATE
App.js
The button I click on is in the component <SearchForm/>
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
There are two ways to programmatically navigate with React Router - <Redirect /> and history.push. Which you use is mostly up to you and your specific use case.
<Redirect /> should be used in user event -> state change -> re-render order.
The downsides to this approach is that you need to create a new property on the component’s state in order to know when to render the Redirect. That’s valid, but again, that’s pretty much the whole point of React - state changes update the UI.
The real work horse of React Router is the History library. Under the hood it’s what’s keeping track of session history for React Router. When a component is rendered by React Router, that component is passed three different props: location, match, and history. This history prop comes from the History library and has a ton of fancy properties on it related to routing. In this case, the one we’re interested is history.push. What it does is it pushes a new entry onto the history stack - aka redirecting the user to another route.
You need to use this.props.history to manually redirect:
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
this.props.history.push('/user');
});
}
You should be getting history as a prop from your <Router> component.
EDIT:
Okay thank you for the code update. The SearchForm component is not nested under your BrowserRouter, so it is not getting the history prop. Either move that component inside the BrowserRouter or use the withRouter HOC in SearchForm reacttraining.com/react-router/web/api/withRouter
Option 1: Move SearchForm inside the BrowserRouter
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
Option 2: use the withRouter HOC to inject the history prop into SearchForm manually:
import { withRouter } from 'react-router-dom';
class SearchForm extends React.Component { ... }
export default withRouter(SearchForm)
I have a react multi step form with a flux store like this:
// MultiForm.js
....
import LoadFile from './components/LoadFile';
import LoadPeople from './components/LoadPeople';
import Confirmation from './components/Confirmation';
class MultiForm extends Component {
.
.
.
nextPage() {
// Update page number with a flux action
MultiStepActions.nextPage();
}
previousPage() {
// Update page number with a flux action
MultiStepActions.previousPage();
}
render() {
const {pagina} = this.state;
<div className="container">
{page === 1 && <LoadFile
handleChange={this.handleChange}
file={this.state.file}
nextPage={this.nextPage}
data1={this.state.data1}
data2={this.state.data2}
data3={this.state.data3}/>
}
{page === 2 && <LoadPeople
listPeople={this.state.listPeople}
nextPage={this.nextPage}
previousPage={this.previousPage}
handleChangePeople={this.handleChangePeople}/>
}
{page === 3 && <Confirmation
listData={this.state.listData}
nextPage={this.nextPage}
previousPage={this.previousPage}/>
}
</div>
}
}
// Index.js with react-router
import Menu from './components/Menu';
class Index extends Component {
class App extends Component {
render() {
return (
<div>
<Menu />
{this.props.children}
</div>
)
}
}
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="/MultiForm" component={MultiForm}>
</Route>
</Route>
</Router>
), document.getElementById('app'));
}
It is a summary of the main component (MultiForm) and a basic react router scenario.
In the component I am using a flux store to set and get the actual number of page of the multi step form and then render the component according to the actual page.
In the components (LoadFile, LoadPeople, Confirmation) I have a button to navigate to the next and previos page (via nextPage and previousPage functions) and all wokr ok.
Now I want achieve the same result using the back and previous buttons of the browser and I suppose with react-router. So, how I must configure react router or what I need to do in order to getting the browser buttons to work equals to my next and previous buttons?
You cannot override a browser's navigation buttons. You can however, hook up the pages states defined in react-router, and as the user browses around using the navigation buttons, your application state remains synced to the browser state.
What i would suggest for you here is to create a parameter for your MultiForm route:
<Route path="/Multiform/:page" component={MultiForm} />
This would then allow you to get that page parameter in this.props.params.page
Even better, you can hook up each of the child components as routes in themselves:
<Route path="/Multiform/" component={MultiForm}>
<Route path="1" component={LoadFile} />
<Route path="2" component={LoadPeople} />
<Route path="3" component={Confirmation} />
</Route>
class Multiform extends Component {
//...
render() {
return (
<div className="container">
{ this.props.children }
</div>
)
}
}