In ReactJS, how do you pass a collection to children elements? - javascript

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

Related

ReactJS error when passing a function as a props

Recently I started to learn ReactJS with help of tutorials and I've run into an error I can't find solution for by myself. One of the first projects I decided to do is To-do list and when trying to pass "handleChange" function as a prop to my component I get this error TypeError: Cannot read property 'handleChange' of undefined.
Here is my full code of App class so you can see what I'm trying to do:
import React from 'react';
import './App.css';
import Content from "./Content"
import ToDoItems from "./ToDoItems"
class App extends React.Component {
constructor() {
super()
this.state = {
items: ToDoItems
}
this.handleChange = this.handleChange.bind(this)
}
handleChange() {
console.log("Change!")
}
render() {
const items = this.state.items.map(function(item){
return (<Content key={item.id} todo={item.item} checked={item.completed} handleChange={this.handleChange}/>)
})
return (
<div>
{items}
</div>
)
}
}
export default App;
I'm getting my data from file called ToDoItems and trying to pass them as props into Content component. Everything is working fine until I try to pass the function handleChange().
I must be doing something wrong. Any help would be appreciated.
The problem is here,
const items = this.state.items.map(function(item){
return (<Content key={item.id} todo={item.item} checked={item.completed} handleChange={this.handleChange}/>)
})
When ur using non-array function it binds this and others stuff to its own protoype. Meaning ur this.handleChanges this actually refers to function, instead of class
Try using this,
const items = this.state.items.map((item) => {
// ^^^^^^^^^^^
return (<Content key={item.id} todo={item.item} checked={item.completed} handleChange={this.handleChange}/>)
})
as Arrow functions don't bind this or any other stuff. so this should now refer to the class

easy react/babeljs question about returning props inside function

I'm learning a udemy course and in it we create this which is a function that takes a component and a class name as the arguments and returns a wrapped JSX having the WrappedComponent nested inside a <div>.
This is going to be real easy but I don't understand the syntax for props => (). Why do we use the props just after return statement? I understand that inside ( ) is the JSX to return. Maybe someone can easily explain why the props is there and how it gets handled?
import React from 'react';
const withClass = (WrappedComponent,className) => {
return props => (
<div className={className}>
<WrappedComponent/>
</div>
);
};
export default withClass;
The example what you copied is a common react pattern called HOC (Higher Order Component). What happens here is the following we have a function which takes a component as an argument ( WrappedComponent ) and we are returning a definition of a new component which will wrap our WrappedComponent. You could wrote the following as well
const withClass = (WrappedComponent,className) => {
return class extends React.Component {
render() {
return(
<div className={className}>
<WrappedComponent/>
</div>
)
}
}
};
So basically the syntax props => () is just a way to define a new component. It is worth to mention that the syntax itself is used to declare an arrow function.

REACT 16.3 - render props array as map (optionally)

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.

Encountered two children with the same key

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.

React: Dynamically setting element keys

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>)
}
}
}

Categories