Prop is an empty object in React child - javascript

I'm trying to add a search bar to a parent component.
All the logic is working fine in the console. With every character that is typed in the search field I get fewer results.
I try to pass it to a child component to render the card(s) result, but I get a blank card: I can not see data passed.
Parent Component <AllAssets>
class AllAssets extends Component {
state = {
cards: [],
searchField: '',
}
async componentDidMount() {
const { data } = await cardService.getAllCards();
if (data.length > 0) this.setState({ cards: data });
}
addToFavorites = (cardId, userId) => {
saveToFavorites(cardId, userId)
toast.error("The asset was added to your favorites.")
}
render() {
const { cards, searchField } = this.state;
const user = getCurrentUser();
const filteredAssets = cards.filter(card => (
card.assetName.toLowerCase().includes(searchField.toLowerCase())));
console.log(filteredAssets);
return (
<div className="container">
<SearchBox placeholder={"Enter asset name..."}
handleChange={(e) => this.setState({ searchField: e.target.value })}
/>
<PageHeader>Assets available for rent</PageHeader>
<div className="row">
<div className="col-12 mt-4">
{cards.length > 0 && <p>you can also add specific assets to your favorites and get back to them later...</p>}
</div>
</div>
<div className="row">
{!!filteredAssets.length ? filteredAssets.map(filteredAsset => <SearchResult addToFavorites={this.addToFavorites} filteredAsset={filteredAsset} user={user} key={filteredAsset._id} />) :
cards.map(card => <CardPublic addToFavorites={this.addToFavorites} card={card} user={user} key={card._id} />)
}
</div>
</div >
);
}
}
export default AllAssets;
Child Component <SearchResult>
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
return (
<div className="col-lg-4 mb-3 d-flex align-items-stretch">
<div className="card ">
<img
className="card-img-top "
src={filteredAsset.assetImage}
width=""
alt={filteredAsset.assetName}
/>
<div className="card-body d-flex flex-column">
<h5 className="card-title">{filteredAsset.assetName}</h5>
<p className="card-text">{filteredAsset.assetDescription}</p>
<p className="card-text border-top pt-2">
<b>Tel: </b>
{filteredAsset.assetPhone}
<br />
<b>Address: </b>
{filteredAsset.assetAddress}
</p>
<p>
<i className="far fa-heart text-danger me-2"></i>
<Link to="#" className="text-danger" onClick={() => addToFavorites(card._id, user._id)}>Add to favorites</Link>
</p>
</div>
</div>
</div>
);
}
export default SearchResult;
When I console.log(filteredAsset) in <SearchResult> I get an empty object. What am I doing wrong?

