I have to use this.props.history.push('/...') in a nested component so I added withRouter() to navigate without history problems using react-router-dom.
But since I have added withRouter, I have You should not use Route outside a Router error.
I have read posts about this error but I can't understand what is wrong with my code.
Root.js:
import { BrowserRouter as Router, Route, Switch, Redirect, withRouter} from 'react-router-dom'
...
const Root = ({ store }) => (
<Router>
<Provider store={store}>
<StripeProvider apiKey="pk_test_XXXXXXXXX">
<Switch>
<Route exact path="/" component={App} />
<Route path="/comp1" component={Comp1} />
<Route path="/comp2" component={Comp2} />
<Route path="/store" component={MyStoreCheckout} />
<Route component={Notfound} />
</Switch>
</StripeProvider>
</Provider>
</Router>
)
Root.propTypes = {
store: PropTypes.object.isRequired
}
export default withRouter(Root)
and index.js:
import { createStore } from 'redux'
import myReducer from './redux/Reducers/myReducer'
import Root from './Root'
import Store from './redux/Store/store'
render(<Root store={Store} />, document.getElementById('root'))
I use withRouter to be able to call this.props.history(...) in CheckoutForm
MyStoreCheckout.js:
class MyStoreCheckout extends React.Component {
render() {
return (
<Elements>
<InjectedCheckoutForm />
</Elements>
);
}
}
export default MyStoreCheckout;
CheckoutForm.js:
class CheckoutForm extends React.Component {
handleSubmit = () => {
fetch(getRequest)
.then((response) => response.json())
.then(...)
.then(() => this.goToSuccessPage())
}
goToSuccessPage(){
this.props.history.push('/') ; //----- error is here if I have no withRouter
render() {
return (
<form onSubmit={this.handleSubmit}>
<DetailsSection/>
<CardSection />
<button>Confirm order</button>
</form>
);
}
}
export default injectStripe(CheckoutForm);
As I mentioned in my comment... Just import withRouter at the top of your CheckoutForm file, then wrap the export with it, at the bottom. Like this:
CheckoutForm.js:
import { withRouter} from 'react-router-dom'
class CheckoutForm extends React.Component {
// ... your class code ...
}
export default withRouter(injectStripe(CheckoutForm));
If your injectStripe HOC doesn't pass all of the props from withRouter down to CheckoutForm, you can try doing export default injectStripe(withRouter(CheckoutForm)); instead, but order shouldn't matter (if set up correctly)
Related
I'm pretty sure that I am using in the right place, but I am still getting the error. Any idea why?
Error: Invariant failed: You should not use <withRouter(App) /> outside a <Router>
Apologies for the ugly code. I am new to react.
render()
return (
<BrowserRouter>
<div className="App">
<Route path="/" exact render={
(props)=> {
return(
<div>
<input type='text' onChange={this.formChangeHandler}/>
<p><button onClick={ () => this.postData(this.state.message)}>Submit</button></p>
</div>
)}
}/>
<Route path="/post" exact render={
(props)=> {
return(
<div>
<b>SUCCESS!</b>
</div>
)
}
}/>
</div>
</BrowserRouter>
);
}
}
export default withRouter(App);
withRouter
You can get access to the history object’s properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
As your error says, you cannot connect a Component to a Router outside of a Router. And your are trying to connecter yhe Component which renders the Router, to the Router
So You need to do something like this
// Main.js
export default class Main extends PureComponent {
render (
<BrowserRouter>
<App />
</BrowserRouter>
)
}
// App.js
class App extends PureComponent {
render (
<div className="app">
...
</div>
)
}
export default withRouter(App)
How do you get the value of a <tr> onClick and move to the next view/component using that value passed into the url? For example, the user is on /contacts and the <tr value="123456"> onClick how do you goto /contacts/123456? Expected url contacts/:id
react-router-dom routes
<Route exact path="/contacts" component={Contacts} />
<Route exact path="/contacts/:id" component={ContactDetails} />
<Route exact path="/contacts/new" component={ContactNew} />
React Component
import React, { Component, Fragment } from "react";
import { connect } from "react-redux";
import { fetchContacts } from "../../actions";
class ContactList extends Component {
componentDidMount() {
this.props.fetchContacts();
}
renderContacts() {
return this.props.contacts.reverse().map(contact => {
return (
<tr
key={contact._id}
value={contact._id}
onClick={...} // ???
onClick={() => {window.location.href = `contacts/${contact._id}`}} // works but better way?
>
<td>{contact.firstname}</td>
<td>{contact.lastname}</td>
<td>{contact.mobile}</td>
<td>{contact.email}</td>
</tr>
)
})
}
render() {
return (
<Fragment>
{this.renderContacts()}
</Fragment>
);
}
}
function mapStateToProps({ contacts }) {
return { contacts };
}
export default connect(mapStateToProps, { fetchContacts })(ContactList);
You can pass the following arrow function to onClick:
import history from './history.js'
()=> history.push(`contact/${contact._id}`)
You will need to npm i history and create a history.js file:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
Then import it into your index.js and configure as:
import history from './history.js'
import {Router} from 'react-router-dom'
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('root')
)
Note: the path to your history.js will vary depending on your folder structure
I am trying to implement redux in this component but I get the following error how could I do this?
the error it shows me is the following:
Uncaught Invariant Violation: Could not find "store" in the context of "Connect(App)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(App) in connect options.
I know it is possible to do it this way but I don't want to inject the store into the component
store.dispatch(doResetStore());
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { Switch, Route, BrowserRouter } from 'react-router-dom';
import environment from '../../commons/enviroment.const';
import Loader from '../loader/Loader';
import {connect} from "react-redux";
import store from '../../store/store';
import { routes as routesConst, context } from '../../commons/routes/routes.const';
import PropTypes from 'prop-types';
import MandateConsulting from '../mandate-consulting/MandateConsulting';
import { doResetStore } from '../../store/config/actions/actions';
class App extends Component {
componentWillMount(){
this.props.doResetStore();
}
render() {
return (
<Provider store={store}>
<BrowserRouter basename={context()}>
<div id={environment.appName} className="ui-kit-reset">
<Loader />
<Switch>
<Route exact path={routesConst.etd} component={MandateConsulting} />
<Route exact path={routesConst.default} component={MandateConsulting} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
}
App.propTypes = {
reset: PropTypes.any,
doResetStore: PropTypes.any,
};
export const mapStateToProps = state => ({
reset: state.config.reset
});
export const mapDispatchToProps = (dispatch) => ({
doResetStore: () => dispatch(doResetStore()),
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
If you want your App component to be connected to the redux store, you need to wrap App component with <Provider>.
For example - It can be done with the parent component of App i.e. index.js:
index.js
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App component
...
render() {
return (
<BrowserRouter basename={context()}>
<div id={environment.appName} className="ui-kit-reset">
<Loader />
<Switch>
<Route exact path={routesConst.etd} component={MandateConsulting} />
<Route exact path={routesConst.default} component={MandateConsulting} />
</Switch>
</div>
</BrowserRouter>
);
}
...
I have a component that is used persistently across my spa. I want it to be aware of my router and the various paths that my spa is on. Is there an easy way to do this, or do I have to bandaid some redux (or something similar) state solution that is always listening to my router changes? Thanks! You can see the below for an example.
index.jsx:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Route, Switch } from 'react-router-dom';
import { history, store } from './redux/store';
import Navigation from './navigation';
const UserReport = () => <h2>User Report</h2>;
const UserPage = () => <h2>User Page</h2>;
const Routes = () => (
<React.Fragment>
<Route component={Navigation} />
<Switch>
<Route exact path="/users/:startDate" component={UserReport} />
<Route exact path="/users/:userId" component={UserPage} />
</Switch>
</React.Fragment>
);
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</Provider>, document.getElementById('app'),
);
navigation.jsx:
import React from 'react';
import { withRouter } from 'react-router-dom';
const Navigation = (props) => {
console.log(props.match.path);
// expected: "/users/:startDate"
// received: "/"
return (
<h2>Navigation</h2>
);
};
export default withRouter(Navigation);
Since the Navigation route doesn't have any path specified, it always matches whatever path you're on but the match.path only shows you the minimum path required to match for the navigation. That's why it's always /.
You can use location.pathname but it gives you the matched value and not the matched path.
const Navigation = props => {
console.log(props.location.pathname);
// prints `/users/1` if you're on https://blah.com/users/1
// prints `/users/hey` if you're on https://blah.com/users/hey
return <h2>Navigation</h2>;
};
Not sure that's what you want but if you expand what exactly you're trying to achieve, maybe I can help more.
Moreover, your second route to path="/users/:userId" overshadows the first route. Meaning there is no way to tell if hey in /users/hey is startDate or userId. You should introduce a separate route like path="/users/page/:userId".
I ended up using this react-router github discussion as my solution.
An example of my implementation:
index.jsx:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Route, Switch } from 'react-router-dom';
import { history, store } from './redux/store';
import Layout from './layout';
const home = () => <h2>Home Page</h2>;
const users = () => <h2>Users</h2>;
const userPage = () => <h2>User Page</h2>;
const layoutRender = component => route => <Layout component={component} route={route} />;
const Routes = () => (
<Switch>
<Route exact path="/" component={layoutRender(home)} />
<Route exact path="/users" component={layoutRender(users)} />
<Route exact path="/users/:id" component={layoutRender(userPage)} />
</Switch>
);
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</Provider>, document.getElementById('app'),
);
layout.jsx:
import React from 'react';
const Layout = (props) => {
const {
component: Component,
route,
} = props;
return (
<div>
<h1>This is the layout</h1>
<Component route={route} />
</div>
);
};
export default Layout;
I'm just doing some basic routing in my react app and I've done it this way before so I'm pretty confused to as why it isn't working now.
The error I am getting says: You should not use <Route> or withRouter() outside a <Router>
I'm sure this is super basic so thanks for baring with me!
import React from 'react'
import { Route } from 'react-router-dom'
import { Link } from 'react-router-dom'
import * as BooksAPI from './BooksAPI'
import BookList from './BookList'
import './App.css'
class BooksApp extends React.Component {
state = {
books: []
}
componentDidMount() {
this.getBooks()
}
getBooks = () => {
BooksAPI.getAll().then(data => {
this.setState({
books: data
})
})
}
render() {
return (
<div className="App">
<Route exact path="/" render={() => (
<BookList
books={this.state.books}
/>
)}/>
</div>
)
}
}
export default BooksApp
You need to setup context provider for react-router
import { BrowserRouter, Route, Link } from 'react-router-dom';
// ....
render() {
return (
<BrowserRouter>
<div className="App">
<Route exact path="/" render={() => (
<BookList
books={this.state.books}
/>
)}/>
</div>
</BrowserRouter>
)
}
Side note - BrowserRouter should be placed at the top level of your application and have only a single child.
I was facing the exact same issue. Turns out that i didn't wrap the App inside BrowserRouter before using the Route in App.js.
Here is how i fixed in index.js.
import {BrowserRouter} from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
document.getElementById('root')
);