ReactJS: Using state causes error rendering heading - reactjs

console.log() inside componentDidMount() works with no problem.
This works as expected with logging working correctly:
Here is the problem; inside return for App it's not letting me render the <h1>:
render() {
return (
<div className="App">
<Navbar />,
<h1>{this.state.users.data[0].images.original.webp}</h1>
</div>
);
}
This code however works without < h1>:
import React, { Component } from "react";
import Navbar from "./components/Navbar";
import "./App.css";
import axios from "axios";
class App extends Component {
state = {
users: [],
loading: false
};
async componentDidMount() {
this.setState({ loading: true });
const res = await axios.get(
"http://api.giphy.com/v1/stickers/search?q=monster&api_key=sIycZNSdH7EiFZYhtXEYRLbCcVmUxm1O"
);
this.setState({ users: res.data, loading: false });
console.log(123);
console.log(this.state.users.data[0].images.original.webp);
}
render() {
return (
<div className="App">
<Navbar />
</div>
);
}
}
export default App;
Please help me understand why this is happening

You need to ensure that the users data is present in your component's state before attempting to access and render it (ie in your <h1> element).
Keep in mind that the component's render() method will be called before componentDidMount() (ie before the network request has completed). This means you'll need to account for users data not being present in your component's rendering logic. Consider making the following changes to your component to solve this:
class App extends Component {
state = {
users: null, /* Set users inital state to null */
loading: false
};
async componentDidMount() {
this.setState({ loading: true });
const res = await axios.get(
"http://api.giphy.com/v1/stickers/search?q=monster&api_key=sIycZNSdH7EiFZYhtXEYRLbCcVmUxm1O"
);
/* Trigger re-render. The users data will now be present in
component state and accessible for use/rendering */
this.setState({ users: res.data, loading: false });
}
render() {
return (
<div className="App">
<Navbar />
{ /* If state.users is null, show loading string, otherwise render data */ }
<h1>
{ this.state.users === null ? "Loading" :
this.state.users.data[0].images.original.webp }
</h1>
</div>
);
}
}

this because the this.state.users is empty array at the first time , when the component mount http request sent and when the response is ready you change the state .
to fix the issue do this :
render() {
return (
<div className="App">
<Navbar />,
{
this.state.user.length > 0 ?
<h1>{this.state.users.data[0].images.original.webp}</h1>
:
<h1>Loading...</h1>
}
</div>
);
}

Related

How can I update a single React Component from multiple different components?

I'm learning React and still trying to figure out how to plan out and implement some things. I have an app that makes three different API calls and thus three different return values. I'd like to have a global status component that tells me if all three loaded or not. Here's my psuedo code since I haven't found the proper way to do this yet, but this is effectively my train of thought at the moment. I have the main app:
const App = () => {
return (
<div>
<GenericAPICallerA />
<GenericAPICallerB />
<GenericAPICallerC />
<div>
<APIStatus/>
</div>
</div>
);
}
This is the APIStatus which just returns if all A, B, and C API calls have loaded or not:
class APIStatus extends React.Component{
constructor(props){
super(props);
this.state = {
aLoaded: false,
bLoaded: false,
cLoaded: false,
};
}
render(){
if (this.state.aLoaded && this.state.bLoaded && this.state.cLoaded){
return <div>Everything has loaded!</div>
}
else{
return <div>Nothing has loaded!</div>
}
}
}
And finally one of the APICaller components. The others are essentially the same:
class GenericAPICallerA extends React.Component{
constructor(props){
super(props);
this.state = {
error: null,
isLoaded: false,
};
}
componentDidMount(){
fetch("example.com/api",{
method: 'GET',
})
.then(res => res.json())
.then(
(result) =>{
this.setState({
isLoaded: true,
});
},
(error) =>{
this.setState({
isLoaded: false,
error
});
}
)
}
render(){
const { error, isLoaded, profile } = this.state;
if (error){
return <div>Errored!</div>
} else if (!isLoaded){
// APIStatus.state.aLoaded=false
} else {
// APIStatus.state.aLoaded=true
return(
<div>Generic Caller A is done!</div>
);
}
}
}
The comments in the render section are what I don't know how to do. I feel like I should pass in the APIStatus as a prop to the GenericAPICaller but I'm still unsure how I would update that property from inside the GenericAPICaller.
Thanks!
You can create a function in parent component and pass it to the child will be triggered and pass a state variable to the child where it will be used
For example:
import React from 'react'
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
class App extends React.Component
{
constructor()
{
super()
this.state = { my_state_variable:`some value` }
}
my_function()
{
this.setState({ my_state_variable:`new value` })
}
render = () => <>
<ComponentA my_function={my_function} />
<ComponentB my_state_variable={my_state_variable} />
</>
}
export default App
ComponentA
import React from 'react'
const ComponentA = ({ my_function }) => <>
<button onClick={() => my_function() }>Click Me </button>
</>
export default ComponentA
ComponentB
import React from 'react'
const ComponentB = ({ my_state_variable }) => <>
<p>{my_state_variable}</p>
{my_state_variable === `some value` && <p>if some value this will render </p>}
{my_state_variable === `new value` && <p>if new value this will render </p>}
</>
export default ComponentA
You can use context to accomplish this. By using context, you are able to access the value you provide to it as long as the component you attempt to access it through is a child of a provider.
The example below illustrates how you can access a shared state between multiple components at different levels without having to pass props down.
const {
useState,
useEffect,
createContext,
useContext,
Fragment
} = React;
const MainContext = createContext({});
function Parent(props) {
const [state, setState] = useState({child1:false,child2:false,child3:false});
return <MainContext.Provider value={{state,setState}}>
<Child1/> {state.child1? 'loaded':'not loaded'}
<br/>
<Child2/>
</MainContext.Provider>;
}
function Child1(){
const {state, setState} = useContext(MainContext);
return <button onClick={()=>setState({...state, child1:!state.child1})}>Load Child 1</button>;
}
function Child2(){
const {state, setState} = useContext(MainContext);
return <Fragment>
<button onClick={()=>setState({...state, child2:!state.child2})}>Load Child 2</button> {state.child2? 'loaded':'not loaded'}
<br/>
<Child3/> {state.child3? 'loaded':'not loaded'}
</Fragment>;
}
function Child3(){
const {state, setState} = useContext(MainContext);
return <button onClick={()=>setState({...state, child3:!state.child3})}>Load Child 3</button>;
}
const el = document.querySelector("#root");
ReactDOM.render(<Parent/>, el);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>

