React navigation - cannot read property 'push' of undefined - javascript

I have a create profile page and an auth page (where one enters a code sent by text). I'd like to navigate to the auth page, when a profile is created.
I have a component for the create profile page and one for the auth page.
Based on the example here.
Relevant code:
// CreateProfileComponent.js
import React from 'react';
..
import { withRouter } from "react-router";
class CreateProfile extends React.Component {
constructor(props) {
super(props);
}
handleCreateProfile(e) {
e.preventDefault();
if (condition) {
createProfile(...);
this.props.history.push('/auth');
} else {
// re-render
}
}
render() {
return (
// data-testid="create-profile" - workaround (https://kula.blog/posts/test_on_submit_in_react_testing_library/) for
// https://github.com/jsdom/jsdom/issues/1937
<form onSubmit={this.handleCreateProfile.bind(this)} data-testid="create-profile">
...
</form>
);
}
}
export default withRouter(CreateProfile);
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import CreateProfile from './CreateProfileComponent';
import { TokenEntry } from './TokenEntryComponent';
ReactDOM.render(
<Router>
<ProtectedRoute exact path="/" component={ActivityList} />
<Route path="/login" component={CreateProfile.WrappedComponent} />
<Route path="/auth" component={TokenEntry} />
</Router>,
document.getElementById('root')
);
//createprofilecomponent.test.js
import React from 'react';
import {
LocationProvider,
createMemorySource,
createHistory
} from '#reach/router';
import { render, screen, fireEvent } from '#testing-library/react';
import CreateProfile from '../CreateProfileComponent';
const source = createMemorySource('/login');
const history = createHistory(source);
let displayName = null;
let phoneNumber = null;
let legalAgreement = null;
global.window = { location: { pathname: null } };
function Wrapper({children}) {
return <LocationProvider history={history}>{children}</LocationProvider>;
}
beforeEach(() => {
render(
<CreateProfile.WrappedComponent location={'/'} />,
{ wrapper: Wrapper }
);
});
it("navigates to /auth when good data entered", () => {
// setup
fireEvent.submit(screen.getByTestId('create-profile'));
expect(global.window.location.pathname).toEqual('/auth');
});
I'm getting
TypeError: Cannot read property 'push' of undefined
during tests and in Chrome.
What am I missing?

Use the react-router-dom
import { withRouter } from "react-router-dom";

Changed
export default withRouter(CreateProfile);
To
export const CreateProfileWithRouter = withRouter(CreateProfile);
Changed
<Route path="/login" component={CreateProfileWithRouter.WrappedComponent} />
To
<Route path="/login" component={CreateProfileWithRouter} />
Pull request created to include an example in withRouter's documentation.
Example gist

Related

Unable to console.log props using Link

I am trying to make a single web application. Basically, I am trying to use the ReactRouter to display what is passed as a Route Parameter. However, I am unable to do that. To check if somethings wrong, I decided to console.log out this.props.match, still nothing shows up. Could someone explain what the problem is? And a possible get around?
My code is-
import React from 'react';
export default class Post extends React.Component {
state = {
id: null
}
componentDidMount(props) {
console.log(this.props.match);
}
render = () => {
return (<div>Hello WOrld</div>)
}
}
The App.js file:
import React, { Fragment, Component } from 'react';
import Navbar from './components/Navbar';
import Home from './components/Home';
import Contact from './components/Contact';
import About from './components/About'
import Post from './components/Post';
import { BrowserRouter, Route } from 'react-router-dom';
class App extends Component {
render = () => {
return (
<BrowserRouter>
<div className="App">
<Navbar />
<Route exact path="/" component={Home} />
<Route path="/contact" component={Contact} />
<Route path="/about" component={About} />
<Route path="/:post-id" component = {Post} />
</div>
</BrowserRouter>
);
}
}
export default App;
I just ran your code on my end, it looks like the problem is using /:post-id. I changed that to /:pid and it worked. I got the below object when I console log this.props.match
{
"path":"/:pid",
"url":"/1",
"isExact":true,
"params":
{
"pid":"1"
}
}
I hope this helps.
You have to load the component with router
try this
import { withRouter } from 'react-router-dom';
class Post extends React.Component {
state = {
id: null
}
componentDidMount(props) {
console.log(this.props.match);
}
render = () => {
return (<div>Hello WOrld</div>)
}
}
export default withRouter(Post);

Child component is not updating from parent state change