This line is incorrect:
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
You are passing in positional arguments, not named props. Do this instead:
const SearchResult = ({addToFavorites, filteredAsset, card, user}) => {
In your original code, React attaches all of your props as fields on the first argument. So they would be accessible in the child, but not in the way you're trying to access them. Try logging out the values of each of the arguments in the child, if you're curious to see what happens.
The corrected version passes in a single object with field names that match the names of your props. It's shorthand that's equivalent to:
const SearchResult = (
{
addToFavorites: addToFavorites,
filteredAsset: filteredAsset,
card: card,
user: user,
}
) => {

Related

How to add and remove multiple checkbox values and update nested array in React state hook

I am using React Context to create a multistep form, the form must keep the selection when the user clicks the next or previous step. I am using the below code and it's working fine until I reach the stage to add multiple features using the checkbox, once items are checked, user can go to the previous step to edit and press next to go to the next stage where checked checkboxes must remain checked. I cannot figure out how to push each checkbox value to the features array and remove the item from array when the user uncheck. The important part is to retain the selected despite user go to previous or next step.
Context Provider
import React, { useEffect, useState, createContext } from 'react'
const carSpecs = {
make: '', features: [],model: '',serviceHistory: false, warranty: false, trim: '', bodyType: '', transmission:''
}
export const UsedCarListingContext = createContext({})
export function GlobalUsedCarListingProvider (props) {
const [usedCar, setUsedCar] = useState(carSpecs)
useEffect(() => {}, [usedCar])
return (
<UsedCarListingContext.Provider
value={[usedCar, setUsedCar]}
>
{props.children}
</UsedCarListingContext.Provider>
)
}
Car Details Component
export const UsedCarAdDetails = () => {
const [usedCar, setUsedCar] = useContext(UsedCarListingContext)
const changeHandler = (e) => {
const {name, value} = e.target
setUsedCar({
...usedCar,
[name]: value
})
}
const handleChange = ({target: {name, checked}}) => {
setUsedCar({
...usedCar,
[name]: checked
})
}
return (
<div className="container-1100 bg-white shadow-nav-bar w-full h-52 pt-12">
<NavLink to={'/'} className='landing-nav-logo'>
<img
className='mr-20 mt-4 w-66 ottobay-logo-center'
src={OttobayGray}
alt={'logo'}
/>
</NavLink>
</div>
<div className="container-1050 mg-0auto flex justify-between mt-48">
<div className='container-700 p-20'>
<form>
<div className='ad-listing-input-wrapper mb-16-mobile mb-16 w-full-mobile flex items-center'>
<div className='ad-label-container'>
<label className="listing-input-label font-semibold mr-40"
htmlFor="videoLink">History: </label>
</div>
<div className="ad-input-group-container">
<div className='checkbox-group-container'>
<CheckboxWithImage
onChange={handleChange}
name={'serviceHistory'}
checked={usedCar.serviceHistory}
label={'Service History'}
icon={<GiAutoRepair/>}
checkboxTitleClass={'historyCB'}
/>
<CheckboxWithImage
onChange={handleChange}
name={'warranty'}
checked={usedCar.warranty}
label={'Warranty'}
icon={<AiOutlineFileProtect/>}
checkboxTitleClass={'historyCB'}
/>
</div>
</div>
</div>
<div>
<div className='checkbox-group-wrapper'>
{carFeatures.map(item => (
<div className='feature-item'>
<Checkbox
label={item.name}
onChange={handleFeaturesChange}
checked={usedCar && usedCar.features.some(val => val === item.id)}
value={item.id}
/>
</div>
))}
</div>
</div>
<div className="error-container"></div>
</div>
<div className="car-basic-submission-container">
<div> </div>
<button type='submit' className='search-button bg-button-primary text-white font-semibold rounded-4'> Next Step</button>
</div>
</form>
</div>
)
}
You seem to be calling a non existent function handleFeaturesChange in you feature-item checkbox.
Anyway, something like this should work:
const handleFeaturesChange = ({target: {value, checked}}) => {
setUsedCar({
...usedCar,
features: checked ? [
...usedCar.features,
value, // add the value to previously selected features
] : usedCar.features.filter(val => val !== value) // remove the value
})
}
You could potentially replace the value with name string but then you'd need to update the condition in the checked param of the Checkbox to compare it with the name instead.

Uncaught TypeError: Cannot read properties of undefined (reading 'map')

I am getting this error
Mainsection.js:27 Uncaught TypeError: Cannot read properties of undefined (reading 'map')
Here is my mainsection.js file, I am using API key to iterate the data, I am still not getting the cause of the error. First I had made an array whose name was info and stored all the data in it and then iterated it, Now after using fetchapi, I deleted that array as it was of no use. I don't know whether it was meant to delete or not.
import React, { Component } from 'react'
import Card from './Card'
export default class Mainsection extends Component {
constructor() {
super();
this.state = {
info:null
}
}
async componentDidMount(){
let Url="https://randomuser.me/api/?inc=gender,name,nat,location,picture,email&results=";
let data= await fetch(Url);
let parsedData= await data.json()
console.log(parsedData);
this.setState({
info : parsedData.info
})
}
render() {
return (
<div>
<div className="container mt-5">
<div className="row">
{this.state.info.map((element) => {
return <div className="col-md-4">
<Card key={element.email} name={element.name} location={element.location} gender={element.gender} imageUrl={element.picture.medium} />
</div>
})}
</div>
</div>
</div>
)
}
}
And here is my card.js file
import React, { Component } from 'react'
export default class Card extends Component {
render() {
let {name, location, gender, imageUrl}=this.props
return (
<div>
<div className="card" style={{ width: "18rem" }}>
<img src={imageUrl} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">{name}</h5>
<p className="card-text">{location}</p>
<p className="card-text">{gender}</p>
Go somewhere
</div>
</div>
</div>
)
}
}
Please let me I should provide more details
You can find a working example here: https://codesandbox.io/s/musing-hill-uxtt0
There are other issues with your code. For example, the name and location are objects and you are directly trying to show it on UI. I have also added code to fix name.
Mainsection.js
import React, { Component } from "react";
import Card from "./Card";
export default class Mainsection extends Component {
constructor() {
super();
this.state = {
info: null,
results: null
};
}
async componentDidMount() {
let Url =
"https://randomuser.me/api/?inc=gender,name,nat,location,picture,email&results=";
let data = await fetch(Url);
let parsedData = await data.json();
console.log(parsedData);
this.setState({
info: parsedData.info,
results: parsedData.results
});
}
render() {
console.log("results : ", this.state.results);
return (
<div>
<div className="container mt-5">
<div className="row">
{this.state?.results?.map((element) => {
return (
<div className="col-md-4">
<Card
key={element.email}
name={element.name}
location={element.location}
gender={element.gender}
imageUrl={element.picture.medium}
/>
</div>
);
})}
</div>
</div>
</div>
);
}
}
Card.js
import React, { Component } from "react";
export default class Card extends Component {
render() {
let { name, location, gender, imageUrl } = this.props;
return (
<div>
<div className="card" style={{ width: "18rem" }}>
<img src={imageUrl} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">{`${name?.title} ${name?.first} ${name?.last}`}</h5>
<p className="card-text">{JSON.stringify(location)}</p>
<p className="card-text">{gender}</p>
<a href="/" className="btn btn-primary">
Go somewhere
</a>
</div>
</div>
</div>
);
}
}
So, the API you are using doesn't response back with an Array. But it responses with an object, that object has 2 attributes which are results and info. as you can see below
{
"results": ...
....
....
"info": ...
}
and results it self is an Array of objects, while info is just an object.
So yes, you cant use map on object, its only useable on arrays.
Check the response carefully so you can decide what you wanna do.

How to render Array stored in a Object in React?

I am trying to develop a discussion forum website using React, Node and MongoDB.In post object, there is nested author object and tags array.
Here is sample image of a post object:
here is the component which I am trying to render:
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags.map((tag) => (
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{post.upvotes.length} Likes {post.views} Views
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;
This throws the following : TypeError: post.tags is undefined. a Similar error is throws while accessing post.upvotes and post.author
Since you do your http request in 'componentDidMount' a render occured at least once before. So react tried to read post.something and it was still undefined.
And even if you do it before an http request is asynchronous so be careful
You need to check that post.something is defined before you use.
Also your initialisation if confusing you initialize post as an array but you are trying to do post.title.
If post is really an array then post.map() won't crash on an empty array.
If it's an object check that is it defined correctly.
Try this as initial state
state = {
post: {
description:"",
title:"",
tags: [],
author:[] ,
upvotes:[] ,
views : 0
},
}
initial state for post is {}
state = {
post: { tags: [] },
replies: [],
};
You can have a simple if condition added. So it will only loop through that if it is present. Check this.
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags && post.tags.map((tag) => ( // <--- map will only execute when it finds tags.
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{(post.upvotes && post.upvotes.length) || 0} Likes {post.views} Views // <---- These default values too will handle the case where the data isnt ready yet
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;

I have to select the 'Search' button twice before the correct results are rendered - React JS

Apologies if this question has been asked before and solved, but I have been searching and haven't had much luck.
I have created an app where a person can search for a book and the results are returned. The problem I am having is when I enter a book title and select the search button an empty array is rendered and then an array with the proper results are returned but not rendered. When I select the search button again the proper results are rendered. I would like the correct results to be rendered on the first click.
I logged the results and it looks like the empty array is being rendered first instead of the correct results. I believe it could be due to the async nature of the API call.
Here is my code:
class App extends Component {
state = {
data: []
}
handleBookSearch = (book) => {
let data = utils.searchBooks(book);
this.setState({ data: data });
}
render() {
return (
<div className="">
<Banner
onSearch={this.handleBookSearch}
onRender={this.renderBooks} />
<div className="custom-margin">
<BookDisplay bookData={this.state.data} />
</div>
</div>
);
}
}
export default App;
class PostDisplay extends Component {
render() {
const { bookData } = this.props;
return (
bookData.map(book => (
<div>
<div className="cap-color">
<h4 className="card-header"><i className="fas fa-book fa-fw"></i>{book.title}</h4>
</div>
<div className="card-body">
<img src={book.thumbnail} alt={book.title} />
<h5 className="card-title margin-above"><b>Author:</b> {book.authors}</h5>
<h6><b>Publisher:</b> {book.publisher}</h6>
<h6><b>Published On:</b> {book.publishedDate}</h6>
<h6><b>Supported Languages:</b> {book.language}</h6>
<p className="card-text"><b>Description:</b> {book.description}</p>
<a href={book.link} target="_blank" rel="noopener noreferrer"
className="btn btn-primary cap-theme-project">Buy the book!</a>
</div>
</div>
))
);
}
}
export default PostDisplay;
class Banner extends Component {
constructor() {
super();
this.state = {
search: ''
};
}
updateSearch(event) {
const searchParam = event.target.value;
this.setState({ search: searchParam });
}
render() {
return (
< div className="title-banner" >
<h1 className="header-padding"><i className="fas fa-book fa-fw"></i><b>Google Library</b></h1>
<div className="container">
<div className="row justify-content-center">
<div className="col-12 col-md-10 col-lg-8 opacity">
<div className="card-body row no-gutters align-items-center">
<div className="col">
<input className="form-control form-control-lg form-control-borderless"
type="text" placeholder="Search for a book..." ref="searchQuery"
value={this.state.search}
onChange={this.updateSearch.bind(this)} />
</div>
<div className="col-auto">
<button onClick={() => this.props.onSearch(this.state.search)}
className="btn-margin btn btn-lg btn-primary"><i class="fas fa-search"></i></button>
</div>
</div>
</div>
</div>
</div>
</div >
);
}
}
export default Banner;
var books = require('google-books-search');
export let data = [];
export function searchBooks(title) {
books.search(title, function (err, results) {
if (!err) {
data = results;
} else {
console.log('ERROR: ' + err);
}
});
return data;
}
Use callback function. It will set state after callback method called.
handleBookSearch = (book) => {
let data = utils.searchBooks(book,(data)=>{
this.setState({ data: data });
});
}
export function searchBooks(title,callback) {
books.search(title, function (err, results) {
if (!err) {
callback(results);
} else {
console.log('ERROR: ' + err);
}
});
}
Yes your assumption is correct it is due to async operation. Handle promise like this.
handleBookSearch = async (book) => {
let data = await utils.searchBooks(book);
this.setState({ data: data });
}

I could not dispaly noroom component if there is no place name that user has queried

I have a search form where user search for place. If the place that user has typed is in the api (response object) then i want to display Room component else NoRoom component. When i type the place that is not in the api , my NoRoom component is not displayed.
search( query='place' ){
let url = "http://localhost:8000/api/v1/rental/?place__startswith="+encodeURIComponent(query);
Request.get(url).then((response) => {
console.log('response',response.body.objects);
this.setState({
place:response.body.objects,
});
});
}
searchUpdated(term){
this.search(term);
}
render() {
var margin = { marginTop : '13em' };
if (this.state.place){
let location = _.map(this.state.place, (place,id) => {
return(
<Room key={id}
slug={place.slug}
place={place.place}
city={place.city}
gallery={place.gallery}
property={place.property}/>
)
console.log('location',location);
});
let gallery = _.map(this.state.place, (place,id) => {
console.log('place',place.gallery);
_.map(place.gallery, (image,id) => {
return(
<img src={image.image} class="img-fluid" />
)
});
});
return(
<div className = "container">
<div className="content text-align-center">
<div className="row text-xs-center">
<div className="middle-text" style={margin}>
<h1 className="welcome"><span>Rental Space Welcome's you </span></h1>
<button ref="test" className="btn how-it-works" onClick={this.handleClick}>Search Space</button>
</div>
</div>
</div>
<div id="mySearch" className="overlay" onKeyDown={this.handleKeyDown}>
<button className="btn closebtn" onClick={this.handleClick}>x</button>
<div className="overlay-content">
<SearchInput ref="searchInput" className="search-input" onChange={this.searchUpdated} />
<div className="container searchList">
{ location }
</div>
</div>
</div>
</div>
);
}
else{
return(
<NoRoom />
)
}
}
}
class Room extends React.Component{
render(){
let imageFile = this.props.gallery.map((image) => {
return(
<img src={image.image} className="img-fluid" width="250px" height="250px" />
);
});
return(
<div className="room">
<div className="thumbnail">
{ imageFile[0] }
</div>
<h3 className="listingName text-left">
<a href = { "/rent/" + this.props.slug }>{this.props.place}</a>
</h3>
<span className="propertySpan">
<i className = "fa fa-home"></i>
<span className="property">{this.props.property}</span>
</span>
</div>
)
}
}
class NoRoom extends React.Component{
render(){
return(
<div>No Room</div>
)
}
}
You can see in image, No Room text is not displayed when there is no place called kat
What have i done wrong?
In you root-component, your local state (this.state.place) is an array that defines whether you display the Room component or the NoRoom component.
If your API doesn't find any matching place, then this.state.place is an empty array, which is truthy. That's why you just have to check the length of the array in your render method:
if (this.state.place.length > 0) {
Detailed explanation:
Your UI behavior is defined by the component state. The initial structure of this state must be described in your component, this is what you do in the component constructor:
this.state = {place: []};
This initial state will be used during the 1st rendering of the component.
Then, each time you want to update this state, you call this.setState() with a new value for the property "place", which must be an array for consistency.
In your render() method, you just have to describe your UI according to the current value of your state. Because "place" is always an array, the only thing you can do to check if you have data in it is to test the "length" property of this array (length === 0 when no data). If you only check the array itself like you did initially (if (this.state.place) { ... }), it will always evaluate to "true" (because an array, even empty, is always "truthy") and it's not what you want.

Categories

Resources