Why is this.props.history undefined despite having used withRouter? - reactjs

I'm trying to do this.props.history.push... in my component, but even after making sure that I'm exporting it using withRouter I still get this error:
Uncaught TypeError: Cannot read property 'push' of undefined
I also made sure that the parent component that's using this is wrapped inside of a ProtectedRoute as well:
// my component:
import React from 'react';
import { withRouter } from 'react-router-dom';
import { Link } from 'react-router-dom';
class UserIndexItem extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.play = this.play.bind(this);
}
handleClick(e) {
if (!e.target.classList.contains("triangle")) {
this.props.history.push(`/playlist/${this.props.playlist.id}`);
}
}
handleTrack(playlist) {
// still going forward one, then back one, and then it plays normally...
if (!playlist.payload.tracks) return;
let tracks = Object.values(playlist.payload.tracks);
let currentTrack = tracks[0];
let nextTrack = tracks[1];
this.props.receiveCurrentTrack(currentTrack);
this.props.receiveNextTrack(nextTrack);
this.props.receiveTitle(currentTrack.title);
this.props.receiveArtist(currentTrack.artist);
this.props.receiveAlbumId(currentTrack.album_id);
}
play() {
const { playlist } = this.props;
this.props.requestSinglePlaylist(this.props.playlist.id).then(playlist => this.handleTrack(playlist));
this.props.receivePlaylistId(playlist.id);
}
render() {
const { playlist } = this.props;
return (
<li>
<div className="playlist-image" onClick={ this.handleClick }>
<div className="play-button" onClick={ this.play }>
<div className="triangle right"></div>
<div className="circle"></div>
</div>
<div className="overlay"></div>
<img src={playlist.photo_url} alt="Playlist thumbnail" onClick={ this.handleClick }/>
</div>
<div className="playlist-name">
<Link to={`/playlist/${playlist.id}`}>{ playlist.title}</Link>
</div>
</li>
);
}
}
export default withRouter(UserIndexItem);
// my parent component:
import React from 'react';
import UserIndexItem from './user_index_item';
import { selectTracksFromPlaylist } from '../../reducers/selectors';
class UserIndex extends React.Component {
constructor(props) {
super(props);
}
render() {
const { user, playlists } = this.props;
return(
<div className="user-index-container">
<div className="header">
<h1>{ user.username }</h1>
<h2>Public Playlists</h2>
</div>
<div className="playlists">
<ul>
{ playlists.map(playlist =>
<UserIndexItem
key={ playlist.id }
playlist={ playlist }
requestSinglePlaylist={ this.props.requestSinglePlaylist }
receiveCurrentTrack={ this.props.receiveCurrentTrack }
receiveNextTrack = { this.props.receiveNextTrack }
receiveTitle={ this.props.receiveTitle }
receiveArtist={ this.props.receiveArtist }
receivePlaylistId={ this.props.receivePlaylistId }
receiveAlbumId={ this.props.receiveAlbumId }
/>)
}
</ul>
</div>
</div>
);
}
}
export default UserIndex;
// my route that's using the parent component:
<ProtectedRoute path="/users/:userId" component={UserIndex} />
// my ProtectedRoute implementation:
const Protected = ({ component: Component, path, loggedIn, exact }) => (
<Route path={ path } exact={ exact } render={ (props) => (
loggedIn ? (
<Component {...props} />
) : (
<Redirect to="/welcome" />
)
) }/>
);

You can try like this:
<ProtectedRoute path="/users/:userId" component={props => <UserIndex {...props} />} />
Please let me know if this is working.
Thanks.

I think that {...props} need to call inside UserIndexItem as well.
According to my understand inside the App.js you need to pass {...props} to child component otherwise it don't have parent properties
// this ProtectedRoute should change according to your requirement. I just put sample code
const ProtectedRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
? <Component {...props} />
: <Redirect to="/Login"/>
)} />
)
<ProtectedRoute path="/users/:userId" component={UserIndex} />
// my parent component:
<UserIndexItem
key={ playlist.id }
playlist={ playlist }
requestSinglePlaylist={ this.props.requestSinglePlaylist }
receiveCurrentTrack={ this.props.receiveCurrentTrack }
receiveNextTrack = { this.props.receiveNextTrack }
receiveTitle={ this.props.receiveTitle }
receiveArtist={ this.props.receiveArtist }
receivePlaylistId={ this.props.receivePlaylistId }
receiveAlbumId={ this.props.receiveAlbumId }
{...this.props}
/>

Related

How to put a function in route

