I'm trying to render out a mapped array supplied by props. The trouble is that the array is not always available depending on the parent's state. If I try to apply a map to a non-existent array, I get an error (naturally!). So I've tried to set a default empty array to get past the error.
If I set the default REACT thinks I'm trying to map over an object. The following code has been simplified for example purposes, but the end result is the same:
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {
// This array is not always available
// It's actually a deep property in an object
// done here for simplicity
myArray: [{name: 'foo'},{name: 'bar'}],
};
}
render() {
return (
<Child myArray={this.state.myArray} />
);
}
}
import React, { Component } from 'react';
export class Child extends Component {
render() {
console.log(this.props.myArray); //=> [{...},{...}] Array
console.log(typeof this.props.myArray); //=> Object (wuh???)
this.props.myArray.map((item) => console.log(item.name)); //=> 'foo' then 'bar'
// Supply a default empty array here to avoid mapping an undefined
const list = this.props.myArray || [];
list.map((item) => {
return (
<li key={item.name}>{item.name}</li>
);
});
return (
<ul> {list} </ul>
);
}
}
Uncaught Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
If I hard code the array and leave out the default empty array, I don't get an error:
const list = this.props.myArray.map((item) => {
return (
<li key={item.name}>{item.name}</li>
);
});
Any ideas on how I can apply a map to a conditional array?
Use default props for your Child component as an empty array:
import React, { Component } from 'react';
export class Child extends Component {
render() {
const list = this.props.myArray.map((item) =>(
<li key={item.name}>{item.name}</li>
));
return (
<ul> {list} </ul>
);
}
}
Child.defaultProps = {
myArray: []
};
.map returns a new array rather than modifying the existing array (which you shouldn't anyway, since props are meant to be read-only).
You can assign .map to a new variable, or do the mapping directly in JSX:
<ul>{list.map(item => <li key={item.name}>{item.name}</li>)}</ul>
You can also just do {(this.props.myArray || []).map(...)} or use default props, or use [] (instead of null etc) for your initial state in parent component corresponding to props.myArray.
Related
I'm trying to render out a redux state by mapping through an array of objects but I'm getting map is not a function. I can console.log my props to see it is receiving but it looks as though it's trying to map through it before the props have been passed into the component. As you can see I've tried also using the && method as others have suggested but all I get back is:
TypeError: myItems.map is not a function
Here's the code I have
import React, {Component} from 'react';
import { connect } from 'react-redux';
class RandomComponent extends Component {
state = {
myItems: this.props.myItems
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('Styles: ', this.props.myItems); // Returns object array
}
render() {
const {myItems} = this.props; // also tried this.state
return (
<ul>
{myItems && myItems.map((item) => {
return <span>Hello.</span>
})}
</ul>
);
}
}
const mapStateToProps = state => ({
myItems: state.getmyItems.myItems
});
export default connect(mapStateToProps)(RandomComponent);
Your initialState is an object, set it to an empty array []. In your catch return an empty array and not an empty object. The && does not work because an empty object "exists". If u still want to use the && then set initialState to undefined
map is a function for arrays your data type might be an object. To iterate over an object you can use for ... in
class CyInfo extends Component {
foo(){
console.log(this.props.id);
return getAttributes(this.props.id)
}
render() {
return ( <Info data = {this.foo()}> </Info>)
}
}
this parent receive "props.id" and pass data value to children which is returned by getAttributes().
export default class Info extends Component {
constructor(props){
super(props)
}
/*componentWillReceiveProps(nextProps){
console.log(nextProps);
*/
render() {
console.log(this.props.data);
return (
<div id="info">{this.props.data}</div>
)
}
}
On child i can see props value on the console and in componentWillReceiveProps also.But array not rendering.
I try the use react-devtool. In react-devtool props seems passes the children but not rendering. Interestingly in react-devtool when i change the some of array's element array is rendering.
What did i do wrong.
EDIT:
[React-Devtool Screenshot][1]
I edited the react-devtool screenshot. Props are seems but component only renders initial value. In screenshot console error is favicon just ignore this
EDIT2:Console prints props array
EDIT 3:
JSON.stringify(this.props.data)
The props are coming from function(getattributes) which is call a method asynchronous and when this props passed the child there are not rendering.
I call async method directly in parent child and set state with callback in componentWillReceiveProps:
componentWillReceiveProps(nextProps){
self = this;
AsyncFunc(nextProps.id ,(error, result) => {
self.setState({data:result})
});
}
and render with
return (<div id="info">
{Array.isArray(this.state.data) && this.state.data.map((data) => {
return <div key={data._id}>{data.class}{data.predicate}{data.yuklem}</div>
})}</div>
)
As foo is a function, you have to pass to child component as:
return ( <Info data = {() => this.foo()}> </Info>)
Also, data is an array, you have to render using .map(), as follows:
export default class Info extends Component {
constructor(props){
super(props)
}
/*componentWillReceiveProps(nextProps){
console.log(nextProps);
*/
render() {
console.log(this.props.data);
return (
<div id="info">{this.props.data.map(( element, index ) => {
console.log(element);
<span key={index}> {element}</span>})}
</div>
)
}
}
As you have mentioned that this.data.props returns an array, and in order to render elements within an array, you need to map over the array elements and also check that the data is an array or not before rendering as initially the value may not be available or not an array
export default class Info extends Component {
constructor(props){
super(props)
}
/*componentWillReceiveProps(nextProps){
console.log(nextProps);
*/
render() {
console.log(this.props.data);
return (
<div id="info">
{this.props.data && Array.isArray(this.props.data) && this.props.data.map((data, index) => {
return <div key={index}>{data}</div>
})}</div>
)
}
}
I'm really new to react and redux development. I have a list component that is connected to a container. I want to update a list on scroll but i get:
Encountered two children with the same key
My component:
import React, { Component, PropTypes } from 'react'
import Track from './Track'
import styles from './TrackList.css'
const altImg = require('../images/sc.jpg');
export default class TrackList extends Component {
static propTypes = {
tracks: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired
}).isRequired).isRequired,
onTrackListScroll: PropTypes.func.isRequired
}
render() {
const { tracks, onTrackListScroll } = this.props;
return (
<div>
<ul className='tracks'
onScroll={(e)=>{
console.log('SCROLL!!',e)
onTrackListScroll()
}}>
{tracks.map(track =>
<Track
key={track.id}
{...track}
//onClick={() => onTrackClick(track.id)}
text={track.title}
imgSrc={!track.artwork_url ? altImg : track.artwork_url}
/>
)}
</ul>
</div>
)
}
}
reducer that update a state is :
const toSearchResultOb = (tracks) => {
return {
tracks: tracks
}
}
case 'UPDATE_SEARCH_RESULT':
return Object.assign({}, state,
toSearchResultOb(state.tracks.concat(action.tracks)))
What is correct way to update component onScroll with redux?
You're getting this error because keys between component siblings need to be unique. You probably have duplicate track.id in your tracks array.
Here's an easy fix:
{tracks.map(track, i =>
<Track
key={i}
{...track}
//onClick={() => onTrackClick(track.id)}
text={track.title}
imgSrc={!track.artwork_url ? altImg : track.artwork_url}
/>
)}
If you have a look at the documentation of map() on MDN, you'll see this:
callback Function that produces an element of the new Array, taking
three arguments:
currentValue The current element being processed in
the array.
index The index of the current element being processed in
the array.
So in the example above, i is the index of the current element. This index increments on each iteration which guarantees unique keys within that map(). Now you don't have to worry about what track.id is.
tl;dr -- How do I dynamically add a key to a React Element?
I have a React component that, when standing alone, has a static list of children:
<componentA>
<child ... />
<child ... />
<child ... />
</componentA>
As the list is static there are no keys on any of the children as they're not needed.
Now I have another component that wraps this one and makes it's children dynamic:
function componentB(componentA) {
return class extends React.Component {
render() {
const filteredChildren = // ... filter this.props.children
return (<componentA>
{filteredChildren}
</componentA>)
}
}
}
As the children are now dynamic I need to add keys to them, but if I try something like:
React.Children.map((child, i) => {
child.key = i;
return child
});
it fails saying key is readonly. From this question it seems that cloneElement is a no-go as well. So is there a way to dynamically set a key?
Use cloneElement inside map :
React.Children.map(this.props.children, (child, i) =>
React.cloneElement(child, { key: i })
);
Known that the 2ⁿᵈ argument of React.cloneElement is the NEW props of the cloned element . One of those props is the key in this example.
I had the same issue, so in your function, simply do this:
function componentB(componentA) {
return class extends React.Component {
render() {
const filteredChildren = // ... filter this.props.children
return (<componentA key={function_generating_random_strings()}>
{filteredChildren}
</componentA>)
}
}
}
In my React app, I set a state object in the constructor of Main.jsx:
this.state = { code: [['Z','R','W','O'],['R','C'],['J'],['N','S','J','T','O','X','K']]}
I can then pass that object to a child object as the props:
<CodeArea code={this.state.code}/>
In my CodeArea component, I want to create a collection of words, one for each sub-array of the code, and pass it to the CodeWords component.
CodeArea.jsx:
import CodeWords from "./CodeWords"
export default class CodeArea extends React.Component {
render() {
let words = this.props.code.map((word, index) => <CodeWords key={index} {...word} />)
return(
<div className="collection">
{words}
</div>
)
}
}
this.props.code is the correct code, and is mapped to a collection of CodeWords.
CodeWords.jsx:
import CodeLetter from "./CodeLetter"
export default class CodeWords extends React.Component {
render() {
let word = this.props.words.map((letter, index) => <CodeLetter key={index} {...letter} />)
return (
<table>
{word}
</table>
)
}
}
I'm getting an error in CodeWords:
CodeWords.jsx:5 Uncaught TypeError: Cannot read property 'map' of undefined
at CodeWords.render (CodeWords.jsx:5)
How do I reference the {words} collection from CodeArea in CodeWords? I eventually want to take each of the {words} elements, which should be CodeWords components, and split them into CodeLetter components.
Since you are spreading ... word onto the CodeWords component, you are effectively creating the component like this (react docs):
<CodeWords key={index} Z='Z' R='R' W='W' O='O' />
What you want to do in CodeArea is send the codeWord (entire array of letters) to the CodeWords component:
let words = this.props.code.map((codeWord, index) => <CodeWords key={index} words={codeWord} />)
This will give you a this.props.words in your CodeWords component.
I also think you would benefit from renaming a few things:
your CodeWords component can be CodeWord (singular) since it seems to me that you intend it to only represent a single group of letters.
the let words variable could be let codeWords
in CodeWords the let word should probably be let letters