OBSOLETE: React: setState for primitive value doesn't work with === - javascript

EDIT: Obsolete, I made some mistake in another piece of code and the received data had the wrong data type.
I have a variable that stores the index of a selected item. I used a conditional expression based on that variable to add/remove a class name so said item is rendered in a distinguishing way. I boiled down the problem to this snippet:
function App() {
const [selectedItem, setSelectedItem] = setState(-1);
setSelectedItem(0);
console.log(selectedItem);
return (
<>
{selectedItem !== 0 && <p>no item selected</p>}
{selectedItem === 0 && <p>item {selectedItem} selected</p>}
</>
);
}
This snippet always displays that no item is selected.

The hook is called useState, not setState.
Calling setSelectedItem inside the render function will trigger an infinite loop. You need to move it to an effect hook so it only runs one.
export default function App() {
const [selectedItem, setSelectedItem] = useState(-1);
useEffect(() => {
setSelectedItem(0);
}, []);
console.log(selectedItem);
return (
<>
{selectedItem !== 0 && <p>no item selected</p>}
{selectedItem === 0 && <p>item {selectedItem} selected</p>}
</>
);
}

What is setState ? Do you mean useState ? And also, you shouldn't update the state like this, you should do it in a useEffect, and use an empty array as dependency, so it will run only once, when your component is being mounted:
useEffect(() => {
setSelectedItem(0);
},[])
Then it is working like a charm

Replace === and !== with == and !=, respectively, and voila, it works. Alas, a warning is reported that one shall use === instead.
selectedItem is an array, apparently primitives make bad references. Still, it is bizarre to some extent that inside <p> the variable is unboxed automatically, while for evaluating === it isn't and thus an array is compared to a primitive, which is not true, no matter the values. == compares every shit in JS that you feed it with, so here it works.
Hope, this saves somebody 2 hours of debugging.
If somebody has a correct workaround for this, please share below.

Related

Why is this variable not considered to be 0 despite debugger saying otherwise?

I'm writing a React component that takes a total number of reviews as a prop. When the number of reviews is 0, I want to render an element stating
<div>No reviews yet.</div>
Otherwise, I render elements containing review data. Here is the component and its context:
const Stats = ({good, neutral, bad, totalReviews}) => {
if ({totalReviews} === 0)
return <div>No reviews yet.</div>
else {
return (
<div>
<Stat text="Total: " amount={totalReviews} />
</div>
);
}
}
const App = () => {
const [good, setGood] = useState(0);
const [neutral, setNeutral] = useState(0);
const [bad, setBad] = useState(0);
let totalReviews = good + neutral + bad;
return (
<div>
<Stats totalReviews={totalReviews} />
</div>
)
}
I have used the debugger command to check in Chrome's developer console the values of each variable. It shows that totalReviews = 0. The variables good, neutral, and bad all also = 0.
I've also used console.log(totalReviews).
0 is displayed by the console.log. How come my program enters the second conditional as if totalReviews isn't 0?
if (totalReviews === 0)
You only wrap js statements in curly braces inside jsx, but your if statement is just regular js.
Problem with your if condition.
it should be if (totalReviews === 0) or if (totalReviews == 0) to avoid strongly type conversation check.
You have added {} inside if condtion which is not a stadard way

Unable to render an array of values (React component)

