Redirecting Conditionally using React Router - react-router

I want to go to the page with the link after checking the terms.But nothing happens.
App.tsx
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Login from "./Components/Login";
import "./styles/index.scss";
import { Suspense } from "react";
import ToDoScreen from "./Pages/ToDoScreen";
function App() {
return (
<Router>
<div className="App">
<Suspense fallback={<h2>Loading...</h2>}>
<Switch>
<Route exact path="/" component={Login} />
<Route path="/home" component={ToDoScreen} />
</Switch>
</Suspense>
</div>
</Router>
);
}
export default App;
Login.tsx
When the button I want is clicked, it first checks and then redirects to the page.
<button onClick={onClick}>Login</button>
OnClick :
const onClick = () => {
if (inputs.name.length < 3 || inputs.lastname.length < 3) {
alert("name/lastname must be at least 3 characters");
} else {
<Link to="/home"></Link>;
}
};

Link components are supposed to be rendered and not returned or used within event handlers. To dynamically update the route, you should use history.push instead
import { useHistory } from 'react-router-dom';
function MyComp() {
const history = useHistory();
const onClick = () => {
if (inputs.name.length < 3 || inputs.lastname.length < 3) {
alert("name/lastname must be at least 3 characters");
} else {
history.push("/home");
}
};
}

<Link> just generates an anchor tag wrapped with some "react-router" functionality.
It won't actually make the user go to a particular route unless, the user clicks on the generated <a>.
Instead, you want to use <Redirect>. With this, you can redirect the user to a new Route.
Thereby, making your else condition to be
else {
<Redirect to="/home" />
}
Refer to the official documentation for more information.

Related

MemoryRouter and jest test

https://reacttraining.com/react-router/web/guides/testing
The react-router testing documentation is bit obscure to me.
How to write a test to check a route is rendered
A Component. - APage.js
import React, { Component } from 'react'
export default class APage extends Component {
render() {
return (
<div>
A Page
</div>
)
}
}
Writing a unit test to check , as per documentation.
routes.test.js
import React from 'react'
import { render } from "react-dom";
import APage from './APage'
import {MemoryRouter} from 'react-router-dom';
test("render route", () => {
render(
<MemoryRouter initialEntries={["/apage"]}>
<APage />
</MemoryRouter>
);
});
It gives an error,
Invariant Violation: Target container is not a DOM element.
for render.
How do I write a basic test, like to test a component is rendered on a route.
I'd like to comment on Remi's solution, since the API in React Router v6 is a little different (and the link to the docs leads now to a 404):
import { Router } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
render(
<Router
location={history.location} // history.location has a default value of '/'
navigator={history}
>
<APage />
</Router>
);
})
see github repo here
I think you should use Router instead. Since that uses BrowserRouter. (see alternatives section on react router example page)
import { Router } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
history.push('/apage');
render(
<Router history={history}>
<APage />
</Router>
);
});
It could be that you should also add your page in a Route, but I'm not sure.
Then it would be something like:
import { Router, Route } from 'react-router-dom';
test("render route", () => {
const history = createMemoryHistory();
history.push('/apage');
render(
<Router history={history}>
<Route path='/aroute' render={(props) => (<APage {...props} />)} />
</Router>
);
});
Ok. Route testing has to be done by enzyme. not just using jest.
Followed https://medium.com/#antonybudianto/react-router-testing-with-jest-and-enzyme-17294fefd303
Used enzyme mount to test.

How to create a link that goes back in react-router-dom v4

I know I can access history.goBack() to go back in the router history.
However, I'd like to create a <Link /> tag that has this functionality and relies on the to property (href) to navigate back rather than an onClick.
Is this possible?
I may have a solution to your problem using the context api.
But I strongly believe that it would be easier to use history.goBack().
First you'll need to wrap the App component inside a router:
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root'),
);
Then in your your App/index.js file you'll need to listen to the location change event and set your state accordingly:
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import { withRouter } from 'react-router'
class App extends Component {
state = { prevLocation: '' };
// Use the context api to retrieve the value in your Link
getChildContext = () => (
{
prevLocation: this.state.prevLocation,
}
);
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.setState({ prevLocation: this.props.location.pathname });
}
}
render() {
return (
<div>
<Switch>
// ...
</Switch>
</div>
);
}
}
App.childContextTypes = {
prevLocation: PropTypes.string,
};
export default withRouter(App);
Then in can create a GoBack component and use the context API to retrieve the value the previous path.
import React from 'react';
class GoBack extends React.Component {
render() {
return <Link to={this.context.prevLocation}>click</Link);
}
}
GoBack.contextTypes = {
prevLocation: PropTypes.string,
};