TypeError: Cannot read property 'map' of undefined When trying to map multiple arrays

I am trying to map multiple arrays at the same time and im not sure if this is how you do it. I am getting the error
TypeError: Cannot read property 'map' of undefined
When trying the following code
import React, { Component } from 'react';
import Axios from 'axios';
import NavBar from '../header-footer/nav-bar'
import Featured from './FeaturedMealplan'
import RecipeItem from './RecipeItem'
export default class MealPlanDetail extends Component {
constructor(props) {
super(props);
this.state = {
currentId: this.props.match.params.slug,
mealplanItem: {}, // Full mealplan
mealplanRecipes: [], // Contains recipe names and difficulty.
}
}
getMealplanItem() {
Axios.get(`http://localhost:5000/get-mealplan/${this.state.currentId}`
).then(response => {
console.log("response", response)
this.setState({
mealplanItem: response.data.mealplan,
mealplanRecipes: this.state.mealplanRecipes.concat(response.data.mealplan["recipes"]),
mealplanIngredients: this.state.mealplanIngredients.concat(response.data.mealplan["recipe_info"]),
recipeItem: response.data.mealplan.recipes
})
}).catch(error => {
console.log("mealplan-detail GET Error ", error)
})
}
componentDidMount() {
this.getMealplanItem();
}
render() {
const renderRecipe = this.state.recipes.map((recipe, idx) => {
return (
<div key={idx}>
<h1>{recipe.recipe_name}</h1>
<h2>Recipe Difficulty: <span>{recipe.recipe_dificulty}</span></h2>
<div>
<RecipeItem recipeItem={this.state.recipeItem} />
</div>
</div>
)
})
return (
<div>
<NavBar/>
<Featured/>
{renderRecipe}
</div>
)
}
}
Data that is given: https://pastebin.com/uYUuRY6U
I just need to be able to format it correctly which this is how I would like it formatted in the renderRecipe return. I am new to mapping and do not know if there is a way to fix or a better way.
Some issues in the code that we can improve on:
this.state.recipes seems to be undefined in your logic. Is it a typo?
I would suggest implementing renderRecipe as a function instead of a variable.
You would only hope to render renderRecipe when there is data, but when your component is being mounted, this.state.recipes is undefined. It would only have value when getMealplanItem gets a response and being defined in the callback. So you should check whether the value is defined before rendering.
Please refer to my comments in the code below.
import React, { Component } from "react";
import Axios from "axios";
import NavBar from "../header-footer/nav-bar";
import Featured from "./FeaturedMealplan";
import RecipeItem from "./RecipeItem";
export default class MealPlanDetail extends Component {
constructor(props) {
super(props);
this.state = {
// ... define `recipes` if that's what you want
};
}
getMealplanItem() {
Axios.get(`http://localhost:5000/get-mealplan/${this.state.currentId}`)
.then((response) => {
console.log("response", response);
// ... set state `recipes` here if that's what you want
this.setState({
mealplanItem: response.data.mealplan,
mealplanRecipes: this.state.mealplanRecipes.concat(
response.data.mealplan["recipes"]
),
mealplanIngredients: this.state.mealplanIngredients.concat(
response.data.mealplan["recipe_info"]
),
recipeItem: response.data.mealplan.recipes
});
})
.catch((error) => {
console.log("mealplan-detail GET Error ", error);
});
}
componentDidMount() {
this.getMealplanItem();
}
render() {
const renderRecipe = () => {
// change renderRecipe from a variable to a function
if (!this.state?.recipes) {
// check whether `recipes` is a defined value
return null;
}
return this.state.recipes.map((recipe, idx) => {
return (
<div key={idx}>
<h1>{recipe.recipe_name}</h1>
<h2>
Recipe Difficulty: <span>{recipe.recipe_dificulty}</span>
</h2>
<div>
<RecipeItem recipeItem={this.state.recipeItem} />
</div>
</div>
);
});
};
return (
<div>
<NavBar />
<Featured />
{renderRecipe()} // it's a function call now
</div>
);
}
}
There is never a this.state.recipes defined. Based on data type and comment
this.state = {
currentId: this.props.match.params.slug,
mealplanItem: {}, // Full mealplan
mealplanRecipes: [], // Contains recipe names and difficulty.
}
I will assume you meant for it to really be this.state.mealplanRecipes.
Your render then becomes
const renderRecipe = this.state.mealplanRecipes.map((recipe, idx) => {...
This can easily handle the initial render with an empty array.

I cannot get image to render after a network query

Using Parse, I am querying the database and getting an imageURL back. React is not updating the dom.
componentWillMount and just regular curly brackets.
export const profileImage = async objectId => {
const query = new Parse.Query(Parse.User);
query.equalTo("objectId", objectId);
const image = await query.find();
console.log(typeof image[0].attributes.image);
console.log(image[0].attributes.image);
return image[0].attributes.image; // return image;
}; // Query for a specific user and get image!
I imported it currently and it does the console logs so the function is executing but never rendering.
export default class MyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
profilePic: "",
};
}
componentDidMount() {
this.setState({ profilePic: profileImage(window.localStorage.objectId) });
}
render() {
return (
<div>
<Sidebar />
<div className={style.Componentcontainer} />
<div className={style.main}>
</div>
<div className={style.profile}>
<div> {this.state.profilePic} </div>
}
I eventually plan to put the string into an image tag, I just got to get this rendering first.
Your function is asynchronous, so setState will not wait and will render undefined.
To fix this, you should return a promise, and consume it with a .then() and set the state there instead. You should also use window.localStorage.getItem(), rather than trying to access a property immediately.
export const profileImage = objectId => {
const query = new Parse.Query(Parse.User);
query.equalTo("objectId", objectId);
return query.find();
};
export default class MyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
profilePic: ""
};
}
componentDidMount() {
profileImage(window.localStorage.getItem(objectId)).then(image => {
this.setState({ profilePic: image[0].attributes.image });
});
}
render() {
return (
<div>
<Sidebar />
<div className={style.Componentcontainer} />
<div className={style.main} />
<div className={style.profile}>
<img src={this.state.profilePic} />
</div>
</div>
);
}
}

