Using react-router v4 and BrowserRouter I'm calling history.push to navigate to a different path. Unfortunately the url change in the browser but there is no navigation. I'm not sure if composing route is the right way to go or am I missing something?
<Route render={({history}) =>
<div className="nav" onClick={() => {history.push('/new-route');}}><i
className="fa fa-user"/></div>}
/>
You can try using the context api
const Button = (props, context) => (
<button
type='button'
onClick={() => {
// context.history.push === history.push
context.history.push('/new-location')
}}
>
Click Me!
</button>
)
// you need to specify the context type so that it
// is available within the component
Button.contextTypes = {
history: React.PropTypes.shape({
push: React.PropTypes.func.isRequired
})
}
Related
I made a go Back button on a page which moves user from current page to a previous one. It works fine on PC and Android devices but it doesn't on iOS. Unfortunatelly I don't have a Mac to take a look at devtools to understand what's happening. Might be someone knows how to fix that? I suspect that the reason might be in useParams and React Router, but I don't know...
GoBack.tsx
interface IGoBackLinkProps {
text: string;
}
const GoBackLink: React.FC<IGoBackLinkProps> = ({ text }) => {
const history = useHistory();
return (
<Link className={styles.link} to='/' onClick={() => history.goBack()}>
{text}
</Link>
);
};
export default GoBackLink;
I use this component in this way:
SomeOtherCOmponent = () => {
const {id} = useParams
...
return (
<GoBackLink text={'Назад'} />
...
)
}
The <Link /> component from react-router-dom, by default, will render an <a> tag and it will use the path from the to prop as the href. So your Link is probably rendering something like this but with an onClick event handler:
<a href="/">
Go Back
<a>
When the user clicks this link, the onClick event is dispatched and then the anchor tag takes you to '/'.
One way to fix this would be to just use a button instead of Link:
<button onClick={() => {history.goBack()}}>
Go Back
</button>
If you must use the Link component or an a tag, e.preventDefault() in your onClick handler may also work.
I have 4 components: App, CatalogList, CatalogPreview, CatalogDetail
App is the parent component and I have these state values:
const [catalog, setCatalog] = useState({ id: 1, name: 'Electronics' })
const [isModalOpen, setIsModalOpen] = useState(false)
CatalogList and CatalogPreview components receive the above state values as props along with their setter functions.
Inside CatalogList.js:
I have a list of catalog links (electronics, furniture, ...) that when you click on one of them,
it opens up the CatalogPreview modal. Inside the modal, there's an 'Explore' button.
When you click the 'Explore' button, it needs to do 4 things:
set the selected catalog in state (in App.js)
save selected catalog in localStorage
navigate to the detail page
close the modal window
I attempted the following in the 'Explore' button's click handler (Inside the modal):
function explore() {
props.setCatalog({...})
props.setIsModalOpen(false)
window.localStorage.setItem('catalog', JSON.stringify({...})
history.push('detail-route') // this brings up the CatalogDetail
}
Link to Code Sandbox
Questions:
Is this the correct way?
In the CatalogDetail component, if I click a Delete button and make a delete API request, how do I navigate back to the CatalogList route AFTER the call is completed.
Instead of storing the modal open/close state in the parent App component it should be demoted to the child CatalogList component that is managing the modal.
Move the persisting to localStorage to a useEffect hook with a dependency on the catalog state. When the state updates, persist it to localStorage.
Create a updateCatalog callback function to accept a new value to update the catalog state with. Pass this to CatalogList.
The explore callback should be promoted/lifted up to the CatalogList component. This allows CatalogPreview to basically just render the modal. explore still calls to update the catalog data, close the modal, and navigate.
App
function App() {
const [catalog, setCatalog] = useState({ id: 1, name: "Electronics" });
useEffect(() => {
window.localStorage.setItem("catalog", JSON.stringify(catalog));
}, [catalog]);
const updateCatalog = value => {
setCatalog( ...value... );
};
return (
<Switch>
<Route exact path="/">
<CatalogList updateCatalog={updateCatalog} />
</Route>
<Route exact path="/detail">
<CatalogDetail catalog={catalog} />
</Route>
</Switch>
);
}
CatalogList
function CatalogList(props) {
const history = useHistory();
const [isOpen, setIsOpen] = useState(false);
const catalogs = [
{ id: 1, name: "Electronics" },
{ id: 2, name: "Furniture & Appliances" },
{ id: 3, name: "Sports & Outdoors" }
];
const openPreview = () => {
setIsOpen(true);
};
function explore() {
props.updateCatalog(... some new value ...);
setIsOpen(false);
history.push("/detail");
}
return (
<div>
Available catalogs
<ul>
{catalogs.map((c, index) => (
<li key={c.id} onClick={() => openPreview(c.id)} className="link">
{c.name}
</li>
))}
</ul>
<CatalogPreview isOpen={isOpen} onExplore={explore} />
</div>
);
}
CatalogPreview
function CatalogPreview(props) {
return (
<Modal show={props.isOpen}>
<Modal.Header closeButton>
<Modal.Title>Catalog Preview</Modal.Title>
</Modal.Header>
<Modal.Footer>
<Button variant="primary" onClick={props.onExplore}>
Explore
</Button>
</Modal.Footer>
</Modal>
);
}
Use useHistory and update the onClick handler to navigate back to the home page.
function CatalogDetail(props) {
const history = useHistory();
return (
<div>
<p>Detail Page</p>
<button
onClick={() => {
// Make API call then navigate home
history.push("/");
}}
>
DELETE
</button>
</div>
);
}
Demo
I'm working on a reactJS web App , im stuck in this problem . I want to use or any other method to redirect to another page after testing a value from the server.
I tried this :
<Link to='/Upload'>
<Button
variant="contained"
color="primary"
onClick={async () => {
await verificationp(phone, code).then(async (result) => {
if ((await result) === true) {
//I want to redirect to another page
} else {
//I want to display an alert
}
});
}}
>
Submit
</Button>
</Link>
You don't need to use the Link component in this example. Try using the history prop (props.history.push('/')) See the below examples, the last one should apply to you:
In your App.js router:
<Switch>
<Route path="/auth" component={Auth} />
<Route path="/errorpage" component={ErrorPage} />
<Route path="/" exact component={HomeScreen} />
<Redirect to='/' />
</Switch>
On the page where you want to redirect:
import React, { useState } from 'react'
const Auth = props => {
const [form, setForm] = useState({
data: {
email: '',
password: ''
}
})
const authHandler = async (event) => {
event.preventDefault()
try {
// await dispatch(action) // dispatch to redux or send a fetch
props.history.push('/') // redirects if no errors
} catch (err) {
props.history.push('/errorpage') // redirects if an error
}
}
const inputChangedHandler = e => {
setForm({
...form,
data: {
...form.data,
[e.target.id]: e.target.value
}
})
}
return (
<div>
<form onSubmit={authHandler}>
<input id='email' type='email' value={form.data.email} onChange={inputChangedHandler} />
<input id='password' type='password' value={form.data.password} onChange={inputChangedHandler} />
<button type='submit'>Login</button>
</form>
</div>
)
}
export default Auth
In your case try the below if the page is on the route stack the history prop will be available or you will have to use withRouter or useHistory hook to get access to the history prop.
const clickHandler = () => {
try {
// await dispatch(action)
props.history.push('/') // redirects
} catch (err) {
props.history.push('/errorpage') // redirects
}
}
return (<button type='button' onClick={clickHandler}>Click Me</button)
Here is the Redirect doc
I think best thing you can do is create a separate function for onClick and change a state to redirect, so this way you can prevent some action by user while request is in progress
const [redirectTo, setRedirectTo] = useState('');
const chekcfromServer = async () => {
await verificationp(phone, code).then(async (result) => {
if ((await result) === true) {
setRedirectTo('/somewhereinyour/application')
} else {
// I want to display an alert
}
});
};
and when in render
return (
<>
redirectTo && <Redirect to={{pathname: redirectTo }} />
...rest of your code
</>
)
You can use "redirect" from "react-router-dom"
Example:
import { Redirect } from "react-router-dom";
<Link to='/Upload'>
<Button
variant="contained"
color="primary"
onClick={async () => {
await verificationp(phone, code).then(async (result) => {
if ((await result) === true) {
//I want to redirect to another page
return <Redirect to="your_url" />
} else {
//I want to display an alert
}
});
}}
>
Submit
</Button>
</Link>
I tend to use the state system (Redux) to resolve this issue.
So my problem was I wanted to fire an event.preventDefault() to stop a link being clicked, but then I wanted to do an API call after it (to check login status, as it was a complex IoT device) and click the link if the check was fine, but not click the link if it wasn't. However, the event.PreventDefault() stopped me doing this, because it cancels any events (I'm sure there are nicer ways to achieve this! but time targets and all that).
I solved this by:
Having my usual async function with the event.preventDefault() inside it.
Within the first call I set some state in Redux, just a basic boolean.
If the API check was successful then I call the same click function again (using .click()) and bypass the event.preventDefault() thanks to Redux (then reset the state afterwards) which forces the re-direct. If the check failed then I display an error message and don't display again (while obviously updating Redux).
The State Management system inside React is very powerful. You can do some hacky things! but sometimes the other "correct" solutions don't work for you; for example the history props functionality, which you can get round that other ways via State Management.
Pretty new to both react, redux and gtm. I am trying to use google tagmanager and I have a navbar where I want to get the classname of every NavLink in a variable so I can send it along my gtm-event. The reason for this is so that I can put the gtm-event in a click-handler so I don't need to put the full gtm-code in every onClick. Is there a way I can get the classnames from Navlinks in this stateless functional component? In the example below I have only put a gtm-event in one of the Navlinks (for readability), but my mission is to have one in every NavLinks. Let me know if you have any questions. Any help is appreciated. Thank you!
const intl = useIntl();
const isAuthenticated = useSelector(state => state.auth.token !== null);
const locale = useSelector(state => state.common.locale);
const user = useSelector(state => state.user.user);
return (
<StyledCategoryNav>
<div className="shadow" />
<div className="wrapper">
<ul>
<li>
<NavLink
exact
to={`/${locale}/lobby`}
className="lobby"
onClick={() => {
TagManager.dataLayer({
dataLayer: {
event: 'open_category',
locale: locale,
userId: isEmpty(user) ? null : user.UserID,
clicked: [Variable that takes the clicked buttons classname]
}
});
}}
>
<HomeIcon />
{intl.formatMessage({ id: 'categories.lobby' })}
</NavLink>
</li>
{isAuthenticated && (
<li>
<NavLink
exact
to={`/${locale}/lobby/new`}
className='new'
>
{intl.formatMessage({ id: 'categories.new' })}
</NavLink>
</li>
)}
<li>
<NavLink exact to={`/${locale}/lobby/recommended`}
className='recommended'
>
{intl.formatMessage({ id: 'categories.recommended' })}
</NavLink>
</li>
</ul>
</div>
</StyledCategoryNav>
);
}
e.target.className should do the trick.
Make sure to use activeClassName and not className because with NavLink, when the route is matched, react-router will add a class called active, so in such case your class name will be a string of classes separated by spaces.
Demo
<NavLink
exact
to={`/${locale}/lobby`}
activeClassName="lobby"
onClick={(e) => {
TagManager.dataLayer({
dataLayer: {
event: 'open_category',
locale: locale,
userId: isEmpty(user) ? null : user.UserID,
clicked: e.target.className //<----here
}
});
}}
>
send a classname in router param and get that param from another page simple. If need more help let me know.
ex:
{isAuthenticated && (
<NavLink
exact
to={/${locale}/lobby/new?**classname="class-name**"}
className='new'
>
{intl.formatMessage({ id: 'categories.new' })}
)}
Is there a non hacky way to keep Material UI tabs and React router in sync?
Basically, I want to change the URL when the user clicks on a tab [1] and the tabs should change automatically when the user navigates to a different page with a non-tab link or button, and of course on direct access [2] and page refresh too.
Also, it would be nice to have the react router's non exact feature too, so the /foo tab should be active both for /foo and /foo/bar/1.
[1] Other SO answers recommend using the history api directly, is that a good practice with react-router?
[2] I'm not sure what it's called, I meant when the user loads for example /foo directly instead of loading / and then navigating to /foo by a tab or link
Edit:
I created a wrapper component which does the job, but with a few problems:
class CustomTabs extends React.PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
setActiveTab(id) {
this.setState({
activeTab: id
});
return null;
}
render() {
return (
<div>
{this.props.children.map((tab,index) => {
return (
<Route
key={index}
path={tab.props.path||"/"}
exact={tab.props.exact||false}
render={() => this.setActiveTab(index)}
/>
);
})}
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
style={{paddingLeft: '10px', paddingRight: '10px', height: '64px'}}
onActive={() => {
this.props.history.push(tab.props.path||"/")
}}
/>
);
})}
</Tabs>
</div>
);
}
}
And I'm using it like this:
<AppBar title="Title" showMenuIconButton={false}>
<CustomTabs history={this.props.history}>
<Tab label="Home" path="/" exact/>
<Tab label="Foo" path="/foo"/>
<Tab label="Bar" path="/bar"/>
</CustomTabs>
</AppBar>
But:
I get this warning in my console:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
I think it's because I set the state immediately after render() is called - because of Route.render, but I have no idea how to solve this.
The tab changing animations are lost: http://www.material-ui.com/#/components/tabs
Edit #2
I finally solved everything, but in a bit hacky way.
class CustomTabsImpl extends PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
componentWillMount() {
this.state.activeTab = this.pathToTab(); // eslint-disable-line react/no-direct-mutation-state
}
componentWillUpdate() {
setTimeout(() => {
let newTab = this.pathToTab();
this.setState({
activeTab: newTab
});
}, 1);
}
pathToTab() {
let newTab = 0;
this.props.children.forEach((tab,index) => {
let match = matchPath(this.props.location.pathname, {
path: tab.props.path || "/",
exact: tab.props.exact || false
});
if(match) {
newTab = index;
}
});
return newTab;
}
changeHandler(id, event, tab) {
this.props.history.push(tab.props['data-path'] || "/");
this.setState({
activeTab: id
});
}
render() {
return (
<div>
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
onChange={(id,event,tab) => this.changeHandler(id,event,tab)}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
data-path={tab.props.path||"/"}
style={{height: '64px', width: '100px'}}
/>
);
})}
</Tabs>
</div>
);
}
}
const CustomTabs = withRouter(CustomTabsImpl);
Firstly, thanks for replying to your very question.
I have approached this question differently, I decided to post here for the community appreciation.
My reasoning here was: "It would be simpler if I could tell the Tab instead the Tabs component about which one is active."
Accomplishing that is quite trivial, one can do that by setting a known fixed value to the Tabs component and assign that very value to whatever tab is supposed to be active.
This solution requires that the component hosting the tabs has access to the props such as location and match from react-router as follows
Firstly, we create a function that factory that removes bloated code from the render method. Here were are setting the fixed Tabs value to the Tab if the desired route matches, other wise I'm just throwing an arbitrary constant such as Infinity.
const mountTabValueFactory = (location, tabId) => (route) => !!matchPath(location.pathname, { path: route, exact: true }) ? tabId : Infinity;
After that, all you need is to plug the info to your render function.
render() {
const {location, match} = this.props;
const tabId = 'myTabId';
const getTabValue = mountTabValueFactory(location, tabId);
return (
<Tabs value={tabId}>
<Tab
value={getTabValue('/route/:id')}
label="tab1"
onClick={() => history.push(`${match.url}`)}/>
<Tab
value={getTabValue('/route/:id/sub-route')}
label="tab2"
onClick={() => history.push(`${match.url}/sub-route`)}
/>
</Tabs>
)
}
You can use react routers NavLink component
import { NavLink } from 'react-router-dom';
<NavLink
activeClassName="active"
to="/foo"
>Tab 1</NavLink>
When /foo is the route then the active class will be added to this link. NavLink also has an isActive prop that can be passed a function to further customize the functionality which determines whether or not the link is active.
https://reacttraining.com/react-router/web/api/NavLink