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

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.

Related

having issues fetching google map

googlemapapiI'm having issues fetching google map, it says the page can't load correctly, I also have some errors on my console. I don't understand what I'm doing wrong, I should be able to make a query and have the places showing in the suggestions, but I'm doing something wrong. here is my component, I have also attached a photo. All help will be welcome [
import React, { Component } from "react";
import { Map, Marker, GoogleApiWrapper } from "google-maps-react";
const apiKey = process.env.REACT_APP_GOOGLE_API_KEY;
const center = {
lat: 51.5074,
lng: 0.1278,
};
let service = null;
export class MapContainer extends Component {
constructor(props) {
super(props);
this.state = {
input: "",
suggestions: [],
places: [],
};
}
savePlace = (place) => {
this.setState({ places: [...this.state.places, place] });
};
handleChange = (e) => {
this.setState({ input: e.target.value });
};
handleKeyPress = (event) => {
if (event.key === "Enter") {
this.search();
}
};
search = () => {
const {input} = this.state;
service.textSearch({query: input}, (suggestions) => {
this.setState({suggestions});
})
};
initPlaces(mapProps, map) {
const { google } = mapProps;
service = new google.maps.places.PlacesService(map);
}
render() {
const { suggestions, places } = this.state;
return (
<div className="container">
<div className="row">
<div className="col">
<div className="form-inline d-flex justify-content-between mb-4">
<input
type="text"
value={this.state.input}
onChange={this.handleChange}
className="form-control flex-grow-1"
placeholder="Search for places on Google Maps"
onKeyPress={this.handleKeyPress}
/>
<button onClick={this.search} className="btn btn-primary ml-2">
Search
</button>
</div>
<h3>Suggestions</h3>
<ul className="list-group">
{suggestions.map((place, i) => (
<li
key={i}
className="list-group-item d-flex justify-content-between align-items-center"
>
<div>
<div>
<strong>{place.name}</strong>
</div>
<span className="text-muted">
{place.formatted_address}
</span>
</div>
<button
className="btn btn-outline-primary"
onClick={() => this.savePlace(place)}
>
Show
</button>
</li>
))}
</ul>
</div>
<div className="col">
<Map google={this.props.google} zoom={14} initialCenter={center} onReady={this.initPlaces}></Map>
</div>
</div>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey,
})(MapContainer);
]2
I checked your code and if you directly put your API key in your
const apiKey = "PUT_YOUR_API_KEY_HERE"; , it will properly show your map.
It seems that you are putting your variables in the .env file (refer here on how to add custom environment variables). Make sure that you put your .env file outside the src folder and set this inside your .env file :
REACT_APP_GOOGLE_API_KEY=API_KEY_VALUE_HERE. This works for me.
You can find the sample code in this link.
Make sure to change the value of the REACT_APP_GOOGLE_API_KEY in the .env file to your API key.
Hope this helps!

React state is udpate but not in the css doodle tag

The state of the app is ok. It is updating when I change a value in the textarea I can see the changement in the state component with the react utility but the css doodle don't update. I must refresh manually to see the changes I don't understand why. Thanks a lot
class App extends Component {
state ={
dood: doodText
}
componentDidMount(){
const dood=localStorage.getItem('dood')
if(dood){
this.setState({dood})
}
else{
this.setState({dood: doodText})
}
}
componentDidUpdate(){
const {dood}= this.state
localStorage.setItem('dood', dood)
}
handleChange = event =>{
var dood= event.target.value
this.setState({dood})
}
render(){
return (
<div className="container" onChange={this.handleChange} >
<div className="row">
<div className="col-sm-6">
<textarea onChange={this.handleChange} value={this.state.dood}
className="form-control"
rows="25" />
</div>
</div>
<div className="col-sm-6" onChange={this.handleChange} >
<css-doodle >{this.state.dood}</css-doodle>
</div>
<div>
</div>
</div>
);
}
}
export default App;
Just set some order
I think its should work, I add a div with dood inside to see if its work.
And I write some comment for you.
class App extends Component {
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
}
state = {
dood: doodText
}
componentDidMount() {
const dood = localStorage.getItem('dood')
if (dood) {
this.setState({ dood })
}
// THIS ELSE DO NOT NECESSARY
// else {
// this.setState({ dood: doodText })
// }
}
componentDidUpdate() {
// FOR WHY IS THAT HAPPEN EVERY UPDATE?
const dood = this.state.dood
localStorage.setItem('dood', dood)
}
// USE BIND IS BETTER
handleChange(ev) {
var dood = ev.target.value
this.setState({ dood })
}
render() {
return (
<div className="container" >
<div className="row">
<div className="col-sm-6">
<textarea onChange={this.handleChange} value={this.state.dood}
className="form-control"
rows="25" />
</div>
</div>
<div>{dood}</div>
<div className="col-sm-6" >
<css-doodle >{this.state.dood}</css-doodle>
</div>
</div>
);
}
}
export default App;
css-doodle provides an .update() method to manually update it, see:
https://css-doodle.com/#js-api-update
So you can listen to the change or input event of the textarea and then call .update()