I'm trying to update my child component's photos from the parent components state. For all the other routes, the appropriate function was already invoked once the app was mounted. The component that renders cats, dogs, or computers is PhotoList.js
But now, I want to be able to enter a parameter after search (ex. /search/:id) and run a function called getImages in my Container.js to search for any type of picture from the Flickr API.
I tried using componentDidMount and invoking the getImages function with the match parameter inside of it but it doesn't seem to change the data props that's put into it. Does anyone have any suggestions as to how I can make this?
Here is Container.js
import React, {Component} from 'react';
import Photo from './Photo';
class Container extends Component {
componentDidMount() {
this.props.getImages(this.props.match.id)
}
render() {
return (
<div className="photo-container">
<h2>Results</h2>
<ul>
{this.props.data.map((photo,index)=>
<Photo
farm={photo.farm}
server={photo.server}
id={photo.id}
secret={photo.secret}
key={index}
/>
)}
</ul>
</div>
);
}
}
export default Container
Here is PhotoList.js
import React, {Component} from 'react';
import Photo from './Photo';
import NoResults from './NoResults';
class PhotoList extends Component {
render() {
return (
<div className="photo-container">
<h2>Results</h2>
<ul>
{this.props.data.map((photo,index)=>
<Photo
farm={photo.farm}
server={photo.server}
id={photo.id}
secret={photo.secret}
key={index}
/>
)}
</ul>
</div>
);
}
}
export default PhotoList;
Here is App.js
import React, {Component} from 'react';
import {
BrowserRouter,
Route,
Switch,
Redirect
} from 'react-router-dom';
import Search from './Search';
import Nav from './Nav';
import '../index.css';
import axios from 'axios';
import apiKey from './Config';
import NotFound from './NotFound';
import PhotoList from './PhotoList';
import NoResults from './NoResults';
import Container from './Container';
class App extends Component {
state= {
cats: [],
dogs: [],
computers: [],
searchResult: [],
loading: true
}
componentDidMount() {
this.getCats()
this.getDogs()
this.getComputers()
}
getCats=(query='cats')=> {
axios.get(`https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&per_page=24&page=1&format=json&nojsoncallback=1`)
.then(res=> {
const cats=res.data.photos.photo
this.setState({cats})
}).catch((error)=> {
console.log("There was an error parsing your data", error);
})
}
getDogs=(query='dogs')=> {
axios.get(`https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&per_page=24&page=1&format=json&nojsoncallback=1`)
.then(res=> {
const dogs=res.data.photos.photo
this.setState({dogs})
}).catch((error)=> {
console.log("There was an error parsing your data", error);
})
}
getComputers=(query='computers')=> {
axios.get(`https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&per_page=24&page=1&format=json&nojsoncallback=1`)
.then(res=> {
const computers=res.data.photos.photo
this.setState({computers});
}).catch((error)=> {
console.log("There was an error parsing your data", error);
})
}
getImages=(query)=> {
axios.get(`https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&per_page=24&page=1&format=json&nojsoncallback=1`)
.then (res=> {
const searchResult=res.data.photos.photo
this.setState({searchResult});
}).catch((error)=> {
console.log("There was an error parsing your data", error);
})
}
render() {
return (
<div className="container">
<Search getImages={this.getImages}/>
<Nav />
<Switch>
<Route exact path="/" render={()=> <Redirect to={'/cats'} />} />
<Route path='/cats' render={()=> <PhotoList data={this.state.cats}/>} />
<Route path='/dogs' render={()=> <PhotoList data={this.state.dogs} />} />
<Route exact path='/computers' render={()=> <PhotoList data={this.state.computers} />} />
<Route path='/search/:id' render={(props)=> <Container {...props} getImages={this.getImages} data={this.state.searchResult} />} />
<Route component={NotFound}/>
</Switch>
</div>
)
}
}
export default App;
Assuming your are using react-router-dom 4 and above.
Try
import React, { Component } from "react";
import { withRouter } from "react-router-dom"; //<-- import this
import Photo from "./Photo";
class Container extends Component {
componentDidMount() {
// Your this.props.match.id is likely undefined
this.props.getImages(this.props.match.params.id); // <-- Change here
}
...
}
export default withRouter(Container); // <-- Change this

Failed prop type The prop is marked as required but its value is `undefined`