appbar responsive with options with react router v4, material-ui and apollo client

I'm working with apollo client, react, reac routerv4 and material-ui, my app is working ,
before insert material-ui i had
<Link to="/" className="navbar">React + GraphQL Tutorial</Link>
then i've inserted material-ui
<AppBar
title="Title"
iconClassNameRight="muidocs-icon-navigation-expand-more"
/>
but it's not clear for me how to add links for the title and options, in responsive mode with small screen the options i think must be invisible, in small screen not.
The official material-ui site is not well explained by example like bootstrap, so i need a litlle of help.
the full code is:
import React, { Component } from 'react';
import {
BrowserRouter,
Link,
Route,
Switch,
} from 'react-router-dom';
import './App.css';
import ChannelsListWithData from './components/ChannelsListWithData';
import NotFound from './components/NotFound';
import ChannelDetails from './components/ChannelDetails';
import AppBar from 'material-ui/AppBar';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import {
ApolloClient,
ApolloProvider,
createNetworkInterface,
toIdValue,
} from 'react-apollo';
const networkInterface = createNetworkInterface({ uri: 'http://localhost:4000/graphql' });
networkInterface.use([{
applyMiddleware(req, next) {
setTimeout(next, 500);
},
}]);
function dataIdFromObject (result) {
if (result.__typename) {
if (result.id !== undefined) {
return `${result.__typename}:${result.id}`;
}
}
return null;
}
// customResolvers:
// This custom resolver tells Apollo Client to check its cache for a Channel object with ID $channelId
// whenever we make a channel query. If it finds a channel with that ID in the cache,
// it will not make a request to the server.
const client = new ApolloClient({
networkInterface,
customResolvers: {
Query: {
channel: (_, args) => {
return toIdValue(dataIdFromObject({ __typename: 'Channel', id: args['id'] }))
},
},
},
dataIdFromObject,
});
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<BrowserRouter>
<MuiThemeProvider muiTheme={getMuiTheme()}>
<div className="App">
<Link to="/" className="navbar">React + GraphQL Tutorial</Link>
<AppBar
title="Title"
iconClassNameRight="muidocs-icon-navigation-expand-more"
/>
<Switch>
<Route exact path="/" component={ChannelsListWithData}/>
<Route path="/channel/:channelId" component={ChannelDetails}/>
<Route component={ NotFound }/>
</Switch>
</div>
</MuiThemeProvider>
</BrowserRouter>
</ApolloProvider>
);
}
}
export default App;
the right is add a code like this:
<AppBar position="static">
<Toolbar>
<IconButton color="contrast" aria-label="Menu">
</IconButton>
<Typography type="title" color="inherit" >
{"Admin"}
</Typography>
<AuthLink to="/customers" label="Customers"/>
<AuthLink to="/tours" label="Tours"/>
<AuthLink to="/signout" label="Sign Out"/>
<AuthLink to="/signin" label=" Sign In" whenLogged="false"/>
</Toolbar>
</AppBar>
Authlink is just a component that I wrote to show the options and where simple I add the Title to display options.
const AuthLink = (props) => {
let auth = checkAuth();
return (
( (auth && !props.whenLogged ) || (!auth && props.whenLogged == "false") ) ? (
<Link to={props.to} className="navbar"><Button>{props.label}</Button></Link>
) : (
null
)
);
}
"Button" is a component from material, "Link" from react-router, here the imports:
import {
BrowserRouter,
Link,
Route,
Switch,
Redirect,
} from 'react-router-dom';
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import IconButton from 'material-ui/IconButton';

Migrating from React Router v2 to v4

