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>
);
Related
Im building this app for a shop, I have the Login already working but I want that when Login in app, it shows the Sidebar, and when choosing an option, the Sidebar may not dissapear only the content part should change, but it doesn't, heres what I have:
App.js
import React from 'react';
import Login from './components/Login'
import Dashboard from './components/Dashboard';
import Administrar from './components/Productos/Administrar';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
function App() {
return (
<React.Fragment>
<Router>
<Switch>
<Route path="/" exact render={ props => (<Login {...props} />)}></Route>
<Route path="/dashboard" exact render={ props => (<Dashboard {...props} />)}></Route>
</Switch>
</Router>
</React.Fragment>
);
}
export default App;
Dashboard.jsx
import React, { useState, useEffect} from 'react';
import Sidebar from '../template/Sidebar';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import axios from 'axios';
import {ApiUrl} from '../services/apirest'
import Administrar from './Productos/Administrar'
const SideAndNavbar = () => {
return (
<React.Fragment>
<Sidebar/>
<Router>
<Switch>
<Route path="/productos/administrar" exact component={ props => (<Administrar {...props} />)}></Route>
</Switch>
</Router>
</React.Fragment>
)
}
const Relog = () => {
return (
<div>
<h1>Relogeate pá</h1>
</div>
)
}
export default function Dashboard() {
const [user, setUser] = useState(null)
useEffect(() => {
obtenerUsuario()
}, [])
const obtenerUsuario = async () => {
let url = ApiUrl + "/usuario";
await axios.get(url)
.then(response => {
setUser(response.data.user)
}).catch(error => {
console.log(error)
})
}
return (
(user ? <SideAndNavbar/>: <Relog/>)
);
}
Login.jsx
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css'
import toast, {Toaster} from 'react-hot-toast'
import logo from '../assets/img/img-01.png'
import axios from 'axios'
import {ApiUrl} from '../services/apirest'
class Login extends Component {
// eslint-disable-next-line
constructor(props){
super(props);
}
state = {
form:{
"email": "",
"password": ""
},
}
manejadorChange = async(e) =>{
await this.setState({
form: {
...this.state.form,
[e.target.name]: e.target.value
}
})
}
manejadorBoton = () => {
let url = ApiUrl + "/auth/logearse";
axios.post(url, this.state.form)
.then(response => {
if(response.data.status === "OK"){
localStorage.setItem("token", response.data.token);
/*
this.props.history.push({
pathname: '/dashboard',
state: response.data.user
})
*/
this.props.history.push('/dashboard');
}else{
toast.error(response.data.message);
}
}).catch(error => {
console.log(error);
toast.error("Error al conectarse con el servidor");
})
}
manejadorSubmit(e){
e.preventDefault();
}
render() {
return (
<React.Fragment>
<Toaster position="top-center" reverseOrder={false}/>
<div className="limiter">
<div className="container-login100">
<div className="wrap-login100">
<div className="login100-pic">
<img src={logo} alt="Imagen"/>
</div>
<form className="login100-form validate-form" onSubmit={this.manejadorSubmit}>
<span className="login100-form-title">
Ingreso de Usuario
</span>
<div className="wrap-input100 validate-input" data-validate = "Valid email is required: ex#abc.xyz">
<input className="input100" type="text" name="email" placeholder="Email" onChange={this.manejadorChange}/>
<span className="focus-input100"></span>
<span className="symbol-input100">
<i className="fa fa-envelope" aria-hidden="true"></i>
</span>
</div>
<div className="wrap-input100 validate-input" data-validate = "Password is required">
<input className="input100" type="password" name="password" placeholder="Password" onChange={this.manejadorChange}/>
<span className="focus-input100"></span>
<span className="symbol-input100">
<i className="fa fa-lock" aria-hidden="true"></i>
</span>
</div>
<div className="container-login100-form-btn">
<button className="login100-form-btn" onClick={this.manejadorBoton}>
Ingresar
</button>
</div>
<div className="text-center p-t-56">
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default Login;
What can I do? Here is some pics:
enter image description here
Looks like you are trying to achieve some heavy Routing without the help of any state management, There are few anti patterns in your current setup. I have a few suggestions that will help you achieve what you are trying to implement.
Firstly Your application routing is setup in a ambiguous way. If you see the routing implementation looks like a nested Routing setup. but the URl you are used is /productos/administrar this route in terms of relativity is rendered from the / base URL.
Your Home(Entry point of the application APP.js) is setup with a parent Router, The router now reads your URL and Tries to render a component. but sees that in the parent Route there is no such URL.
Now if you see carefully your dashboard component is setup to render <SideNav>, to render the sidenav on the /productos/administrar route you should firstly go through the dashboard component.
in your current setup the dashboard component is missed and directly the Admin component is rendered at the root of the router.
I would want you to follow the Layout Pattern where you can stuff the sidenav and topnav (if you have any) along with a main section which render the childrens passed to the component, and on each route call this Layout Component with children props.
this way you ensure the Layout is visible on every route. But that's a long way . If you want a quick fix is to implement proper nested Routing using useRouterMatch() to pass the current route down the component tree. Now change the dashboard component to something like this
const SideAndNavbar = ({match}) => {
return (
<React.Fragment>
// Make sure you user the match props in Sidebar to append he correct URl for nesting to the Link tag , for example (<Link to={`${match.url}/productos/administrar`}>GO to Admin</Link>)
<Sidebar match={match}/>
<Router>
<Switch>
<Route path={`${match.path}/productos/administrar`} exact component={ props => (<Administrar {...props} />)}></Route>
</Switch>
</Router>
</React.Fragment>
)
}
const Relog = () => {
return (
<div>
<h1>Relogeate pá</h1>
</div>
)
}
export default function Dashboard() {
const [user, setUser] = useState(null)
let match = useRouteMatch();
useEffect(() => {
obtenerUsuario()
}, [])
const obtenerUsuario = async () => {
let url = ApiUrl + "/usuario";
await axios.get(url)
.then(response => {
setUser(response.data.user)
}).catch(error => {
console.log(error)
})
}
return (
(user ? <SideAndNavbar match={match} />: <Relog/>)
);
}
For more info on this topic see the official documentation of React Router
having some trouble rendering props in a component for a project using Reactjs. The information is showing in props in the react dev tools, yet I am unable to render them on the browser. When console logging, there is no value showing...
I'm wondering if I need to dig deeper into the api in order to grab what I need?
CocktailRecipe.js
````import React, { Component } from 'react'
// import Spinner from '../layout/Spinner'
class CocktailRecipe extends Component {
componentDidMount(){
this.props.getCocktail(this.props.match.params.idDrink);
// console.log(this.props.match.params.idDrink)
}
render() {
const {strDrink} = this.props.cocktailrecipe;
console.log(strDrink);
// console.log(this.props.cocktailrecipe.strDrink);
// const {loading} = this.props.loading;
// if (loading) {
// <Spinner />
// }else{
return (
<div>
<h3>{strDrink}</h3>
<h3>This is the title</h3>
</div>
)
// }
}
}
export default CocktailRecipe````
app.js
````import { Component, Fragment } from 'react';
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import './App.css';
import Navbar from './layout/Navbar';
import CocktailList from './cocktail/CocktailList';
import axios from 'axios';
import Search from './cocktail/Search';
import Alert from './layout/Alert';
import About from './pages/About';
import CocktailRecipe from './cocktail/CocktailRecipe';
class App extends Component {
state={
cocktails: [],
cocktailrecipe:{},
loading: false,
msg:'',
type:''
}
async componentDidMount() {
try {
this.setState({loading: true})
const res = await axios.get('https://www.thecocktaildb.com/api/json/v1/1/search.php?s=');
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
handleSearchCocktails= async (text) => {
try{
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${text}`);
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
// Get cocktail recipe
getCocktail = async (idDrink) => {
try {
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${idDrink}`);
// console.log(res.data.drinks);
this.setState({cocktailrecipe: res.data.drinks.id, loading: false})
} catch(error) {
console.log(error)
}
}
handleClearCocktails= () => {
this.setState({cocktails:[], loading: false})
}
handleSetAlert=(msgfromSearch, typefromSearch)=>{
this.setState({ msg:msgfromSearch, type:typefromSearch })
setTimeout(()=>this.setState({msg:'', type:''}), 5000)
}
render() {
const {cocktails, loading, cocktailrecipe} = this.state;
return (
<Router>
<div className="App">
<Navbar title="COCKTAIL LIBRARY" />
<div className="container">
<Alert msg={this.state.msg} type={this.state.type} />
<Switch>
<Route exact path='/' render={props=>(
<Fragment>
<Search searchCocktails={this.handleSearchCocktails} clearCocktails={this.handleClearCocktails} showClear={this.state.cocktails.length>0?true:false} setAlert={this.handleSetAlert} />
<CocktailList loading={loading} cocktails={cocktails} />
</Fragment>
)} />
<Route exact path='/about' component={About} />
<Route exact path='/cocktailRecipe/:idDrink' render={props => (
<CocktailRecipe {...props} getCocktail={this.getCocktail} cocktailrecipe={cocktailrecipe} loading={loading}/>
)} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default App;````
In your screenshot, props cocktailrecipe is an array of object.
Use array desctructuring instead of object on CocktailRecipe.js
- const {strDrink} = this.props.cocktailrecipe;
+ const [strDrink] = this.props.cocktailrecipe;
So turns out that my wifi connection is part of the problem. And grabbing the wrong object.
In CocktailRecipe.js I added in:
line 22:
const { drinks } = this.props.cocktailrecipe;
and then put into the render():
{drinks && drinks[0].strDrink }
I'm told that this may not be the most elegant or efficient solution, so if anybody has a better way, please let me know.
Hi I have been developing this application using react and react-router-dom The Page component is wrapped by a HOC that imports a contentservice to access a rest api.
My navigation is in the App component. The relevant part is the
<Link to="/page/123">About Page</Link>
and
<Link to="/page/456">Contact Page</Link>
When these links are clicked the page doesn't redraw as i expected. First time i go to 'About Page' it's all good. Then when i click to go to 'Contact Page' nothing changes. Then i click on the 'About Page' again and the 'Contact Page' shows.
In all the cases above the browser address bar shows the right path and if i refresh the page i go to the right page.
Here is my navigation page:
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { connect } from "react-redux";
import Home from "./Home";
import Admin from "./Admin";
import Members from "./Members";
import Login from "./Login";
import Page from "./Page";
import PrivateRoute from "./PrivateRoute";
import "./App.css";
class App extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home Page</Link>
</li>
<li>
<Link to="/page/123">About Page</Link>
</li>
<li>
<Link to="/page/456">Contact Page</Link>
</li>
<li>
<Link to="/members">Members</Link>
</li>
<li>
<Link to="/admin">Admin</Link>
</li>
</ul>
</div>
<Switch>
<Route path="/login" component={Login} />
<Route path="/page/:id" component={Page} />
<Route exact path="/" component={Home} />
<PrivateRoute path="/members">
<Members />
</PrivateRoute>
<PrivateRoute path="/admin">
<Admin />
</PrivateRoute>
</Switch>
</Router>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
export default connect(mapStateToProps, null)(App);
This is my page component:
import React, { Component } from "react";
import WithBackend from "./WithBackend";
class Page extends Component {
constructor(props) {
super(props);
this.resource = "/pages/";
this.state = { model: null };
}
render() {
if (this.state.model != null) {
return (
<div className="container">
<div className="row">
<div className="col-md">
<h1>{this.state.model.title}</h1>
<h2 dangerouslySetInnerHTML={{ __html: this.state.model.body }} />
</div>
</div>
</div>
);
} else {
return (
<div>
<h2>Page id: {this.props.match.params.id}</h2>
</div>
);
}
}
componentDidMount() {
this.props
.getEntity(this.resource, this.props.match.params.id)
.then((model) => this.setState({ model }));
}
componentDidUpdate(nextProps) {
if (nextProps.match.params.id !== this.props.match.params.id) {
this.props
.getEntity(this.resource, nextProps.match.params.id)
.then((data) => {
this.setState({ model: data });
});
}
}
}
export default WithBackend(Page);
This is the Withbackend HOC:
import React from "react";
import ContentService from "./ContentService";
const WithBackend = (WrappedComponent) => {
class HOC extends React.Component {
constructor() {
super();
this.contentService = new ContentService();
this.getEntity = this.getEntity.bind(this);
this.getEntities = this.getEntities.bind(this);
}
getEntity(resource, id) {
return this.contentService
.getEntity(resource, id)
.then((response) => response.json())
.catch((e) => {
console.log(e);
});
}
getEntities(resource) {
return this.contentService
.getEntities(resource)
.then((response) => response.json())
.catch((e) => {
console.log(e);
});
}
render() {
return (
<WrappedComponent
getEntity={this.getEntity}
getEntities={this.getEntities}
{...this.props}
/>
);
}
}
return HOC;
};
export default WithBackend;
And the content service:
class ContentService {
baseUrl = "http://localhost:8080";
getEntity(resource, id) {
const path = this.baseUrl + resource + id;
const fetchPromise = fetch(path, {
method: "GET",
});
return Promise.resolve(fetchPromise);
}
getEntities(resource) {
const fetchPromise = fetch(this.baseUrl + resource, {
method: "GET",
});
return Promise.resolve(fetchPromise);
}
}
export default ContentService;
Has anyone got any ideas why this is happening? I am not sure if it has anything to do with the Page component being wrapped by HOC but just thought it is worth mentioning.
Thank you.
Issue
The componentDidUpdate lifecycle method receives the previous props, state, and snapshot values, not the next ones.
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
By sending the "previous" props' match param id you were a "cycle" behind.
Solution
Use the current id value from props.
componentDidUpdate(prevProps) {
if (prevProps.match.params.id !== this.props.match.params.id) {
this.props
.getEntity(this.resource, this.props.match.params.id)
.then((data) => {
this.setState({ model: data });
});
}
}
I managed to to use history.push in an onClick as I want to pass the user id to a Profile page component but the uuid params in the URL is undefined and I don't know why. I'm really stuck at this part.
I also want to pass all the other props which I get from the Random User Generator API as I'm doing in CardList to be able to build the profile page.
Would definitely appreciate anyone’s help.
import React, { Fragment } from "react";
import { withRouter } from "react-router-dom";
const Card = ({ history, firstName, lastName, email, uuid, image, city, country }) => {
return (
<Fragment>
<div className="tc bg-washed-green dib br3 pa3 ma2 dim bw2 shadow-5 pointer">
<img src={image} alt="userImage" onClick={() => history.push(`/profilepage/${uuid}`)} />
<h2>{`${firstName} ${lastName}`}</h2>
<p> {email} </p>
<div>
<span>{`${city}, ${country}`}</span>
</div>
</div>
</Fragment>
);
};
export default withRouter(Card);
import React, { Fragment } from "react";
const ProfilePage = ({ uuid }) => {
return (
<Fragment>
<h1 className="f1">Profile Page: {uuid}</h1>
</Fragment>
);
};
export default ProfilePage;
and this is the Routing in App.js
render() {
const { users, isPending } = this.props;
if (isPending) {
return <h1 className="tc"> Loading... </h1>;
} else {
return (
<div className="tc">
<Switch>
<Route exact path="/homepage" render={() => <CardList users={users} />} />
<Route path="/profilepage/:uuid" component={ProfilePage} />
</Switch>
</div>
);
}
}
}
import React, { Fragment } from "react";
import Card from "./Card";
const CardList = ({ users }) => {
return (
<Fragment>
<h1 className="f1"> IOTA Users </h1>
{users.map((user) => {
return (
<Card
key={user.login.uuid}
image={user.picture.large}
firstName={user.name.first}
lastName={user.name.last}
email={user.email}
city={user.location.city}
country={user.location.country}
/>
);
})}
</Fragment>
);
};
export default CardList;
In your ProfilePage component you can get the uuid like below ways
Approach-1: In this approach you either need to spread all the other props which will be sent from parent or else need to use ...rest param to capture all the other props which you don't want to spread.
import React, { Fragment } from "react";
const ProfilePage = ({ match }) => {
return (
<Fragment>
<h1 className="f1">Profile Page: {match.params.uuid}</h1>
</Fragment>
);
};
export default ProfilePage;
Approach-2: This way you can access other props also
import React, { Fragment } from "react";
const ProfilePage = (props) => {
return (
<Fragment>
<h1 className="f1">Profile Page: {props.match.params.uuid}</h1>
</Fragment>
);
};
export default ProfilePage;
EDIT: Look like you don't send uuid as props. Can you check CardList component?
There is undefined because it's not actually send uuid data as props. You should fetch it from this.props.match.params.uuid
Can you also check is there exist your id in url? If so my method should work.
And from react-router-dom version 5 you can use their hook like useParams. So, you can make your code base more clear
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}
/>