I'm using react v4, and I'm trying to list an array in the page "treinamentos", but I'm getting an error as I load the page:
Failed prop type: The prop treinamentos is marked as required in
Treinamentos, but its value is undefined.
What am I missing here? is it because of the version I'm using?
Treinamentos:
import React from 'react';
import TreinamentosList from './TreinamentosList';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
class Treinamentos extends React.Component {
render() {
return (
<div>
Treinamentos
<TreinamentosList treinamentos={this.props.treinamentos} />
</div>
);
}
}
Treinamentos.propTypes = {
treinamentos: PropTypes.array.isRequired
}
function mapStateToProps(state) {
return {
treinamentos: state.treinamentos
}
}
export default connect(mapStateToProps)(Treinamentos);
TreinamentosList:
import React from 'react';
import PropTypes from 'prop-types';
export default function TreinamentosList({ treinamentos }) {
const emptyMessage = (
<p>Adicione um treinamento</p>
);
const treinamentosList = (
<p>treinamentos list</p>
);
return (
<div>
{treinamentos.length === 0 ? emptyMessage : treinamentosList}
</div>
);
}
TreinamentosList.propTypes = {
treinamentos: PropTypes.array.isRequired
}
AppRouter:
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Greetings from './components/Greetings';
import SignUp from './components/singup/SignUp';
import NoMatch from './components/NoMatch';
import Treinamentos from './components/Treinamentos';
import NavigationBar from './components/NavigationBar';
const AppRouter = () => (
<Router>
<div className="container">
<NavigationBar />
<Switch>
<Route path="/" component={Greetings} exact={true}/>
<Route path="/signup" component={SignUp}/>
<Route path="/treinamentos" component={Treinamentos}/>
<Route component={NoMatch}/>
</Switch>
</div>
</Router>
);
export default AppRouter;
You definitely are receiving a null object when you request the state from treinamentos reducer, you should check your reducer, return treinamentos as an empty array as a initial state (or whatever your business logic requires).

React router 4 nesting routes alternative technique

On my attempt to do nested routes, I've failed to have the child components to mount when the route changes through Link or history.push; but if declaring the routes directly in the root.js file, it works. So, ideally I'd like to keep as much routes configuration as possible in the root/routes.js file and not all over the App (I'm iterating over the root/routes.js object instead to do this automatically; I mean... trying)
To break it down logically (it's a bit abstract, but check the code below afterwards please):
- There's a `root/routes.js` that has all the routes configuration (parents, nested components, etc)
- The `root.js` defines the `<Route...>` where the attribute `component` is the return value of a function that passes the `routes` configuration to its `routes` component prop
- the main wrapper iterates over the component prop `routes` and defines `child` routes automatically...I mean...I'm trying...
Why would I want to do this? The way my brain works and why not? Was possible before react router 4
<MyAppWrapper>
<CommonNavigationBar />
<Main>
----- dynamic / changes by route etc -----
</Main>
<Footer />
</MyAppWrapper>
I wonder where my attempt is failing?
// Working version
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
import App from '../app/containers/app'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<Route path='/' component={App} />
</BrowserRouter>
</Provider>
)
}
export default Root
For the previous example, the App component as nested , bellow I'm trying to do that dynamically..and it fails for some reason! It should be exactly the same though...there must be a typoe somewhere...
like,
import React, { Component } from 'react'
import { isBrowser } from 'reactatouille'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { withRouter, Route } from 'react-router'
import Navbar from '../navbar'
import JourneySelector from '../journeySelector'
import reservationFinder from '../../../reservationFinder'
// include the stylesheet entry-point
isBrowser() && require('../../../../sass/app.scss')
class App extends Component {
constructor (props) {
super(props)
this.state = {
init: true
}
}
render () {
return (
<div className={'app' + ' ' + (!this.state.init && 'uninitialised')}>
<Navbar />
<main>
<Route exact path='/' component={JourneySelector} />
<Route exact path='/reservation-finder' component={reservationFinder.containers.App} />
</main>
</div>
)
}
}
// export default App
function mapStateToProps (state, ownProps) {
return {
// example: state.example
}
}
function matchDispatchToProps (dispatch) {
return bindActionCreators({
// replay: replay
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(withRouter(App))
While my technique fails (all I'm trying to do is iterate the root/routes children routes to generate these ):
// root/routes.js
import app from '../app'
import reservationFinder from '../reservationFinder'
const rootRoutes = [
{
path: '/',
component: app.containers.App,
exact: true,
routes: [{
path: '/',
exact: true,
component: app.containers.JourneySelector
}, {
path: '/reservation-finder',
component: reservationFinder.containers.App
}]
}
]
export default rootRoutes
The root js file. You see the setRoute fn returns a new component, where the children routes is passed as a props? I believed this would work:
// root.js
import React from 'react'
import { Route, Switch } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
const setRoute = (route) => {
const MyComponent = route.component
return <Route key={route.path} exact={route.exact || false} component={() => (<MyComponent routes={route.routes} />)} />
}
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
{ rootRoutes.map(route => setRoute(route)) }
</BrowserRouter>
</Provider>
)
}
export default Root
the main app that I want to use as a wrapper:
// main app
import React, { Component } from 'react'
import { isBrowser } from 'reactatouille'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { withRouter, Route } from 'react-router'
import Navbar from '../navbar'
// include the stylesheet entry-point
isBrowser() && require('../../../../sass/app.scss')
class App extends Component {
constructor (props) {
super(props)
this.state = {
init: true
}
}
render () {
return (
<div className={'app' + ' ' + (!this.state.init && 'uninitialised')}>
<Navbar />
<main>
{ Array.isArray(this.props.routes) && this.props.routes.map(route => <Route key={route.path} {...route} />) }
</main>
</div>
)
}
}
// export default App
function mapStateToProps (state, ownProps) {
return {
// example: state.example
}
}
function matchDispatchToProps (dispatch) {
return bindActionCreators({
// replay: replay
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(withRouter(App))
I understand I MIGHT be able to achieve something similar, like?!
// root
import React from 'react'
import { Route, Switch } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
import MyAppWrapper from 'xxx/MyAppWrapper'
const setRoute = (route) => {
const MyComponent = route.component
return <Route key={route.path} exact={route.exact || false} component={() => (<MyComponent routes={route.routes} />)} />
}
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<MyAppWrapper>
<Route path='x' component={x} />
<Route path='y' component={y} />
</MyAppWrapper>
</BrowserRouter>
</Provider>
)
}
export default Root
Notes: During testing, I've noticed that it worked server-side? I mean, I may have missed something, and I didn't save my work. Also, when it fails, the previous component (the default) is still mounted and does not unmount
I even tried (without sucess...I wonder if this is a bug):
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import App from '../app/containers/app'
import rootRoutes from './routes'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<Route path='/' render={() => (
<App />
)} />
</BrowserRouter>
</Provider>
)
}
export default Root
Ok, I think this is a bug so reported ( https://github.com/ReactTraining/react-router/issues/5190 ), you can find the live example here ( https://codepen.io/helderoliveira/pen/rmXdgd ), click topic. Maybe what I'm trying to do is not supported, but instead of blank we should get an error message.
Ok, I found the typo. The solution is to use render and pass the routerProps + any other props you desire through Object.assign and the spread operator!
// root.js
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import App from '../app/containers/app'
import rootRoutes from './routes'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<Route path='/' render={routeProps => <App {...Object.assign({}, routeProps, { routes: rootRoutes[0].routes })} />} />
</BrowserRouter>
</Provider>
)
}
export default Root
And the main app wrapper:
class App extends Component {
render () {
return (
<div className={'app kiosk' + ' ' + (!this.state.init && 'uninitialised')}>
<Navbar />
<main>
{ Array.isArray(this.props.routes) && this.props.routes.map(route => <Route key={route.path} exact={route.exact} path={route.path} component={route.component} />) }
</main>
</div>
)
}
}
export default App
the routes file:
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
const setRoute = (route) => {
const MyComponent = route.component
return <Route key={route.path} path={route.path} render={routeProps => <MyComponent {...Object.assign({}, routeProps, { routes: rootRoutes[0].routes })} />} />
}
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<div>
{ rootRoutes.map(route => setRoute(route)) }
</div>
</BrowserRouter>
</Provider>
)
}
export default Root