React this.props.id is undefined in part of class

In my React application I need the userId in the Timeline class to get the posts from a user, but React says that it's undefined.
If I say in the rendered part
{ this.props.id }
Than it will show the right id..
I already tried every solution that I could possibly find on the internet.
import React, { Component } from 'react'
import axios from 'axios'
import Timeline from './Timeline'
class Profile extends Component{
state = {
user: {}
}
componentDidMount() {
axios.get(`http://localhost:8090/user/${this.props.match.params.id}`)
.then(res =>{
const user = res.data
this.setState({ user: user })
})
}
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
<Timeline id={this.state.user.id}/>
</div>
)}
}
export default Profile
import Cookies from 'universal-cookie'
import React, { Component } from 'react'
import axios from 'axios'
const cookies = new Cookies()
class Timeline extends Component {
state = {
user: cookies.get('user'),
posts: []
}
componentDidMount() {
const id = this.props.id
console.log("ID IS " + id)
if (this.state.user === undefined)
return
axios.get(`http://localhost:8090/user/${id}/postEntities`)
.then(response => {
this.setState({
posts: response.data._embedded.post
})
})
.catch(error => {
console.log(error)
})
}
render() {
if (this.state.user !== undefined) {
if (this.state.posts.length <= 0) {
return (
<main>
<h2>Personal timeline</h2>
<h2>This id works: { this.props.id }</h2>
<h6>There does not seem to be anything here..<br />Create a post and come back later!</h6>
</main>
)
} else {
return (
<main>
<h2>Personal timeline</h2>
{
this.state.posts.map(post => {
return (
<div>
<h5>{ post.title }</h5>
<img src={post.pictureUrl} width="200" height="200"></img>
<p><i>You took this picture at { post.longitude }, { post.latitude }</i></p>
</div>
)
})
}
</main>
)
}
}
else {
return (
<h5>You need to be logged in to use this feature</h5>
)
}
}
}
export default Timeline
The expected output in the url needs to be 2 but is undefined, the expected value in the rendered part is 2 and it outputs 2.
With react, the componentDidMount of children is called BEFORE the one from the parent.
So, when the componentDidMount of Timeline is called the first time, the componentDidMount of Profile has not been called, so there is no userId yet.
To avoid this problem, you should render the Timeline only when the Profile component has been mounted and when you have your user id.
So something like that in the render of Profile
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
{this.state.user.id && (
<Timeline id={this.state.user.id}/>
)}
</div>
)}
Because
this.state.user.id
only has value when function axios.get in componentDidMount has done. while function render() is called before.
So, To avoid undefined, you must set state with format:
state = {
user: {id : 0} //or null
}
Initially you won't have user.id, it is coming from axios service call. In this case wait till you get response and then show timeline based on condition in render.
import React, { Component } from 'react'
import axios from 'axios'
import Timeline from './Timeline'
class Profile extends Component{
state = {
user: {}
}
componentDidMount() {
axios.get(`http://localhost:8090/user/${this.props.match.params.id}`)
.then(res =>{
const user = res.data
this.setState({ user: user })
})
}
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
{typeof(this.state.user.id) !== 'undefined' ? <Timeline id={this.state.user.id}/> : ''}
</div>
)}
}
export default Profile
What variable is undefined? this.state.user.id?
If so, that probably means that you start with user: {}, then you make a promise and then set the state. The problem is that a promise will take time to fulfill, so meanwhile you are still with user: {} and this.state.user.id gives undefined.
When you call <Timeline id={this.state.user.id}/> make sure you have a id and email in your state. Or define your state with user: {is: '', email:''} or do a conditional render. Hope I understood your problem correctly!

