React: props populated with componentWillMount and Redux - javascript

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

Related

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

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.

Cannot read property 'date' of undefined - only when FlatList

i have a weird problem.
the process:
1) i fetch from api in useEffect an array of object of meetings
useEffect(() => {
trackPromise(
bookingContext.getUserMeetings(state.token, state.userId),
"getUserMeetings"
);
}, []);
2) i render the array conditinally if his render (so first render is empty - and after useEffect is get the array from the "MeetingContext"
{bookingContext.state.userMeetings.length > 0 ? (
<View style={styles.container}>
<Text>{bookingContext.state.userMeetings[0].date}</Text>.............
So far it works
now when i change the last row ( bookingContext.state.userMeetings[0].date} )
to flat list like this:
<FlatList
data={bookingContext.state.userMeetings}
renderItem={({ meet }) => (
<Text>{meet.date}</Text>
)}
keyExtractor={meet => meet._id}
/>
and try to render he tell me "TypeError: Cannot read property 'date' of undefined"
he does not care my conditionnaly or nothing.. its just crush.
4) **i use in react-promise-tracker for the loading and wrap the use effect with it.
the question : why with flat list it doesnt work and in regular syntax(like part 2) it works.
question 2: there is a way to tell react to first wait until the promise done in the use effect and just after he finish to render the array?

TypeError: this.props.messages.map is not a function

I am using React-Redux and have problem to parse array.
I using selector to return data to component, but I am getting error TypeError: this.props.messages.map is not a function. But if I console.log values it returns me normal array. What I am doing wrong?
Components render method:
render() {
const {loading, error, messages} = this.props;
const mes = JSON.stringify(messages);
console.log(mes); //<------------- returns [{"key":"value"}]
return (
<div>
<MessageList
messages={mes}
/>
);
}
MessageList component map function:
{this.props.messages.map((item, i) => (
<MessageItem
key={i}
message={item.message}
}
Maybe someone could tell me what I am doing wrong?
What you can do is
render() {
const {loading, error, messages} = this.props;
return (
<div>
<MessageList
messages={messages}
/>
</div>
);
}
Looks like the messages are from redux state. So initially the messages props will be undefined. So before doing map check whether messages is not undefined using conditional operator like below
{this.props.messages && this.props.messages.map((item, i) => (
<MessageItem
key={i}
message={item.message}
/>
))}
In a comment you've said:
messages returns this: List, not array
JavaScript doesn't have a List type, and it's clear from the result of JSON.stringify that messages is an array, or at least something that JSON.stringify thinks is an array.
I think if you look more carefully, you'll find that messages is an array and you can just use it in your MessageList directly.
If messages really, really doesn't have a map method, you can convert it from whatever it is into an array using Array.from:
mes = Array.from(messages);
...or possibly (depending on what it is) spread notation:
mes = [...messages];
You certainly don't want JSON.stringify, which creates a string.

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: []
};

React 16 forces component re-render while returning array

Assume I have the next piece of code inside the React component
removeItem = (item) => {
this.items.remove(item) // this.items -> mobx array
}
renderItem = (item, index) => {
var _item = undefined
switch (item.type) {
case "header":
_item = <Header key={item.id} onRemove={() => this.removeItem(item)} />
// a few more cases
// note that item.id is unique and static
}
// return _item -> works fine
return [
_item,
this.state.suggested
? <Placeholder key={-item.id} />
: null
]
}
render() {
return (
<div>
{this.items.map((item, i) => renderItem(item))}
</div>
)
}
Also assume that inside each of item I have a button that triggers onRemove handler with click. And each component has textarea where user can enter his text.
Obviously, when user enters text inside item's textarea, it should be saved until item will be removed.
The problem is when I remove some item, each item that goes after the removed one is being remounted (edited for Vlad Zhukov). It happens only when I return an array from renderItem(...) (I mean, when I return only item, this problem doesn't happen).
My question: is this a bug, or it's a feature? And how can I avoid it (desirable without wrapping item and Placeholder with another React child)?
UPDATED
I tried rewrite renderItem(...) the next way:
renderItem = (item, index) => {
var Item = undefined
switch (item.type) {
case "header":
Item = Header
// a few more cases
// note that item.id is unique and static
}
// return _item -> works fine
return [
<Item key={item.id} onRemove={() => this.removeItem(item)} />,
this.state.suggested
? <Placeholder key={-item.id} />
: null
]
}
And it still causes the problem.
Rerendering is absolutely fine in React and can be considered the main feature. What happens in your case is components remount when you make changes to an array of elements when these elements have no key props.
Have a look at this simple example. As you can see rerendering components has no difference but removing the first element will clear values of inputs below.
You've got 2 options:
Use a component instead of an array and set key to it (see an example). There is really no reason not to.
Remove all keys. The reason why it works is because React internally already uses keys for elements. However I wouldn't suggest this as it doesn't look reliable enough to me, I'd prefer to control it explicitly.

Categories