Function not working on onClick event in React.js - javascript

I am writing a react program where data will be fetched through an API and will be displayed in a table. There are 3 main files App.js, Table.js and Search.js and Button.js.
The data is being displayed, the search is working but delete button is not working.
I have written a function for delete button and I guess something is wrong in that but don't know what.
App.js
import React, { Component } from 'react';
import './App.css';
import Table from './components/Table';
import Search from './components/Search';
//API config
const DEFAULT_QUERY = 'react';
const PATH_BASE = 'https://hn.algolia.com/api/v1';
const PATH_SEARCH = '/search';
const PARAM_SEARCH = 'query=';
const url = `${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${DEFAULT_QUERY}`;
class App extends Component {
constructor(){
super();
//here searchText is set to DEFAULT_QUERY which will return the result for keyword "redux"
//refer line 8 to change
this.state={
searchText:'',
result:''
}
this.onDismiss=this.onDismiss.bind(this);
this.onSearchChange=this.onSearchChange.bind(this);
this.searchStories=this.searchStories.bind(this);
//this.isSearched=this.isSearched.bind(this);
}
//to add a delete button
onDismiss=(id)=>{
//filter out item array and return results with no matched id
const deleteList=this.state.list.filter(item=>item.objectID!==id);
//setting state of list to lastest deleteList
this.setState({
result:deleteList
})
}
//to add a search bar
onSearchChange=(e)=>{
//set state to value in search bar
this.setState({
[e.target.name]:e.target.value
})
}
searchStories=(result)=>{
this.setState({
result
});
}
//after mounting will fetch the api data
componentDidMount(){
fetch(url)
.then(response => response.json())
.then(result => this.searchStories(result));
}
render() {
const {result,searchText}=this.state;
if(!result){
return null;
}
return(
<div className="page">
<div className="interactions">
<Search
searchText={searchText}
onSearchChange={this.onSearchChange}
>
Search
</Search>
</div>
<Table
list={result.hits}
onDismiss={this.onDismiss}
searchText={searchText}/>
</div>
)
}
}
export default App;
Table.js
import React from 'react';
import Button from './Button';
const Table=(props)=>{
const {list,searchText,onDismiss}=props;
return(
<div className="table">
{/*Filter out item title and search title and give away results from item array */}
{list.filter((item)=>{
{/*The includes() method determines whether an array includes a certain value among its entries
, returning true or false as appropriate. */}
return item.title.toLowerCase().includes(searchText.toLowerCase());}).map((item)=>{
return(
<div key={item.objectID} className="table-row">
<span style={{ width: '40%' }}>
<a href={item.url}>{item.title}</a>
</span>
<span style={{ width: '30%' }}>
{item.author}
</span>
<span style={{ width: '10%' }}>
{item.num_comments} comments
</span>
<span style={{ width: '10%' }}>
${item.points} points
</span>
<span style={{ width: '10%' }}>
<Button className="button-inline" onClick={()=>onDismiss(item.objectID)}>delete</Button>
</span>
</div>
)})}
</div>
)
}
export default Table;
Button.js
import React from 'react';
const Button=(props)=>{
const{onclick,className='',children}=props;
return(
<div>
<button onClick={onclick} className={className} >{children}</button>
</div>
)
}
export default Button;

Your button needs to be modified slightly:
<button onClick={onClick} className={className} >{children}</button>
The handler needs to refer to the props passed in which are this.props.onClick, not this.props.onclick (which you had).
The error you are encountering can be fixed by modifying the App.js:
onDismiss = id => {
if (id) {
const deleteList = this.state.list.filter(item => item.objectID !== id);
// setting state of list to lastest deleteList
this.setState({
result:deleteList
})
}
}

In Button component change
const{onclick,className='',children}=props;
to
const{onClick,className='',children}=props;
Also it seems that you have not set list in the state therefore when you try to access this.state.list.filter it will throw an error.
onDismiss=(id)=>{
const deleteList=this.state.result.hits.filter(item=>item.objectID!==id);
this.setState({
result:{...this.state.result,hits:deleteList}
})
}

Related

Why is my functional component's state undefined?