React app not showing in Codepen no matter what?

I have a react app that I made in VS Studio, putting it into codepen, it doesnt seem to load a thing, any suggestions?
I have tried making sure React is linked and checked all of my syntax, no errors on local host but no display in codepen.
I have looked through the code multiple times and I feel its such a silly mistake
https://codepen.io/donnieberry97/pen/EzmOvW
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
editing: false,
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
if (this.state.userInput === "") { (alert("Please enter a To-do")); return; };
const { list, userInput } = this.state;
this.setState({
list: [...list, {
text: userInput, key: Date.now(), done: false
}],
userInput: ''
})
}
handleChecked(e, index) {
console.log(e.target.checked);
const list = [...this.state.list];
list[index] = { ...list[index] };
list[index].done = e.target.checked;
this.setState({
list
})
}
handleEditing(e) {
this.setState({
editing: true
})
}
handleRemoved(index) {
const list = [...this.state.list];
list.splice(index, 1);
this.setState({
list
})
}
render() {
var viewStyle = {};
var editStyle = {};
if (this.state.editing) {
viewStyle.display = "none"
}
else {
editStyle.display = "none"
}
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<div class="submitButton">
<button onClick={() => { this.addToList(this.state.userInput) }}>Add todo</button>
</div>
{this.state.list.map((list, index) => (
<div className="form">
<ul>
{/* <div style={viewStyle} onDoubleClick={this.handleEditing.bind(t his)}> */}
<li key={list.key}>
<div class="liFlexCheck">
<input type="checkbox" onChange={(e) => this.handleChecked(e, index)} />
</div>
<div class="liFlexText">
<div class="liFlexTextContainer">
<span style={{ textDecoration: list.done ? 'line-through' : 'inherit' }}>
{list.text}
</span>
</div>
</div>
<button onClick={(index) => this.handleRemoved(index)}>Remove</button>
<input
type="text"
style={editStyle}
value={list.text}
/>
</li>
{/* </div> */}
</ul>
</div>
))}
</div>
);
}
}
Remove the import statements, working example.
You shouldn't use import when you got External Scripts.
Also, you got many errors in your code that should be handled, like:
<div class="submitButton">, use className.
Each child in a list should have a unique key prop.
Form field with value prop but without onChange handler.
Check out the logs:
In codpen, you don't need to import the react instead just write code,
here is codepen working one : codepen
from codesandbox, you can learn with all imports also because it doesn't uses any external scripts,
your code will work fine if you add an import to it
that is import ReactDOM from 'react-dom';
codesandbox will show all these suggestions,
here is codesandbox working example: codesandbox

items do not map until page refresh - using firestore, react and redux

