Why my this.props.data coming undefined anywhere before render function? - reactjs

Data is passing from parent to child accordingly hence its showing inside render function but I am unable to use that to a async function which I need to run before the render.
If you see the data.title and data.link all showa under the render function but for some reason they are showing this error:
×
TypeError: Cannot read property 'link' of null
What can I alternatively do?
export default class Highlight extends Component {
state = {
htmlString: ''
}
fetchActivity = () => {
axios({
method: 'post',
url: 'api/singleactivity',
data: { activityLink: "http://play.fisher-price.com" + this.props.data.link }
})
.then((res) => {
this.setState({
htmlString: res.data
})
})
.catch((err) => {
console.log('There was an error fetching data', err);
})
}
componentDidMount() {
this.fetchActivity()
}
render() {
if (this.props.visibility.highlight) {
return (
<div>
<section className="card bg-info"
aria-label="Book Detail">
<div className="bg-secondary">
<h2>{this.props.data.title}</h2>
<h2>{this.props.data.link}</h2>
<h2>{this.props.data.imageLink}</h2>
</div>
<br />
<div className="row" dangerouslySetInnerHTML={{ __html: this.state.htmlString }}>
</div>
<br />
<div className="bg-warning">
{!this.props.visibility.favorites ?
<button onClick={() => this.addToFavorites()}> Favorite</button> :
<button onClick={() => this.removeFromFavorites()}> Remove</button>}
</div>
</section>
<br />
</div>
)
} else {
return null;
}
}

Well, mixing fetch calls and view code or using dangerouslySetInnerHTML apart, your prop this.props.data does not always hold a value and this is the direct source of your issue.
So surely you're using your component this way <Highlight data={stuff} ... /> you can change it to something like {stuff ? <Highlight data={stuff} ... /> : null} so the component won't be created in the first place if the input data is not ready yet.

What you can do is :
{this.props.data ? this.props.data.title : ''}
{this.props.data ? this.props.data.link: ''}

Related

How to fetch and display image in React, saved in Mongodb as Cloudinary URL

I just started using Cloudinary to upload picture in an application am working on. It is stored in MongoDB database as url link. I have succeeded with the upload. My database looks like this after saving:
id:5f90315331dc1727bc869afe
userEmail:"fletcher#gmail.com"
image:"https://res.cloudinary.com/myapp/image/upload/v1603285331/ifyo38crk..."
imageId:"ifyo38crkdniixeimme9"
__v:0
My get() endpoint looks like this :
app.get("/getAvatar/:email", (req, res) => {
userEmail= req.params.email;
Image.find({userEmail:userEmail},(err, images)=> {
if (err) {
res.status(500);
res.send(err);
} else {
res.json(images);
}
});
});
and my React front-end :
class AllImages extends Component {
componentDidMount(){
const{user}=this.props.auth
const email = user.email
this.props.getAvatar(email)
}
render() {
return this.props.resumeData.map(image=>{
return(
<div className="image-card-container">
<div key={image.id} className='image-card'>
<h4>{image.userEmail}</h4>
<img
className='main-image'
src={image.image}
alt='profile image'
/>
</div>
</div>
)
Redux action :
export const getAvatar=(email)=>dispatch=>{
dispatch(AvatarLoading());
axios.post('/getAvatar/'+ email )
.then(res=>
dispatch({
type: GET_RESUME_AVATAR,
payload: res.data
})
)
history.push('/');
};
On componentMount() i get the error :
TypeError: Cannot read property 'map' of undefined
something is definitely wrong with my code. This is my first time of working with Cloudinary, I will appreciate if anyone can help out!
Add a null check inside render()
class AllImages extends Component {
componentDidMount(){
const{user}=this.props.auth
const email = user.email
this.props.getAvatar(email)
}
render() {
return (
<div>
{this.props.resumeData && this.props.resumeData.map(image=>{
return(
<div className="image-card-container">
<div key={image.id} className='image-card'>
<h4>{image.userEmail}</h4>
<img
className='main-image'
src={image.image}
alt='profile image'
/>
</div>
</div>
)}
</div>
)
}
The render will get executed before componentDidMount and re-rendered on prop change.

Setting conditional onClick behaviour in ReactJS

(This is my first time using React)
I am working on app that filters videos based on the button click and this is the code below
const data = [{ address: 'https://www.youtube.com/watch?v=eIrMbAQSU34', category: 'java' },
{ address: 'https://www.youtube.com/watch?v=RRubcjpTkks', category: 'C#' }];
class User extends React.Component {
constructor(props){
super(props);
this.state={
videos : data,
};
}
render(){
return (
<div className="box">
<div className="buttons-filter">
<button onClick={()=>{
this.setState({videos: data })
}}>
All
</button>
<button id="btnJava" onClick={()=>{
this.setState({videos: data },
()=>{
this.setState({
videos: this.state.videos.filter(item => { return item.category === 'java';})
})
})
}}>
Java
</button>
<button id="btnReact" onClick={()=>{
this.setState({videos: data },
()=>{
this.setState({
videos: this.state.videos.filter(item => {return item.category==='React';})
})
})
}} >
React
</button>
</div>
<div className="content">
<div className="wrapper">
{this.state.videos.map(video => (
<ReactPlayer className="vid" url={video.address} controls={true} height="300" width="350" />
))}
</div>
</div>
<div className='bottom-btn'>
{/* <a className='btn-logout'><Link to="/Login" className='link'>Logout</Link></a> */}
</div>
</div>
);
}
};
export default User;
I was thinking of a way to reduce redundancy in my code, so I want to add an onclick function, semthing like
onClick(){
if(button.id === 'btnJava')
{
this.setState({videos: this.state.videos.filter(item => {
return item.category === 'java';})})
}
else if()
{}
}
Any idea if and how JSX handles a situation like this?
You can have one function to handle all cases based on the button value:
handleClick = (event) => {
const value = event.target.value;
this.setState({ videos: this.state.videos.filter(item => {
return item.category === value
})
})
}
<button value="java" onClick={this.handleClick}>Java</button>
<button value="React" onClick={this.handleClick}>React</button>
The idea is good to abstract the button click into its own function.
onClick passes event as a value to the function and you can get the id or value or which ever attribute from that.
If you are going to use
this.setState({videos: this.state.videos.filter(item => {
return item.category === 'java';
})
The your are going to loose your original video state after filtering.
I would recommend storing the filtered state into a different variable. This way you have the option to reset the filter.
Combining the answer from HermitCrab
this.state={
videos : data,
filtered: data
};
...
handleClick = (event) => {
const value = event.target.value;
this.setState({ filtered: this.state.videos.filter(item => {
return item.category === value
})
})
}
...
<div className="box">
<div className="buttons-filter">
<button value="java" onClick={this.handleClick}>Java</button>
<button value="React" onClick={this.handleClick}>React</button>
</div>
<div className="content">
<div className="wrapper">
{this.state.filtered.map(video => (
<ReactPlayer className="vid" url={video.address} controls={true} height="300" width="350" />
))}
</div>
</div>

Pass an object value in props to another component

Consider the code below, I need to pass an id which is in object format to another component by props. But I have try many time and it not working. I think I may have some thing mistake, but I'm not sure where is it.
Main page (updated):
render(props){
const data = this.state.data;
return (
<div>
<Header />
<NavigationBar />
<PurchaseInfoView show={this.state.displayModal} closeModal={this.closeModal} value={this.openModal}/>
<div className="purchase-side">
<div className="side-title">
<h1>Purchase Order List</h1>
</div>
<hr class="solid" />
{
Object.keys(data).map((key) =>
<div className="list-item">
<h2 onClick= {() => this.openModal(data[key].id)}> //get id
{ data[key].item_name}
</h2>
</div>
)}
</div>
<div className="dads">
</div>
</div>
);
}
openModal (Updated):
openModal = (id) => {
this.setState(
{
displayModal: true,
id: id
});
console.log(id) // i can get the id from here id=1
};
PurchaseInfoView to get the id (Updated).
class PurchaseInfoView extends Component {
render() {
console.log(this.props.id) // get undefined
return (
<div className="Modal"
style={{
transform: this.props.show ,
opacity: this.props.show ? "1" : "0"
}}
>
<h3>Purchase Order detail</h3>
<p>Id: {this.props.id}</p> //cannot get it
</div>
);
}
}
export default PurchaseInfoView;
console.log result:
If you want to pass an object to props here are the steps:
define the object in your parents state.
pass the object in props when calling components
get the object from props in child.
Here you are missing the second step!
You should try these:
MainPage
render(props){
const { data, modalObject, displayModal } = this.state; //use destructuring is more readable
return (
<div>
<Header />
<NavigationBar />
<PurchaseInfoView show={displayModal} closeModal={this.closeModal} modalObject={modalObject}/> //pass the object from destructuring state as props
<div className="purchase-side">
<div className="side-title">
<h1>Purchase Order List</h1>
</div>
<hr class="solid" />
{
Object.keys(data).map((key) =>
<div className="list-item">
<h2 onClick= {() => this.openModal(data[key].id)}> //get id
{ data[key].item_name}
</h2>
</div>
)}
</div>
<div className="dads">
</div>
</div>
);
}
OpenModal
openModal = (id) => {
this.setState(
{
displayModal: true,
modalObject: {id: id, ...any others key/val pair}
});
};
PurchaseInfoView
class PurchaseInfoView extends Component {
render() {
const { modalObject} = this.props; //here get your object from props
console.log(modalObject.id);// here you have the object
return (
<div className="Modal"
style={{
transform: this.props.show ,
opacity: this.props.show ? "1" : "0"
}}
>
<h3>Purchase Order detail</h3>
<p>Id: {modalObject.id}</p>
</div>
);
}
}
Tell me if you have any question in comment ;)
NB: i did this with an object (aka {} ) if you needed more things in your modal than just id. If just id is needed you just have to replace the modalObject by just the "id" you need
Cheers!
EDIT: for this solution to work you have to either:
initialise your state to this at least:
this.state={ modalObject : { id: ''}}
or make a not null test in your child component before displaying the element like so:
Id: {modalObject && modalObject.id ? modalObject.id : ' '}
These are needed because on first render your state will have the initial state you setted so if you didnt set anythin or didnt test for a value... well... it's undefined! :)
(note if id is null instead of having an undefined error you will have a blank space displaying in your modal)
Guess you are calling it wrongly. It should be {this.props.id}
render() {
console.log(this.props.id);
return (
<div className="Modal">
<h3>Purchase Order detail</h3>
<p>Id: {this.props.id}</p> //Changed line
</div>
);
}
Inside main page pass the id to PurchaseInfoView and access it as a prop
<PurchaseInfoView show={this.state.displayModal} closeModal={this.closeModal} value={this.openModal} id={this.state.id}/>

How to resolve React state updating but not pushing values to props?

I am fetching data from an API to build a page that will display recipes as a fun little app to add on to my "things I've built" list. I can run the fetch call and see that the state is updated in dev tools, but after adding a ternary operation to render this new data once the search is performed, the new state/data does not seem to pass into my child component props.
I've tried providing default values to the recipes prop
recipes={ this.state.results || {"id": 1, title: "test", "servings": "6", "readyInMinutes": 10}}
and I've tried setting isLoading in the callback of my setState call
this.setState({ results: resData.data.results},
() => { this.setState({isLoading: false});} )
I've been all over stack overflow and other resources trying just about anything i can find...I understand the setState is asynchronous and I've tried playing around with every solution I can find on google rephrasing this question over and over, and at this point I assume its some precise problem that I am just not noticing.
Main Component:
class CookingPage extends Component {
state = {
results: [],
isLoading: true,
}
isActive = true;
constructor(props) {
super(props);
}
// componentDidMount(){
// this.setState({
// isLoading: false
// });
// }
srchApi = e => {
e.preventDefault();
let validated = false;
let query = document.getElementById('search').value;
let cuisine = document.getElementById('cuisine').value;
let diet = document.getElementById('diet').value;
if(query){
validated = true;
}
if (!validated) {
//code to notify user of invalid search
return;
} else {
fetch('http://localhost/cooking', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: query,
cuisine: cuisine,
diet: diet
})
}).then(res => {
return res.json();
}).then(resData => {
if(this.isActive){
debugger;
this.setState({
results: resData.data.results,
isLoading: false
});
}
}).catch(err => {
if(this.isActive){
this.setState({isLoading: false});
}
});
}
}
componentWillUnmount() {
this.isActive = false;
}
render() {
return (
<div className='background'>
<div className="container">
<div className="row">
<div className="col-12">
<div className='container search-ctr'>
<Form>
<div className='row'>
<div className='col-4 plain-search'>
<Form.Group controlId='search'>
<Form.Label>Plain Search</Form.Label>
<Form.Control type='text' placeholder='Recipes...Nutrients...Ingredients...Just search!'></Form.Control>
</Form.Group>
</div>
<div className='col-4 col-cuisine'>
<Form.Group controlId='cuisine'>
<Form.Label>Cuisine</Form.Label>
<Form.Control type='text' placeholder='Italian, Mexican, etc..'></Form.Control>
</Form.Group>
</div>
<div className='col-4 col-diet'>
<Form.Group controlId='diet'>
<Form.Label>Diet</Form.Label>
<Form.Control type='text' placeholder='Vegetarian, Vegan, etc...'></Form.Control>
</Form.Group>
</div>
</div>
<div className='row'>
<div className='col-12'>
<button type="submit" className="btn btn-outline-light btnSearch" onClick={this.srchApi}>Search</button>
</div>
</div>
</Form>
</div>
</div>
</div>
<div className='row'>
<div className='col-12'>
{this.state.isLoading ? (<div></div>) :
<RecipeList
recipes={this.state.results}
onDetail={this.showDetailsHandler}
/>
}
</div>
</div>
</div>
</div>
);
}
}
export default CookingPage;
Child component:
const RecipeList = props => {
const mapRecipes = props.recipes.map(recipe => {
return(
<PreviewRecipe
key = {recipe.id}
className = "preview-recipe"
recipekey = {recipe.id}
recipeid = {recipe.id}
title = {recipe.title}
cookTime = {recipe.readyInMinutes}
servings = {recipe.servings}
onDetail = {props.onViewDetail}
/>
)
});
return (
<React.Fragment>
<div className = "recipe-list-ctr">
<h4 className = "recipe-list-title">Title</h4>
<h4 className = "recipe-list-servings">Servings</h4>
<h4 className = "recipe-list-img">Image</h4>
</div>
{mapRecipes}
</React.Fragment>
)
};
export default RecipeList;
I expect a list of RecipeList components to display on the page after being mapped from the props, however I get the error:
"TypeError: Cannot read property 'map' of undefined.
As I explained before, using dev tools and removing the isLoading:false from the setState call, I can see in dev tools that the state is updating to the data received from the API, so I am really unsure as to why it is not being passed through. My understanding of the life-cycle just might not be up to par yet, and I would appreciate and solutions or suggestions to help me debug and get back on the right track.
I've figured out the issue I believe. After days and hours of debugging, the issue came to be that my import statement for one of my components was importing from the wrong file. Thus it was rendering a component with props that were undefined.

ReactJS error: TypeError: this.state.data is undefined

Learning ReactJS practically so I've made an "app" that will load jokes async with Fetch request and load them on the page.
ReactCode
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data : [],
}
}
componentDidMount(){
this.postdata()
}
postdata(){
var initial_data = {'id': 1, 'model-name': 'Joke'}
var self = this;
fetch("\start-jokes\/", {
body: JSON.stringify(initial_data),
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
},
method: 'POST',
mode: 'cors',
redirect: 'follow',
referrer: 'no-referrer',
})
.then(response => response.json()).then((json) => {
self.setState({ data : json.data }) // which ever the key hold the data
})
}
render(){
return(
<div>
{this.state.data.length == 0 &&
<div> No options available.</div>
}
{this.state.data.length > 0 &&
<div className="container" id="jokes">
{this.state.data.map(function(item,i){
return(
<div key={i} className="card col-md-7">
<div className="card-body">
{item} // here you are getting object in item. Get the key from the object like item.name
</div>
</div>
)
})}
</div>
}
</div>
)
}
}
// ========================================
ReactDOM.render(
<App />,
document.getElementById('root')
);
it however logs this error message in the console.
TypeError: this.state.data is undefined
this.state.data is referenced twice in render and data is declared in the constructor.
Possible cause is though it's not getting it from the constructor therefore failing.
Update
Thanks to the suggestion I have tried logging console.log(json.data) and as predicted got back undefined however I'm sure there's data sent back correctly just that it comes back as an Object and might need a different way to access the data.
Console log json response returns this Object { status: "ok", jokes: Array[10], ref-id: 11 }
It looks like json.data you are assigning here:
.then(response => response.json()).then((json) => {
self.setState({ data : json.data }) // which ever the key hold the data
})
is undefined.
renderList = () => {
if (this.state.data){
return this.state.data.map(function(item,i){
return(
<div key={i} className="card col-md-7">
<div className="card-body">
{item}
</div>
</div>
)
})
}
}
render(){
let html = this.state.data?(
<div className="container" id="jokes">
{this.renderList()}
</div>
) : (<div> No options available.</div>)
return(
<div>
{html}
</div>
)
}
Try refactoring your code a little bit.
Always do a null check before doing rendering operation.
Always use Arrow functions otherwise you will have to bind the functions manually in the constructor of the class.
Hope this helps.

Resources