I'm working on a flashcard App, my flashcard component is as below:
import React, { useState } from 'react';
import './Flashcard.css'
import {
Button,
} from '#material-ui/core';
function Flashcard({ data }) {
// // Get an initial card
const [currentCard, setCurrentCard ] = useState(data[0]);
// Create state to track whether the card has been flipped yet
const [flipped, setFlipped] = useState(false);
// When the Flip button is clicked, we display the verso
const handleFlipClick = () => {
setFlipped(true);
}
const handleNextClick = () => {
// Change the current active card
setCurrentCard(data[1]);
// Restore the 'flipped' state to indicate that the new active card hasn't been flipped yet
setFlipped(false);
}
// Get data needed from the server
return (
<div className="flashcard__container">
{/* Card content goes here */}
<div className="flashcard__content">
<p>
{ (flipped) ? currentCard.verso : currentCard.recto }
</p>
</div>
<div className="flashcard__actions">
{/* Display the flip button if the card hasn't been flipped yet */}
{(!flipped) ?
<Button
variant="contained"
color="primary"
onClick={handleFlipClick}
>Flip</Button>
:
<>
<Button onClick={handleNextClick} color='primary' variant='contained'>
Hard
</Button>
<Button onClick={handleNextClick} color='primary' variant='contained'>
Slightly difficult
</Button>
<Button onClick={handleNextClick} color='primary' variant='contained'>
Good
</Button>
<Button onClick={handleNextClick} color='primary' variant='contained'>
Easy
</Button>
</>
}
</div>
</div>
)
}
export default Flashcard
'data' is pass from another component, when I console log it in the Flashcard component, here is what I get:
[
0: {id: "PynaJl43Jphl2xI8fWkn", reviewedOn: Array(2), recto: "To go", verso: "行く"}
1: {id: "mSDdg5ATenvjBYojfy8N", verso: "こんにちは", recto: "Hi", reviewedOn: Array(2)
]
However, this line: (flipped) ? currentCard.verso : currentCard.recto gives me the following error message: "Type Error - Cannot read property 'recto' of undefined".
Indeed, when I try to console log 'currentCard', it says it is undefined. Why is that happening? I don't see any mistake in the way I initialized the state. Thank you!
Here is the parent component:
import React, { useState, useEffect } from 'react';
import FlashCard from './Flashcard';
import mockData from './mockData';
import { getParams, useParams } from 'react-router-dom';
import { db } from './firebase';
import Header from './Header';
function Deck() {
const { id } = useParams();
const [cards, setCards] = useState(); // Create a state to store the deck
// When the page is first loaded: Import the deck's card from the database
useEffect(() => {
db.collection(`decks/${id}/cards`)
// Return an array of cards
.onSnapshot((snapshot) => {
setCards(snapshot.docs.map((doc) => {
return {
id: doc.id,
...doc.data()
}
}))
})
}, [])
return (
<div>
<Header />
{cards && <FlashCard data={cards} />}
</div>
)
}
export default Deck
data is likely undefined when this component is first rendered. You can fix this in one of two ways.
1.) Add logic to the parent component so that Flashcard is not rendered until data is defined.
{data && <Flashcard data={data} />}
2.) Add a useEffect that monitors for changes to data and updates state.
React.useEffect(() => {
if(typeof currentCard === 'undefined'){
setCurrentCard(data[0])
}
}, [data])
Maybe you are passing some faulty data into the props. Try my sample, it works:
import React, { useState } from "react";
import "./styles.css";
// import { FlashCard } from "./FlashCard";
import { Button } from "#material-ui/core";
function Flashcard({ data }) {
// // Get an initial card
// // Get an initial card
const [currentCard, setCurrentCard] = useState(data[0]);
// Create state to track whether the card has been flipped yet
const [flipped, setFlipped] = useState(false);
// When the Flip button is clicked, we display the verso
const handleFlipClick = () => {
setFlipped(true);
};
const handleNextClick = () => {
// Change the current active card
setCurrentCard(data[1]);
// Restore the 'flipped' state to indicate that the new active card hasn't been flipped yet
setFlipped(false);
};
// Get data needed from the server
return (
<div className="flashcard__container">
{/* Card content goes here */}
<div className="flashcard__content">
<p>{flipped ? currentCard.verso : currentCard.recto}</p>
</div>
<div className="flashcard__actions">
{/* Display the flip button if the card hasn't been flipped yet */}
{!flipped ? (
<Button variant="contained" color="primary" onClick={handleFlipClick}>
Flip
</Button>
) : (
<>
<Button
onClick={handleNextClick}
color="primary"
variant="contained"
>
Hard
</Button>
<Button
onClick={handleNextClick}
color="primary"
variant="contained"
>
Slightly difficult
</Button>
<Button
onClick={handleNextClick}
color="primary"
variant="contained"
>
Good
</Button>
<Button
onClick={handleNextClick}
color="primary"
variant="contained"
>
Easy
</Button>
</>
)}
</div>
</div>
);
}
export default function App() {
return (
<div className="App">
<Flashcard
data={[
{
id: "PynaJl43Jphl2xI8fWkn",
reviewedOn: Array(2),
recto: "To go",
verso: "行く"
},
{
id: "mSDdg5ATenvjBYojfy8N",
verso: "こんにちは",
recto: "Hi",
reviewedOn: Array(2)
}
]}
></Flashcard>
</div>
);
}
I recreated this in a CodeSandbox and it worked just fine. My guess is you have an error on the props name when passing props to the Flashcard component?
It should be rendered as seen below since in Flashcard you are destructuring data from props:
<Flashcard data = {insertArrayHere} />
I would suggest you using useEffect and adding data as a dependency. Then in useEffect check, if the data exists it should change the state. You can make it this way:
useEffect(() => {
if(data){
setCurrentCard(data[0]);
}
}, [data])
or you can check existing of this prop in the parent element, adn if it exists show it that time. You can implement it in this way:
{ data ? <Flashcard data={data} /> : null }