I was working on a forum project,Ii used firestore as backend database, react, and redux.
I have an issue whenever someone post a comment on new post with no comment, it does not show, but after refresh is appears, all comments after that appears normally.
github https://github.com/nikhilb2/Forum
deployment http://mariosforum.surge.sh/signin
Can anyone please help me.
import React, { Component } from "react";
import { postComment } from "../../store/actions/projectActions";
import { connect } from "react-redux";
import moment from "moment";
class Comment extends Component {
constructor(props) {
super(props);
this.state = {
comment: "",
authorId: "",
projectId: ""
};
this.handleContent = this.handleContent.bind(this);
this.handlePost = this.handlePost.bind(this);
}
handleContent(e) {
this.setState({
comment: e.target.value,
projectId: this.props.projectId,
authorId: this.props.auth.uid
});
}
handlePost() {
this.props.postComment(this.state);
this.refs.comment.value = "";
}
render() {
const { user, project, state } = this.props;
console.log(`user`);
console.log(this.props);
return user ? (
<div className="container">
{project &&
project.comment &&
Array.isArray(project.comment) &&
project.comment.map(comment => {
const authorId = comment.authorId;
//console.log(user[authorId]);
//console.log(authorId)
return (
<div className="container project-details">
<div className="card z-depth-0">
<div className="card-content">
{comment.comment}
<div className="card-action grey lighten-4 grey-text">
{user[authorId] ? user[authorId].firstName : authorId}{" "}
{user[authorId] ? user[authorId].lastName : authorId}
<div>
{comment.time
? moment(comment.time.toDate()).calendar()
: authorId}
</div>
</div>
</div>
</div>
</div>
);
})}
<div className="card z-depth-0">
<div className="card-content">
<div className="input-field">
<label htmlFor="comment">Type Comment</label>
<textarea
id="comment"
ref="comment"
type="text"
className="materialize-textarea"
onChange={this.handleContent}
/>
</div>
<button
className="btn pink lighten-1 z-depth-0"
onClick={this.handlePost}
>
Post
</button>
</div>
</div>
</div>
) : null;
}
}
const mapDispatchToProps = dispatch => {
return {
postComment: project => dispatch(postComment(project))
};
};
const mapStateToProps = state => {
console.log(state);
return {
state: state
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Comment);
It sounds similar to a problem I had with the same setup. Adding following line to the react-redux-firebase settings in your index.js file might solve the problem:
allowMultipleListeners: true
export const postComment = (project) => {
return(dispatch,getState,{getFirestore}) =>{
const firestore = getFirestore()
console.log(getState())
firestore.collection('projects').doc(project.projectId).update({
comment: firestore.FieldValue.arrayUnion({
comment:project.comment,
authorId:project.authorId,
time: new Date()})
}).then(()=>{
dispatch({
type:'POST_COMMENT',
project
})
}).catch((err)=>{
dispatch({type:'POST_COMMENT_ERROR'})
})
}}
You need to add an action to update redux store for comments, so every time you make a comment it will update redux store

Updating State Breaks Child Prop Component

I am still trying to get a handle on parent-child data sharing and have an issue where my stateful component has an array of objects that display correctly within my child prop components via componentWillReceiveProps(), but when I trigger my updateCommentsFunc() function, which is triggered by a child component form, the POST is called and value is appended to the array, but I get an undefined error from my child component that demonstrates that the data isn't flowing post state update.
Am I using the wrong method? Should I add something to my updateCommentsFunc()?
Here is the console error:
Uncaught TypeError: Cannot read property 'picture' of undefined
at Comment (comment.js?2fa6:15)
at mountIndeterminateComponent (react-dom.development.js?cada:10400)
at beginWork (react-dom.development.js?cada:10601)
at performUnitOfWork (react-dom.development.js?cada:12573)
at workLoop (react-dom.development.js?cada:12682)
at HTMLUnknownElement.callCallback (react-dom.development.js?cada:1299)
at Object.invokeGuardedCallbackDev (react-dom.development.js?cada:1338)
at invokeGuardedCallback (react-dom.development.js?cada:1195)
at performWork (react-dom.development.js?cada:12800)
at scheduleUpdateImpl (react-dom.development.js?cada:13185)
Triggered at line:
<img src={props.user.picture} className="comment__record-profile"/>
This is the parent component which is fed an object that has its nested array mapped and stored in an array in the state:
//Loop through JSON and create Comment and Comment Container Component
class CommentFeed extends React.Component {
constructor(props){
super(props);
this.state = {
comments: []
};
this.updateCommentsFunc = this.updateCommentsFunc.bind(this);
}
//Load Array to component
componentWillReceiveProps(nextProps){
let commentArr = [];
nextProps.comments.map((comment) => {
comment.comment_comments.map((comment) => {
commentArr.push(comment);
})
})
this.setState({comments: commentArr});
}
//Append new POST value to commentArr
updateCommentsFunc(newComments){
var updatedCommentArr = this.state.comments.slice();
updatedCommentArr.push(newComments)
this.setState({comments: updatedCommentArr});
}
render(){
return (
<div>
{
this.props.comments.map((comment, index) => {
return (
<div className="row">
<div className="col-md-6 col-md-offset-3 comment-card">
<CommentCard {...comment} key={comment.commentIdHash} user={this.props.user} />
<Comments comments={this.state.comments} key={index} commentId={comment.commentIdHash} csrf={this.props.csrf} updateComments={this.updateCommentsFunc}/>
</div>
</div>
);
})
}
</div>
);
}
}
Here is the child component split into a form and displayed comments:
//Blog Comment - Container
export default class Comments extends React.Component {
render() {
return (
<div className="blog-comment-container">
<CommentForm updateComments={this.props.updateComments} blogId={this.props.blogId} csrf={this.props.csrf}/>
{ this.props.comments.map((comment, i) =>
<AttachedComment commentObj={comment} blogComponentId={this.props.blogId}/>
)}
</div>
);
}
}
Here is the form calling this.props.updateComments() with the returned JSON data from the POST:
class CommentForm extends React.Component {
constructor(props){
super(props);
this.state = {
value: ''
};
this.postComment = this.postComment.bind(this);
this.onChange = this.onChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
postComment(comment, blogId, csrfToken) {
var body = { comment: comment };
var route = 'http://localhost:3000/app/blog/' + blogId + '/comment';
fetch(route,
{
method: 'POST',
body: JSON.stringify(body),
headers: {
'X-CSRF-Token': csrfToken,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => {
return res.json();
})
.then(data => {
this.props.updateComments(data)
})
.catch(err => {
console.log(err);
});
}
onChange(e){
this.setState({
value: e.target.value
});
}
handleSubmit(e){
e.preventDefault();
this.postComment(this.state.value, this.props.blogId, this.props.csrf);
}
render(){
return (
<div className="blog-comment__form">
<div className="row">
<div className="col-md-12">
<label>Comment:</label>
</div>
</div>
<div className="row">
<form action={"/app/blog/" + this.props.blogId + "/comment"} method="post" onSubmit={this.handleSubmit}>
<input type="hidden" name="_csrf" value={this.props.csrf}/>
<div className="col-md-9">
<textarea name="comment" className="blog-comment__form-text-area" onChange={e => this.setState({ value: e.target.value })} value={this.state.value}></textarea>
</div>
<div className="col-md-3">
<button type="submit" className="blog-comment__form-button" disabled={!this.state.value}>Comment</button>
</div>
</form>
</div>
</div>
)
}
}
Here is the conditional that checks to see if the nested array Id matches the id from the fed object at the parent:
const AttachedComment = props => {
if(props.commentObj.blogIdHash == props.blogComponentId){
return (
<Comment {...props.commentObj} key={props.commentObj.blogCommentId}/>
)
} else {
return null;
}
}
Finally, if that returns TRUE then the component where the error appeared is rendered:
const Comment = props => {
return (
<div className="comment-comment__record">
<div className="row">
<div className="col-md-12">
<div className="comment-comment__meta">
<div className="row">
<div className="col-md-6">
<img src={props.user.picture} className="comment-comment__record-profile"/>
</div>
<div className="col-md-6">
</div>
</div>
</div>
<h5>{props.user_id}</h5>
<h4>{props.comment}</h4>
<h3>{props.synotate_user.fullNameSlug}</h3>
</div>
</div>
</div>
)
}

Resources