As a beginner, I'm trying to put my list of "clients" in a function to display in the route/component, is this possible?
App.js
render() {
return (
<div className = "App">
<div>
<AddCliente onAdd = {this.onAdd} />
</div>
<Router>
<div>
<Link to = "/">Cli</Link>
<Route path = "/" exact component = {Adde} />
</div>
</Router>
</div>
);
}
}
I wanna do this:
function Adde() {
return <div>
{
this.state.clientes.map(cliente => {
return (
<ClienteItem
key = {cliente.nome}
{...cliente}
onDelete = {this.onDelete}
/>
);
})
}
</div>;
}
export default Add;
Error:
TypeError: Cannot read property 'state' of undefined
You can do something like this in your component.
import React, { Component } from 'react';
class componentName extends Component {
constructor(props) {
super(props);
this.state = {
clients:[
{
name:'sdfd',
title:'sdfd'
}
]
};
}
Adde=()=>{
return(
<div>
{
this.state.clientes.map(cliente => {
return (
<ClienteItem
key = {cliente.nome}
{...cliente}
onDelete = {this.onDelete}
/>
);
})
}
</div>
);
}
render() {
return (
<div className = "App">
<div>
<AddCliente onAdd = {this.onAdd} />
</div>
<Router>
<div>
<Link to = "/">Cli</Link>
<Route path = "/" exact component = {Adde} />
</div>
</Router>
</div>
);
}
}
export default componentName;

Undefined prop value in child component

I'm attempting to read an array item in a child component via props. Logging the array in the child component works. But if I try to access a property of one of the array items by indexing it with the :id from match.params, it tells me that I can't access a property of 'undefined'.
Any guidance would be greatly appreciated.
tours.js
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
// Page Imports
import Summary from "../pages/summary";
import Details from "../pages/details";
// Component Imports
import Homebutton from "../components/homebutton";
class Tours extends Component {
state = {
tours: []
};
componentDidMount() {
window.scrollTo(0, 0);
fetch("/tours")
.then(res => res.json())
.then(res => this.setState({ tours: res }));
}
render() {
const tours = this.state.tours;
return (
<section className="tours-page">
<div className="center-box">
<h2>Tours</h2>
</div>
<Switch>
<Route
exact
path={this.props.match.url}
render={props => <Summary {...props} tours={tours} />}
/>
<Route
path={this.props.match.url + "/:id"}
render={props => <Details {...props} tours={tours} />}
/>
</Switch>
<Homebutton />
</section>
);
}
}
export default Tours;
details.js
import React from "react";
const Details = ({
tours,
match: {
params: { id }
}
}) => (
<section className="details">
<h2>{tours[id]["name"]}</h2>
</section>
);
export default Details;
To be sure that tours[id] is not undefined you should check it first
<section className="details">
<h2>{tours[id] && tours[id]["name"]}</h2>
</section>
As componentDidMountalways gets called after first render, you must validate your props to avoid app crashes:
const Details = ({
tours,
match: {
params: { id }
}
}) => (
<section className="details">
<h2>{tours.length && tours[id]["name"]}</h2>
</section>
);

Is it possible to realize communication between independent components in ReactJS?