React - Add user to list of Favorites

I have a simple user list with several details from the following api: https://gorest.co.in/public-api/users, where I want to add a selected user to a list of favorites. I am working with react-router to navigate between pages. Is this possible with React or do I also need Redux?
I have a complete LIVE EXAMPLE here with the user page and favorites.
Here is the code below for the user list:
import React from "react";
import axios from "axios";
import NavLinks from "./components/navLink";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
addToFav: false
};
this.list = [];
}
componentDidMount() {
this.getList();
}
/* get users list */
getList = async () => {
const api =
"https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_";
await axios
.get(api)
.then(response => {
this.list = response.data.result;
this.setState({
list: this.list
});
})
.catch(err => {
console.log(err);
});
};
addToFav = () => {
this.setState(
{
addToFav: !this.state.addToFav
},
() => console.log(this.state.addToFav)
);
};
render() {
let style = {
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
padding: "1rem",
gridGap: "1rem 1rem"
};
return (
<div>
<NavLinks />
<ul style={style}>
{this.state.list.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
<button onClick={this.addToFav}>Add to Favorites</button>
</li>
);
})}
</ul>
</div>
);
}
}
Thank you!
Here's a working codesandbox: https://codesandbox.io/s/brave-fire-4kd4p
This train of thought pretty much follows what #Chris G mentioned. Have a top-level state that holds the list of users and the favorites list. Then pass those as props to the individual components.
App.js
Hit your API here instead of inside your UserList component to prevent any unnecessary re-renders.
import React, { Component } from "react";
import UserList from "./userList";
import FavoriteList from "./favoriteList";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import axios from "axios";
export default class App extends Component {
state = {
list: [],
favorites: []
};
addFavorite = favorite => {
const { favorites } = this.state;
if (!favorites.some(alreadyFavorite => alreadyFavorite.id == favorite.id)) {
this.setState({
favorites: [...this.state.favorites, favorite]
});
}
};
getList = async () => {
const api =
"https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_";
await axios
.get(api)
.then(response => {
this.setState({
list: response.data.result
});
})
.catch(err => {
console.log(err);
});
};
componentDidMount() {
this.getList();
}
render() {
return (
<Router>
<Switch>
<Route
path="/"
exact
render={() => (
<UserList list={this.state.list} addFavorite={this.addFavorite} />
)}
/>
<Route
path="/favorites"
render={() => <FavoriteList favorites={this.state.favorites} />}
/>
</Switch>
</Router>
);
}
}
UserList.js
Call the addFavorite event-handler on button-click to pass that item back up to the parent-state.
import React from "react";
import NavLinks from "./components/navLink";
export default class UserList extends React.Component {
render() {
let style = {
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
padding: "1rem",
gridGap: "1rem 1rem"
};
return (
<div>
<NavLinks />
<ul style={style}>
{this.props.list.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
<button onClick={() => this.props.addFavorite(user)}>
Add to Favorites
</button>
</li>
);
})}
</ul>
</div>
);
}
}
Favorite.js
Use the favorites array that was passed in as a prop and iterate over it.
import React from "react";
import NavLinks from "./components/navLink";
export default class FavoriteList extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { favorites } = this.props;
return (
<div>
<NavLinks />
<ul>
{favorites.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
</li>
);
})}
</ul>
</div>
);
}
}
Is this possible with React or do I also need Redux?
Most if not all of those problems can be solved without redux just by using component state. It just gets increasingly difficult to pass the state to the components needing it the more global state you have and the more components at different depth need to access and update it.
In your case it might be sufficient to store the favorites in a component state high up the tree and pass it to the components consuming it. You could either pass it directly to the components or you could use react context to make it accessible to components deep in the tree.
A simple example:
const FavoritesContext = React.createContext({favorites: []});
const FavoritesProvider = ({children}) => {
const [favorites, setFavorites] = useState([]);
const add = useCallback(favorite => setFavorites(current => [...current, favorite]), [setFavorites]);
return (
<FavoritesContext.Provider value={{favorites, add}}>
{children}
</FavoritesContext.Provider>
};
You can use it like that:
<FavoritesProvider>
<MyApp />
</FavoritesProvider>
then anywhere in a component in your app:
const MyComponent = () => {
const {favorites, add} = useContext(FavoritesContext);
const [draft, setDraft] = useState('');
const handleChange = event => setDraft(event.target.value);
const handleAdd = () => {
add(draft);
setDraft('');
};
return (
<div>
<ul>
{favorites.map(favorite => <li>{favorite}</li>)}
</ul>
<input value={draft} type="text" onChange={handleChange} />
<button onClick={handleAdd}>Add</button>
</div>
);
}
In this simple example the favorites are just text but they could as well be objects. Also it demonstrates how you could provide a handler for adding a favorite. You could implement e.g. a handler for removing favorites in the same way.
Persisting your favorites is yet another topic you may need to deal with. You could use e.g. localStorage for that or you could store that in a database on a server and fetch it when your app mounts the first time.
I have changed your file a bit take a look - https://codesandbox.io/s/clever-butterfly-vb2iz
One way is to use the localstorage of browser.
But this way is slighty expensive and synchronous.
Update the list whenever the favorited item status is changed via
localStorage.setItem('users',JSON.stringify(users));
And look for the favorited items via
localStorage.getItem('users');//You need to parse this by JSON.parse()
Maintain a isFavorite variable in the object list.
let users=[{name:"Mr.A",isFavorite:false},{name:"Mr.B",isFavorite:true},...];
On the click of favoriting button this.addToFav change it as follows
addToFav=user=>{
const {users}=this.state;
this.setState({
users:users.map(userObject=>userObject.id===user.id?
{...userObject,isFavorite:!userObject.isFavorite}:user)
},()=>{saveToLocal(this.state.users)});
}
Now you can access the favorite items even if the page is reloaded and stays there till you clear the storage.Use this localStorage.clear() for that.
First I would change your onClick to this:
<button onClick={() => this.addToFav(user.id)}>Add to Favorites</button>
This will allow you to pass the id to the addToFave function.
Then I would add a new state called faves (an array) and every time someone clicks the add button I would add their id into this array. This will allow you to filter your original list when you want to display the faves.
this.state = {
list: [],
faves: [],
};
}
addToFav = (id) => {
this.setState(prevState => ({
faves: [...prevState.faves, id],
}));
};
When I want to use the list of faves instead of the normal list I would do this:
const favesList = [];
this.state.list.map(listItem =>
this.state.faves.find(
faveId => listItem.id === faveId
) && favesList.push(item);
Then I would pass that to the faves component
I changed accordingly, please try
https://codesandbox.io/s/youthful-poincare-7oeh0
the key is you can use push state to your link like below
<Link to={{ pathname: "/favorites", state: { favList: this.props.favList }}} onClick={() => this.forceUpdate()}>
later on under your fav page call to retrieve the state
this.props.location.state.favList
i have changed the code a little by using react context.
I would not use redux for this cause i think it would be a overkill.
Anyways here is the updated sandbox...
Link for sandbox

TypeError: Cannot read property 'thumbnail' of undefined

I got access to rest of properties instead of one. I didnt catch what problem is.
I've tried:
this.props.data.imageLinks.thumbnail
this.props.data.imageLinks[thumbnail]
this.props.data.imageLinks["thumbnail"]
But other properties spew out correct value when I tried: {this.props.data.title}, {this.props.data.author}.
class Book extends React.Component {
render() {
console.log('prop',this.props.data.imageLinks)
return (
<div key={this.props.data.id}>
<div className="book">
<div className="book-top">
<div
className="book-cover"
style={{
width: 128,
height: 192,
backgroundImage: `url(${this.props.data.imageLinks.thumbnail})`
}}
></div>
<DropDownList/>
</div>
<div className="book-title">{this.props.data.title}</div>
<div className="book-authors">{this.props.data.author}</div>
</div>
<div>
{/*BooksAPI.getAll().then(function(res){console.log(res)})*/}
</div>
</div>
)
}
}
This object how its look
I guess, thumbnail comes after component mount. You should check first, if there is a thumbnail use thumbnail as a background image or wait until thumbnail load
I have got a new method of making this work. Basically I made a different component for rendering, and passed properties through props. I then used try and catch in the component where i map the array, and before sending the props, I try for error, if there's no error, it passes normally image links. if there's an error, I would pass image property as "", which basically means empty string in img tag. I will attach the code. its rough code, with console.logs, but main thing is inside the try and catch
main driver code
import React, { useEffect, useState } from 'react';
import Details from './bookDetails';
import './books.css'
export default function Books2() {
const [bookName, setName] = useState("a");
const [load, setLoad] = useState(false)
const [itemArray, setArray] = useState([]);
useEffect(() => {
loadData();
}, [])
async function loadData() {
setLoad(true);
// console.log(bookName)
await fetch("https://www.googleapis.com/books/v1/volumes?q=intitle:"+bookName)
.then(response => response.json())
.then(data => setArray(data.items))
setLoad(false)
}
function handleChange(event) {
// console.log(event.target.value)
setName(event.target.value)
}
function handlePress() {
loadData();
}
// console.log(itemArray)
var className = (load===true) ? "loading" : "null"
return (
<div>
<h1>Hello</h1>
<input placeholder="book name" value={bookName} onChange={handleChange} />
<button onClick={handlePress}>Search</button>
<h3 className={className}>Loading...</h3>
{/* <input placeholder="name of book" value={} /> */}
{itemArray.map(book => {
{/* console.log(book.volumeInfo.imageLinks) */}
try{
return(
<Details
key={book.id}
bookName={book.volumeInfo.title}
bookYear={book.volumeInfo.publishedDate}
bookDesc={book.volumeInfo.description}
bookLink={book.volumeInfo.infoLink}
bookImg={book.volumeInfo.imageLinks.smallThumbnail}
/>
)}
catch(err) {
<Details
key={book.id}
bookName={book.volumeInfo.title}
bookYear={book.volumeInfo.publishedDate}
bookDesc={book.volumeInfo.description}
bookLink={book.volumeInfo.infoLink}
bookImg={""}
/>
}
})}
{/* <button onClick={handlePress}>Search</button> */}
</div>
)
}
component for rendering code
import React from 'react';
export default function Details(props) {
return(
<div>
<h1>{props.bookName}</h1>
<a href={props.bookLink} target='_blank'>Link to book</a>
<img src={props.bookImg} className="img"/>
<h3>{props.bookYear}</h3>
{/* <h4>{bookPage}</h4> */}
{/* <h5>{bookRating}</h5> */}
<p>{props.bookDesc}</p>
</div>
)
}

React router problem fetching data when needed

I have a tricky situation with react router 4.
Imagine I have a route
<Route path='/search/:term?' render={(props) => {
return (<ProductList
setFlag={(flag)=>this.setState({flag})}
{...props}
/>)
}} />
Now you can see I am using render in Route which means it will not unmount this component at each render rather update the old instance with new props.
However, at some point inside the ProductList the user calls setFlag function which you can see updates some property in parent.
Because of this, a rerender of the parent is caused. Which also calls componentWillReceiveProps(CWRP) of ProductList. Inside CWRP of ProductList I am always (unconditionally) fetching items with new props.
This causes my problem. You can see that when user updated flag, there was no need to fetch data again in CWRP, because updating that flag wasn't related to my data.
You could say that I should put some condition in CWRP that would do some check and fetch data only when it is necessary. However, I find it impossible to come up with such check. Because for example, ProductList receives a search term. I could, for example, compare a search term from the previous render to search term of new render and if they are different then to fetch data, however, that is incorrect, because even in case of same search term a fetch should be issued (maybe the data was updated on a server).
What solution do you see in such a situation?
So that my product list doesn't fetch data everytime the flag of parent changes?
Elevate your state and move your logic out of the render method and into a parent container-component, then utilize this.setState() to stop state updates OR use shouldComponentUpdate() to continue to allow state updates BUT stop re-renders when the flag hasn't been changed (either one will prevent ProductList from being updated):
import React, { Component } from 'react';
import ProductList from './ProductList';
export default class SearchTerms extends Component {
state = { flag: '' };
shouldComponentUpdate = (nextProps, nextState) => ( this.state.flag !== nextState.flag )
handleFlag = flag => this.setState(prevState => { return this.state.flag !== flag ? { flag } : null })
render = () => ( <ProductList setFlag={this.handleFlag} {...this.state} {...this.props} /> )
}
Then the route will change to:
<Route path='/search/:term?' component={SearchTerms} />
In addition, I'd avoid using componentWillReceiveProps() altogether and instead use componentDidUpdate().
Here's an example of a parent container-component controlling several component children. The children can update the parent via a passed down parent method.
In this simple example, searchForPlayer's onChange and onSubmit updates the parent's searchTerm state and changes the URL query via parent's handleSubmit method. The URL query change triggers the parent's componentDidUpdate method, which then fetches new data and updates the displayPlayerList component.
URL before:
/players/all
URL after form submit:
/players/player?number=${this.state.searchTerm}
So if a user types out the URL to:
/players/player?number=10
or
/players/fdskmsdfk?number=10
and hits enter, it'll load a filtered list because it's only looking for a number query.
If they go to:
/player/dsfdsdfdsdf
or
player/1223345
or anything without a number query, then it'll just fetch all the players instead (this can be handled differently, but was done for simplicity).
Working example: https://codesandbox.io/s/xn3p3o6vq
containers/PlayersList.js (parent container-component)
import isEmpty from "lodash/isEmpty";
import React, { Component, Fragment } from "react";
import qs from "qs";
import DisplayPlayerList from "../components/displayPlayerList";
import NoPlayerFound from "../components/noPlayerFound";
import SearchForPlayer from "../components/searchForPlayer";
import ServerError from "../components/serverError";
import Spinner from "../components/spinner";
export default class PlayersList extends Component {
state = {
err: "",
isLoading: true,
searchTerm: "",
players: [],
noplayer: "",
number: ""
};
componentDidMount = () => this.fetchPlayers();
componentDidUpdate = (prevProps, prevState) => this.props.location.search !== prevProps.location.search && this.fetchPlayers();
fetchPlayers = () => {
const { number } = qs.parse(this.props.location.search, { ignoreQueryPrefix: true })
fetch(`https://jsonplaceholder.typicode.com/users${number ? `/${number}` : ""}`)
.then(response => response.json())
.then(players =>
this.setState({
err: "",
players: !number ? [...players] : [players],
noplayer: isEmpty(players) ? true : false,
isLoading: false,
number,
searchTerm: ""
})
)
.catch(err => this.setState({ err: err.toString() }));
};
handleChange = e => this.setState({ searchTerm: e.target.value });
handleSubmit = e => {
e.preventDefault();
this.props.history.push(`/players/player?number=${this.state.searchTerm}`);
};
render = () => (
this.state.isLoading // (if isLoading is true..)
? <Spinner /> // (then show a spinner)
: <div style={{ padding: 20, width: 500 }}> // (otherwise...)
<SearchForPlayer // (show player search form and...)
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
{...this.state}
/>
{ this.state.err // (if there's an error...)
? <ServerError {...this.state} /> // (show the error)
: this.state.noplayer // (otherwise, if there's no player...)
? <NoPlayerFound {...this.state} /> // (show no player found)
: <DisplayPlayerList {...this.state} /> // (otherwise, display updated list)
}
</div>
);
}
components/searchForPlayer.js (child component)
import React from "react";
export default ({ handleChange, handleSubmit, searchTerm }) => (
<form onSubmit={handleSubmit}>
<input
className="uk-input"
type="number"
value={searchTerm}
onChange={handleChange}
placeholder="Search for player by number..."
style={{ width: 300, marginRight: 10 }}
min={1}
/>
<button
disabled={!searchTerm}
className="uk-button uk-button-primary"
type="submit"
>
Search
</button>
</form>
);
components/displayPlayerList.js (child component)
import map from "lodash/map";
import React from "react";
export default ({ players }) => (
<ul style={{ listStyleType: "none" }}>
{map(players, ({ id, name, username, email }) => (
<li style={{ margin: "10px 0" }} key={id}>
<strong>Player # {id}</strong>
<span> - {name}</span>
</li>
))}
</ul>
);
components/noPlayerFound.js (child component)
import React from "react";
export default ({ number }) => (
<div style={{ color: "red", padding: 20 }}>
No player was found matching #{number}!
</div>
);
component/serverError.js (child component)
import React from "react";
export default ({ err }) => (
<div style={{ color: "red", padding: 20 }}>
<i style={{ marginRight: 5 }} className="fas fa-exclamation-circle" /> {err}
</div>
);

Why I get props is undefined?

import React from "react";
import styles from "../articles.css";
const TeamInfo = props => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{props.team.city} {props.team.name}
</span>
</div>
<div>
<strong>
W{props.team.stats[0].wins}-L{props.team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
export default TeamInfo;
the code that render this
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => {
const teaminfofunc = (team) => {
return team ? (
<TeamInfo team={team}/>
) : null
}
return (
<div>
{teaminfofunc(props.teamdata)}
</div>
)
}
export default header;
and I am getting error TypeError: props is undefined in line 8 why is that ?
Line 8 is
background: url('/images/teams/${props.team.logo}')
Update:
I found that in index.js the componentWillMount bring the data correctly but in the render() those data (article and team) was not passed to render, any idea why ?
import React, {Component} from 'react';
import axios from 'axios';
import {URL} from "../../../../config";
import styles from '../../articles.css';
import Header from './header';
import Body from './body';
class NewsArticles extends Component {
state = {
article:[],
team: []
}
componentWillMount() {
axios.get(`${URL}/articles?id=${this.props.match.params.id}`)
.then(response => {
let article = response.data[0];
axios.get(`${URL}/teams?id=${article.team}`)
.then(response => {
this.props.setState({
article,
team:response.data
})
})
})
}
render() {
const article = this.state.article;
const team = this.state.team;
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
}
export default NewsArticles;
You render your component immediately, long before your AJAX call finishes, and pass it the first element of an empty array:
<Header teamdata={team[0]}
componentWillMount does not block rendering. In your render function, short circuit if there's no team to render.
render() {
const { article, team, } = this.state;
if(!team || !team.length) {
// You can return a loading indicator, or null here to show nothing
return (<div>loading</div>);
}
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
You're also calling this.props.setState, which is probably erroring, and you should never call setState on a different component in React. You probably want this.setState
You should always gate any object traversal in case the component renders without the data.
{props && props.team && props.team.logo ? <div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div> : null}
This may not be you exact issue, but without knowing how the prop is rendered that is all we can do from this side of the code.
Update based on your edit. You can't be sure that props.teamdata exists, and therefore your component will be rendered without this data. You'll need to gate this side also, and you don't need to seperate it as a function, also. Here is an example of what it could look like:
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => (
<div>
{props.teamdata ? <TeamInfo team={props.teamdata}/> : null}
</div>
)
export default header;
First -- while this is stylistic -- it's not good practice to pass props directly to your functional component. Do this instead.
const TeamInfo = ({team}) => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{team.city} {team.name}
</span>
</div>
<div>
<strong>
W{team.stats[0].wins}-L{team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
Second, you might just want to do some kind of null check. If team is undefined the first time the component tries to render, you might just want to render null so you're not wasting cycles.
In case this isn't the issue, you'd learn a lot by console.log-ing your props so you know what everything is each time your component tries to render. It's okay if data is undefined if you're in a state that will soon resolve.

Categories

Resources