So, I'm currently using react-router v2 as follows:
import { IndexRoute, Router, Route, Redirect } from 'react-router';
import App from './components/App';
....
render () {
return (
<ApolloProvider store={store} client={client}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={PhotoGrid} />
<Route path="/view/:postId" component={Single}></Route>
<Route path="/login" component={LoginUser}></Route>
</Route>
</Router>
</ApolloProvider>
)
}
}
export default MainApp;
App.js
....
import Main from './Main';
const allPostsCommentsQuery = graphql(All_Posts_Comments_Query, {
options: {
cachePolicy: 'offline-critical',
fetchPolicy: 'cache-first',
},
});
function mapStateToProps(state) {
return {
auth: state.auth
};
}
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
}
export default compose(
allPostsCommentsQuery,
connect(mapStateToProps, mapDispatchToProps)
)(Main);
Main.js
class Main extends React.Component {
constructor (props) {
super(props);
}
componentWillMount () {
if (!this.props.auth.token){
this.context.router.push('/login');
}
}
render () {
return (
<div>
<h1>
<Link to="/">Flamingo City</Link>
</h1>
{ React.cloneElement(this.props.children, this.props) }
</div>
);
}
}
Main.contextTypes = {
router: function() { React.PropTypes.func.isRequired }
};
export default Main;
How do I convert my current v2 router to v4? What I am not clear on, is the parent nested element:
<Route path="/" component={App}>
In all the v2 -> v4 conversion examples I have seen thus far, none clearly explain what happens to the child elements. Am I expected to place the child elements within the App.js component itself, and if so, in the version of my App.js, how would that work as the first sign of any navigation actually occurs with Main.js?
Really useful post on github where you can see all the important parts of migrating to v4.
https://gist.github.com/kennetpostigo/7eb7f30162253f995cd4371d85c1e196
Also explaining how to go about child routes. Basically, you are supposed to place a Match inside App.js so this parent component will become responsible for its own part of child routes, an so on with every parent component.
Haven't tried this, let me know how it goes!

Testing href value from a link component while having dynamic props

I am looking for a solution in order to still be able to use Link from react-router instead of a when testing href attribute value.
Indeed, I have some components which change of route according to the context. However, when I am testing the href attribute value, the only thing returned is null.
However, when I use an a, it returns me the expected value.
Here is an failing test:
import React from 'react';
import {Link} from 'react-router';
import TestUtils from 'react-addons-test-utils';
import expect from 'must';
const LINK_LOCATION = '/my_route';
class TestComponent extends React.Component {
render() {
return (
<div>
<Link className='link' to={LINK_LOCATION}/>
<a className='a' href={LINK_LOCATION}/>
</div>
);
}
}
describe('Url things', () => {
it('should return me the same href value for both link and a node', () => {
const test_component = TestUtils.renderIntoDocument(<TestComponent/>);
const link = TestUtils.findRenderedDOMComponentWithClass(test_component, 'link');
const a = TestUtils.findRenderedDOMComponentWithClass(test_component, 'a');
expect(link.getAttribute('href')).to.eql(a.getAttribute('href'));
});
});
Output: AssertionError: null must be equivalent to "/my_route"
knowbody from React-router answered to see how they test Link, but they do not have dynamic context which can change value of the href attribute.
So I have done something like that:
class ComponentWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
set_props(props) {
this.setState({props});
}
render() {
if (this.state.props) {
return <Component {...this.state.props}/>;
}
return null;
}
}
But now, from my component helper:
render_into_document() {
const full_component_props = {
location: this.location,
widget_categories: this.widget_categories
};
node = document.createElement('div');
this.component = render((
<Router history={createHistory('/')}>
<Route path='/' component={ComponentWrapper} />
</Router>
));
this.component.set_props(full_component_props);
return this;
}
I am not able to lay hand on this.component in order to changes props. How could I do that?
I just looked at how react-router tests <Link /> and came up with this for my case:
import test from 'ava'
import React from 'react'
import { render } from 'enzyme'
import { Router, Route } from 'react-router'
import createHistory from 'history/lib/createMemoryHistory'
import SkipToXoom from '../skip-to-xoom'
test('the rendered button redirects to the proper URL when clicked', t => {
const toCountryData = { countryName: 'India', countryCode: 'IN' }
const div = renderToDiv({ toCountryData, disbursementType: 'DEPOSIT', userLang: 'en_us' })
const { attribs: { href } } = div.find('a')[0]
t.true(href.includes(encodeURIComponent('receiveCountryCode=IN')))
t.true(href.includes(encodeURIComponent('disbursementType=DEPOSIT')))
t.true(href.includes(encodeURIComponent('languageCode=en')))
})
/**
* Render the <SkipToXoom /> component to a div with the given props
* We have to do some fancy footwork with the Router component to get
* the Link component in our SkipToXoom component to render out the href
* #param {Object} props - the props to apply to the component
* #returns {Element} - the div that contains the element
*/
function renderToDiv(props = {}) {
return render(
<Router history={createHistory('/')}>
<Route path="/" component={() => <SkipToXoom {...props} userLang="en" />} />
</Router>
)
}
I hope that's helpful!