Warning: Failed context type: The context `store.isRequired` is marked as required in `Routing`, but its value is `undefined`

I'm having an issue with react-redux Provider:
I moved our router out of our index.js to a routing.js file. I used a Provider to give my Routing component knowledge of the store but I'm getting a strange warning before the execution reaches the render method :
Warning: Failed context type: The context `store.isRequired` is marked as required in `Routing`, but its value is `undefined`.
This happens even if I get rid of the .isRequired in Routing.contextTypes. Moreover if I put a debugger statement at the beginning of the render function, it appears that this.contextisn't undefined and my app is working like a charm. I'm wondering if I did something wrong or if it could possibly be a bug of react-redux Provider or something.
Here is my code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { setLanguage } from 'redux-i18n';
import I18n from 'redux-i18n/immutable';
import configureStore from './store/configure_store';
import { Routing } from './routing';
import translations from './translations';
import '../scss/index.scss';
const store = configureStore();
store.dispatch(setLanguage(navigator.language));
ReactDOM.render(
<Provider store={ store }>
<I18n translations={ translations }>
<Routing />
</I18n>
</Provider>,
document.getElementById("root")
);
routing.js (simplified for reading purpose)
import React from 'react';
import { Route, IndexRedirect, IndexRoute, Router, browserHistory } from 'react-router';
import AppContainer from './components/App/App';
import Base from './components/Root/Base';
import BaseInProject from './components/App/BaseInProject';
import { ProjectListContainer } from './components/App/Projects/ProjectsList';
export class Routing extends React.Component {
render() {
const { store } = this.context;
const beforeAppRouteEnter = (nextState, replace, callback) => {
// This function needs to store.dispatch some stuff
};
// Some other router functions
const routes = (
<Route path='/' component={ Base }>
<Route component={ AppContainer } onEnter={ beforeAppRouteEnter }>
<IndexRoute component={ ProjectListContainer } />
// Some other routes
<IndexRedirect to='media' />
</Route>
</Route>
</Route>
);
return (
<Router history={ browserHistory }>{ routes }</Router>
);
}
}
Routing.contextTypes = {
store: React.PropTypes.shape(React.PropTypes.any)
};
Thanks!

Categories

Resources