I have two components. These components are located on different routes. 'CreateItem' component gives me possibility to create new items. I store new items to array. Array will include new created items. I want send this modified array to component 'Main' where I will iterate those items and display them as list.
Here is my code:
1) index.js file:
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom'
import {Main} from "./components/Main"
import {CreateItem} from "./components/CreateItem"
import {CurrentItem} from "./components/CurrentItem"
render(
<BrowserRouter>
<div>
<Route exact path="/" component={Main}/>
<Route path="/create_item" component={CreateItem}/>
<Route path="/item" component={CurrentItem}/>
</div>
</BrowserRouter>,
document.getElementById('app')
);
2) Main.js
import React from 'react';
import { withRouter } from 'react-router-dom';
import { Route, browserHistory } from 'react-router-dom';
export class Main extends React.Component {
render(){
const ToCreateItemPageButton = () => (
<Route render={({ history}) => (
<button type='button' onClick={() => { history.push('/create_item') }}>Move to create item page!</button>
)}
/>
)
return (
<div>
<h1>Main Page</h1>
<ToCreateItemPageButton/>
</div>
);
}
}
3) CreateItem.js
import React from 'react';
import { Route, browserHistory } from 'react-router-dom';
export class CreateItem extends React.Component {
constructor(props) {
super(props);
this.state = {
mainArray: [],
item: {},
item_id: 0,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({item: {item_id: this.state.item_id,
name:event.target.value}});
}
handleSubmit(event) {
if (this.state.item.name.length > 0) {
this.state.mainArray.push(this.state.item);
this.state.item_id = this.state.item_id + 1;
let data = JSON.stringify(this.state.mainArray);
localStorage.setItem('mainObject', data);
this.setState(
{mainArray : this.state.mainArray,
item_id : this.state.item_id,}
);
event.preventDefault();
}
}
render(){
const ToMainPageButton = () => (
<Route render={({ history}) => (
<button type='button' onClick={() => { history.push('/') }}>Move to main page!</button>
)}
/>
)
return (
<div>
<h1>Create new item</h1>
<ToMainPageButton/>
<form onSubmit={this.handleSubmit}>
<label>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
So all I want is to have possibility to transfer my mainArray from 'CreateItem' component to 'Main' component.
You could redirect and send data like that:
this.props.history.push({
pathname: '/target-path',
data: [/*your data*/]
});
and receive it on the target component so:
const { data } = this.props.location;
Short answer - Yes it's possible using container component like in fiddle example.
So the idea is to keep you array of items in a container state and pass it to "iterated" component as well as a callback for handling incoming item.
// container component
class Container extends React.Component {
constructor(props){
super(props);
this.state = {
array: ['Hello', 'Stack', 'Overflow']
}
this.handleOnAdd = this.handleOnAdd.bind(this)
}
handleOnAdd(item){
this.setState({
array: [...this.state.array, item]
})
}
render() {
return (
<div>
// pass shared props to "display" component
<ChildOneDisplay items={this.state.array} />
// pass a callback to CreateItem component
<ChildTwoAdd onAdd={this.handleOnAdd} />
</div>
);
}
}
// display component
class ChildTwoAdd extends React.Component{
constructor(props){
...
this.handleAdd = this.handleAdd.bind(this)
}
handleAdd(){
this.props.onAdd(this.state.item);
...
}
render(){
return(
<div>
<input
name="item"
type="text"
onChange={this.handleChange}
value={this.state.item}
/>
<button onClick={this.handleAdd}>Add Me</button>
</div>
)
}
}
So all you need is to wrap your two routes with a container component and pass props to both of them as i did in this example.
// So your container should look like the following one
render(){
return (
<div>
<Route exact path="/" render={() => <Main items={this.state.array}}/>
<Route path="/create_item" render={() => <CreateItem onAdd={this.handleAdd}/>}/>
</div>
)
}
// And render it as the following
<BrowserRouter>
<Container />
<Route path="/item" component={CurrentItem}/>
</BrowserRouter>
Moreover i suggest looking at redux - this is the library for managing your app state.
Thanks!

Pass props from wrapper to one children page

Hello and thank you in advance for your help. I have a problem passing props to components loaded with routes. I have a routes file with a wrapper component that loads the pages regarding the path url. On the wrapper component (Layout) I would like to pass to the children components some props. But as the children components are called with this.props.children I don't know how to pass the props. I tried many things and nothing has worked.
I have the following rotes file:
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import Layout from '../components/pages/Layout.js';
import Search from '../components/pages/Search.js';
import Queue from '../components/pages/Queue.js';
import About from '../components/pages/About.js';
const routes = () =>
<Route path="/" component={Layout}>
<IndexRoute component={Search}></IndexRoute>
<Route path="queue" component={Queue}></Route>
<Route path="about" component={About}></Route>
</Route>
export default routes;
In Layout I have:
import React from "react";
import Footer from "../common/Footer.js";
import Nav from "../common/Nav.js";
import Header from "../common/Header.js";
export default class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
isSongPlaying: false,
playingTrackId: "",
playingList: []
}
}
handleClickTrack(track) {
this.setState({
isSongPlaying: !this.state.isSongPlaying
});
}
renderTrack(i) {
return (
<Player audio_id={id} />
);
}
render() {
const { location } = this.props;
const { history } = this.props;
const { children } = this.props;
return (
<div>
<Header />
<Nav location={location} history={history}/>
<div className="container">
<div className="row">
<div className="col-lg-12">
{this.props.children}
</div>
</div>
<div className="row">
<div className="col-lg-12">
<div className="song-player">
{this.state.isSongPlaying ? this.renderTrack(this.state.playingTrackId) : null}
</div>
</div>
</div>
<Footer/>
</div>
</div>
);
}
}
on {this.props.children} the component is loading my pages components Search, Queue, and About, but i would like add callback props to my Search and Queue components.
On my wrapper Layout component I want to achieve the following:
import React from "react";
import Footer from "../common/Footer.js";
import Nav from "../common/Nav.js";
import Header from "../common/Header.js";
export default class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
isSongPlaying: false,
playingTrackId: "",
playingList: []
}
}
handleClickTrack(track) {
this.setState({
isSongPlaying: !this.state.isSongPlaying
});
}
renderTrack(i) {
return (
<Player audio_id={id} />
);
}
render() {
const { location } = this.props;
const { history } = this.props;
const { children } = this.props;
return (
<div>
<Header />
<Nav location={location} history={history}/>
<div className="container">
<div className="row">
<div className="col-lg-12">
{RENDER SEARCH WITH onClick prop}
{RENDER QUEUE WITH onClick prop}
</div>
</div>
<div className="row">
<div className="col-lg-12">
<div className="song-player">
{this.state.isSongPlaying ? this.renderTrack(this.state.playingTrackId) : null}
</div>
</div>
</div>
<Footer/>
</div>
</div>
);
}
}
I'm using render={() => <Component/>} in my React apps to give my Routes props. Don't know if it's the perfect way. There might be other ways. But it's working! :)
Here's an example of one of your Routes:
<Route exact path="/queue" render={() => <Queue prop={something}/>} />
You can pass the props to child component using childContextTypes static object.Define below context in parent Layout component.
static childContextTypes={
isSongPlaying: React.PropTypes.bool,
playingTrackId:React.PropTypes.string,
playingList: React.PropTypes.array
}
Then populate the value using getChildContext() in Layout class
getChildContext=()=>{
return {
isSongPlaying: false,
playingTrackId:"Any Value to child component that you are going to pass",
playingList: [] //Array with value
}
}
Now you can get the value in child component (About.jsx or Search.jsx) by defining context types like below
static contextTypes={
isSongPlaying: React.PropTypes.bool,
playingTrackId:React.PropTypes.string,
playingList: React.PropTypes.array
}
Now you can access the property value in child component using the context like below
let isPlaying= this.context.isSongPlaying //or
let playingTrackId=this.context.playingTrackId