React: Issues with Conditional Rendering

In my React-App, i use the Firebase SDK. If a user wants to reset his password, he will be redirected to a page within my app. If the code is valid, the component <PWResetConfirmForm /> should be rended. If the code is invalid, the component <PWResetOutdatedForm /> is to be rendered.
My Page Component looks like this:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset(code)
.then(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
})
.catch(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
})
}
render() {
return (
<div>
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage
When i try to run, i get a blank page and not error.
Where is my issue and how can i fix that?
Thank you very much for your help and for your time
You will not be able to return JSX from within then()/catch() of auth.doVerfiyPasswordReset() like that. You can instead approach this by taking advantage of React.Component lifecycle method componentDidMount and using setState() to manipulate state properties for conditional rendering. I've added state properties to the component, one to track whether loading (API call has completed) and one to track whether the call was a success (then) or failure (catch). These properties are used to conditionally generate JSX content for rendering. This is assuming that verfiyResetPassword() is intended to run when the component is first mounted, instead of every time render() is called:
class App extends Component {
constructor() {
super();
this.state = {
isResetVerified: null,
loading: true
};
}
componentDidMount() {
this.verfiyResetPassword();
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset('foobar')
.then(() => {
this.setState({
...this.state,
isResetVerified: true,
loading: false
});
})
.catch(() => {
this.setState({
...this.state,
isResetVerified: false,
loading: false
});
})
}
getContent() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
if (this.state.isResetVerified) {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
} else {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
}
Here is a basic example in action.
Also, in the constructor this.verfiyResetPassword = this.verfiyResetPassword.bind(this); would only be needed if verfiyResetPassword() is executed by a DOM event such as button onClick or similar.
Hopefully that helps!
I could still fix the error myself:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {
isValid: false,
code: "",
};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
componentDidMount() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
this.setState({code:code})
auth.doVerfiyPasswordReset(code)
.then(() => {
this.setState({
...this.state,
isValid: true,
});
})
.catch(() => {
this.setState({
...this.state,
isValid: false,
});
})
}
verfiyResetPassword() {
if (this.state.isValid) {
return (
<div>
<TopBar></TopBar>
<PWResetConfirmForm code={this.state.code}></PWResetConfirmForm>
</div>
);
} else {
return (
<div>
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
render() {
return (
<div className="HomePage-Main">
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage

Resources