I am building a test app to learn more about React and I have made an API call which gets a huge JSON object.
I was able to break this json into the parts that I need and now I have 10 arrays of 3 props each. I am able to send these 10 arrays in 3 props to another component, which needs to use these 3 props 10 times and render a div class Card each.
I can console.log(this.props) and it shows 10 different arrays with 3 props each,however, I cannot produce a same element 10 times.. I tried using map() but since my array is initially undefined, map() is not able to function properly either. Is there any thing in react like *ngFor in Angular ?
What is the best way to go about this?
*EDIT
Here's more code guys. Sorry still noobie here..
ERROR : this.props.map is not a function
return(
<div>
{this.props.map((data,i)=>{
return(
<li key={i}>{data.likes}</li>
);
*EDIT 2
Soo I tried running map function with an if condition but the code still breaks the very moment the condition gets true..
render() {
if(this.props.url !== undefined){
this.props.map((data,i) =>{
return <li key={i}>{data.likes}</li>
})
}
My state method is :
state = {
userId: undefined,
likes: undefined,
url: undefined
}
and im setting my values on each data stream as follows :
const pics = await fetch(`${base_url}?key=${api_key}&q=${query}
&img_type=photo&per_page=12`).then(response => {
return response.json();
})
pics.hits.map((data) =>{
return this.setState({
userId: data.user_id,
likes: data.likes,
url: data.webformatURL
})
})
this.props won't have map, it's not an array. It's an object with a property for each property passed to your component. For instance:
<YourComponent foo="bar"/>
...will have this.props.foo with the value "bar".
So if you're passing an array to your component, like this:
<YourComponent theArrayProperty={[{likes: 42},{likes:27}]} />
...then you need the name of that property:
return (
<div>
{this.props.theArrayProperty.map((data,i) => {
return (
<li key={i}>{data.likes}</li>
);
})}
</div>
);
Side note: You can use a concise arrow function for the map callback instead:
return (
<div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>
);
...and no need for the () if you put the opening tag on the line with return (you can't leave off the ( if it's on the next line, but you probably knew that):
return <div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>;
...but that's a matter of style.
With little information that you have provided, my guess is that code fails at map() when you try to use it with undefined value.
Try adding a conditional check to render
{props && props.map([RENDER CODE HERE])}
You can just make simple if statement to check if the array is not undefined, and then pass it to map function.
Another option is to set a defaultProps for an empty array.
MyComponent.defaultProps = {
arrProp: []
};

Uncaught Error: Objects are not valid as a React child but it's an array

I'm very new to react trying to learn it by building a small app which displays ingredients based on button clicks.
In my recipe view component I'm trying to render multiple ingredient views however I'm getting this error:
Uncaught Error: Objects are not valid as a React child (found: object with keys {ingred}). If you meant to render a collection of children, use an array instead.
My code is far from perfect, and it's difficult for me to try and explain the issue I can link you to my full application if you don't understand my explanation to see the error for yourself. Here's the relevant code:
import React from 'react';
import IngredView from './IngredView'
import './styles/RecipeView.css';
const RecipeView = ( {recipe} ) => {
const { recipe_name, set_ingredients } = recipe
const isIngredDefined = (set_ingredients !== undefined)
const ingred = isIngredDefined
? set_ingredients.map(set_ingredients => {
return (
<IngredView
set_ingredients={set_ingredients}
/>
);
})
: "none"
console.log(ingred);
return (
<section className="recipe-view">
<h1 className="recipe-name">{recipe_name}</h1>
{set_ingredients !== undefined &&
{ingred}
}
</section>
)
}
export default RecipeView;
And here is my Ingredient View ( the console log was for debugging)
import React from 'react';
import './styles/IngredView.css'
const IngredView = ({set_ingredients}) => {
const { components } = set_ingredients[0]
console.log(set_ingredients);
return <section className="ingred-view"><img className="ingred-image" alt="comp" src={components[0]} /><img className="ingred-image" alt="comp" src={components[1]} /><img className="ingred-image" alt="comp" src={components[2]} /><img className="ingred-image" alt="comp" src={components[3]} /><img className="ingred-image" alt="comp" src={components[4]} /></section>
}
export default IngredView;
There are a couple things here that I can see right off the bat in your RecipeView component.
First of all, in it's return statement, you have {set_ingredients !== undefined && {ingred} }. The brackets surrounding ingred are incorrect. You are not allowed to have those brackets within them in such a manner in JS (well... You are technically allowed, but you are turning ingred into an object with ingred as an attribute, which I don't think was your intention and they cannot be rendered in React as the error being displayed to you suggests), so the correct statement would be {set_ingredients !== undefined && ingred }.
Secondly, still looking at that same statement. From what I can tell, your intention is to display ingred if set_ingredients is NOT undefined and nothing otherwise, is this correct? The way you are doing it is incorrect, since if the first condition is false, you will display false. React does not render boolean values. To fix this, I would use the ternary operator, which will make the statement look like this:
{set_ingredients !== undefined ? ingred : null }
In your IngredView, I am not sure what you are trying to do. You set your components variable as the first element from set_ingredients. This will only work if the object you are trying to deconstruct components from is from the first index of set_ingredients, which based off of the screenshot you showed me, is not the case. The correct way to do this, would simply be:
const { components } = set_ingredients
So to sum it all up:
Remove the extra brackets from the return statements RecipeView. The result should be this:
{set_ingredients !== undefined && ingred }
Use the ternary operator instead of the && statement in the above code:
{set_ingredients !== undefined ? ingred : null }
In IngredView, pull components out of the set_ingredients object, not the first index of it:
const { components } = set_ingredients

Cannot read property 'address_1' of undefined - React

I have a code like this
class EventDetails extends React.Component {
constructor( props ) {
super (props);
this.state={
startdate: props.startdate || "",
enddate: props.enddate || "",
venue: props.venue || ""
}
}
componentDidMount() {
fetch(`https://www.eventbriteapi.com/v3/venues/${this.state.venue}/?token=EventBrite'sTOKEN`)
.then(response => response.json())
.then(eventvenue => this.setState({ venue: eventvenue }))
}
render() {
const { startdate, enddate, venue } = this.state;
const getDateWhenTheEventStart = new Date(Date.parse(startdate));
const theDateWhenTheEventStart = getDateWhenTheEventStart.toDateString();
const theHourWhenTheEventStart = getDateWhenTheEventStart.getHours();
const theMinuteWhenTheEventStart = getDateWhenTheEventStart.getMinutes();
const getDateWhenTheEventEnd = new Date(Date.parse(enddate));
const theDateWhenTheEventEnd = getDateWhenTheEventEnd.toDateString();
const theHourWhenTheEventEnd = getDateWhenTheEventEnd.getHours();
const theMinuteWhenTheEventEnd = getDateWhenTheEventEnd.getMinutes();
function checkTime(time) {
if (time < 10) {
time = '0' + time
}
return time;
}
return(
<React.Fragment>
<Container text>
<Header as="h1">Date and Time</Header>
<p><strong>Starts:</strong> {theDateWhenTheEventStart} | {checkTime(theHourWhenTheEventStart)}:{checkTime(theMinuteWhenTheEventStart)}</p>
<p><strong>Ends:</strong> {theDateWhenTheEventEnd} | {checkTime(theHourWhenTheEventEnd)}:{checkTime(theMinuteWhenTheEventEnd)}</p>
<Header as="h1">Location</Header>
<List>
<List.Item>{venue.name}</List.Item>
{venue.address.address_1 != undefined && <List.Item>{venue.address.address_1}</List.Item>}
{venue.address.localized_area_display != undefined && <List.Item>{venue.address.localized_area_display}</List.Item>}
</List>
</Container>
</React.Fragment>
)
}
}
export default EventDetails;
The problem of the code is here
{venue.address.address_1 != undefined && <List.Item>{venue.address.address_1}</List.Item>} // gives me the error of 'Cannot read property 'address_1' of undefined'
I suspect that this problem occured because the component render first and the program breaks because the state is not updated yet.
Do you have any ideas how to solve this problem? Thank you
Make sure that the venue.address is not null before using venue.address.address_1
{venue.address && venue.address.address_1 !== undefined && <List.Item>{venue.address.address_1}</List.Item>}
You would have to do full error checking elsewhere - for example when you set the state.
But if you would like to do a one liner and do a full Error Check, following would be possible
<List>
<List.item>Date and Time</List.item>
{venue ? venue.address ? venue.address.address_1 ? <List.Item>{venue.address.address_1}</List.Item> : <List.Item>Loading..</List.Item> : <List.Item>Loading..</List.Item> : <List.Item>Loading..</List.Item>
</List>
However, as you can see, it's a very ugly way of doing it so do validity checking elsewhere if you want to use ternary
This is the safest full check if your object is defined or not with all properies during rendering (I think the cleanest too). It will render element if your object is fully valid.
{!!venue && !!venue.address && !!venue.address.address_1
&& <List.Item>{venue.address.address_1}</List.Item>
}
the best way of doing this is to use Object.prototype.hasOwnProperty() to check whether your object contains a particular key or not :
{venue.hasOwnProperty(address) &&
venue.address.hasOwnProperty(address_1) &&
<List.Item>{venue.address.address_1}</List.Item>}
Check something like below
{venue && venue.address && venue.address_1 ? .
{venue.address.address_1} : ''}
Here you can see, first we are checking if venue object is available then we check for the inner one and then nested object. This is the best practice instead of directly displaying the value. Hope this helps :)
It's always a good idea to check than an object and all nested objects you want to access have loaded. Please find an example of how I would check. When using the && operator, Javascript will check for the first false statement and then stop. In other words, if venue is "" (an empty string is considered false), null, or undefined, Javascript will stop checking at venue, before an error similar to the one you posted above is thrown.
If venue exists, it will then check venue.address, and so on, until finally, it will return the last statement "{venue.address.address_1}". If you try the code below, you'll see what I mean.
One last thing - please make sure the venue props are actually being passed to your component, or the two List.Item components will not render (though you will not get an error).
<List>
{venue && venue.name && <List.Item>{venue.name}</List.Item>}
{venue && venue.address && venue.address.address1 && <List.Item>{venue.address.address_1}</List.Item>}
{venue && venue.address && venue.address.localized_area_display && <List.Item>{venue.address.localized_area_display}</List.Item>}
</List>
Instead of long checks like a && a.b && a.b.c, use lodash get.
https://lodash.com/docs/4.17.10#get

React: props populated with componentWillMount and Redux

So I have this loop in my render:
{this.props.OneTestArrayProp.questions.map((q, i) =>
<div key={i} className="questions_div">
<div>Question: {q.question}</div>
<div>
)}
The this.props.OneTestArrayProp is loaded this way:
componentWillMount() {
if ( ! this.props.OneTestArrayProp.length ) {
this.props.dispatch(fetchOneTest(test_id));
}
}
But I'm getting:
Error: "this.props.OneTestArrayProp.questions.map" undefined
the error appears just for a second and then it disappears when Redux load the data. Of course the root of the problem is that half second when this.props.OneTestArrayProp is populated by the middleware thunk.
There is a way to indicate to React to "wait" before it does the render?
SOLVED
The solution provided by Red Mercury works pretty well, however I coded a more "redux-like" approach adding a new Array:
this.props.QuestionsArrayProp = [];
and linking it with its reducer:
QuestionsArrayProp: state.rootReducer.questions_reducer.QuestionsTestArrayProp
That way I get an initial, empty but existent array.
Check if it exists before mapping over its questions
{this.props.OneTestArrayProp &&
this.props.OneTestArrayProp.questions.map((q, i) =>
<div key={i} className="questions_div">
<div>Question: {q.question}</div>
<div>
)}
If this.props.OneTestArrayProp is undefined the expression will short-circuit and the map will never be executed

Categories