Change background of parent after Match

I am very new to react...
I have a parent component that represents the background for my webapp. I use Match from react-router to handle when the user navigates to different routes.
The new route renders the PageView component, which I want to have a different background image. So to do this I set the background image of it's parent by passing a function from the parent to he child's props and then calling it in the child from componentDidMount()
This does not work and gives me Maximum call stack exceeded error
Here is my parent component:
class App extends Component {
constructor () {
super()
this.state = {
backgroundImage: '../public/img/initial.jpg'
}
this.changeBackground = this.changeBackground.bind(this)
}
changeBackground (backgroundImage) {
this.setState({
backgroundImage
})
}
render () {
return (
<BrowserRouter>
<Provider store={store}>
<div className='app' style={{backgroundImage: `url(${this.state.backgroundImage})`}}>
<div className='overlay'>
<Match exactly pattern='/' component={() => <Landing />}
/>
<Match
pattern='/healthcare'
component={(props) => <PageView descriptionText='Healthcare Solutions'
backgroundImage='../public/img/5.png' changeParentBackground={this.changeBackground} {...props} />}
/>
<Match
pattern='/officeofthefuture'
component={(props) => <PageView descriptionText='Office of the Future'
backgroundImage='../public/img/1.png' changeParentBackground={this.changeBackground} {...props} />}
/>
</div>
</div>
</Provider>
</BrowserRouter>
)
}
}
And here is my child component PageView
class PageView extends Component {
componentDidMount () {
const { backgroundImage, changeParentBackground } = this.props
changeParentBackground(backgroundImage)
}
render () {
const { descriptionText } = this.props
console.log(this.props.changeParentBackground)
return (
<div className='outerDiv'>
<LeftDesign show />
<RightDesign descriptionText={descriptionText} />
</div>
)
}
}
RightDesign:
class RightDesign extends Component {
render () {
const { descriptionText } = this.props
return (
<div className='rightDiv'>
<div id='bigCircle'>
<div className='bigCircleTextDiv'>
<h1 className='bigCircleText'>{descriptionText}</h1>
</div>
</div>
</div>
)
}
}
LeftDesign
class LeftDesign extends Component {
constructor (props) {
super(props)
const { show } = this.props
this.state = {show}
}
render () {
return (
<div className='leftDivContainer'>
<div className='leftDiv'>
{this.state.show && <WelcomeMsg />}
</div>
</div>
)
}
}
I commented earlier about this sort of solution.
It uses CSS background images and transitions to switch the image based on what the string of the location's pathname's second item is, which would correspond to the first-level routes of your app.
function getBackground(array) {
switch(array[1]) {
case 'healthcare':
return 'www.foo.com/image/1.jpg';
case 'officeofthefuture':
return 'www.foo.com/image/2.jpg';
default:
return 'www.foo.com/image/default.jpg'
}
}
export default class Parent extends Component {
render() {
let { location } = this.props;
// Split the pathname string into an array of sub-paths
let locationArray = location ? location.pathname.split('/') : [];
// Determine the Background URL
let backgroundUrl = getBackground(locationArray);
// Define the Parent Style
const parentStyle = {
transition: 'all 0.2s ease-out',
backgroundImage: "url('"+backgroundUrl+"')",
backgroundPosition: 'center',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat'
};
return <div style={parentStyle}>
{this.props.children}
</div>
}
}

Resources