I am creating a react web app. My state in my parent component is an array of objects (one to many number of objects stored in this array...it could be any number of objects). I want to send object X of the array through to my child component through props. Here is my child component:
import React, { Component } from 'react'
export class Card extends Component {
render() {
console.log('in card');
console.log(this.props.newCard);
return (
<div>
<h2>Here is a card!</h2>
</div>
)
}
}
export default Card
For necessary context, here is my render method in the parent component that calls the child (named Card):
render() {
return (
<div>
<Card newCard={this.state.cardList[this.state.eachCard]}></Card>
<button>Next</button>
</div>
)
}
The this.state.eachCard is just referring to an accumulator I will later implement to go through each object of the array upon clicking the Next button. Right now it is just set to position 0 for testing purposes.
When I console.log the newCard prop in the child component, this is the structure of the object that is send from parent to child:
{CardID: 3, CardName: "test", CardDefinition: "testing", category_CategoryID: 2}
However, I am wanting to specify a particular property of this object. For example, I want to retrieve the name of the card. However, when I tried to console.log this
console.log(this.props.newCard.CardName);
I received the following error:
"Cannot read property 'CardName' of undefined"
This does not make sense to me, as this.props.newCard was not undefined. Therefore it would make sense to me that specifying the newCard prop one more degree to newCard.CardName should logically work. I cannot figure out what I am missing. Is this some sort of syntax error? Or is my logic just totally off?
I seem to be very close, but am hung up on how to proceed...any ideas sure would be appreciated. Thanks!!
A good first step would be to guard against undefined here. I'm not sure what the rest of your code looks like but if there's some async happening somewhere it's possible on first render that the prop is undefined. When you pass undefined in to console.log it doesn't log anything so if this component is indeed getting rendered twice then you'd get no log for the first render. A great way to test this theory is to do your console log like the following:
console.log('newCard', this.props.newCard);
You can also guard against undefined here so it won't throw an error by returning null if this.props.newCard is in fact undefined.
export class Card extends Component {
render() {
if (this.props.newCard === undefined) {
return null;
}
return (
<div>
<h2>Here is a card!</h2>
</div>
)
}
}
export default Card
Edit due to additional context.
The way you render items in an array as children in react is using the map method of the array object and passing in a component to the callback:
return (
<div>
{
this.state.cardList.map(eachCard => (<Card newCard={eachCard} />))
}
<button>Next</button>
</div>
)
}
There is a typo in your console log, change porps by props.
Related
app.js:
import './App.css';
import HttpService from '../services/http-service'
import React, { Component } from 'react';
const http = new HttpService()
class App extends Component {
constructor(props){
super(props)
this.state = {accounts: ""}
this.loadData = this.loadData.bind(this)
}
loadData = () => {
http.getAccounts().then(res =>{
this.setState({accounts: res})
console.log(this.accounts)
return res
}, err => {console.log(err)})
}
render() {return (
<div className="container">
<h1 className="title">Retail API</h1>
<DisplayAcc accounts = {this.accounts} />
</div>
)}
}
export default App;
on DisplayAcc, I have a console.log(this.props.accounts) in the constructor.
Output is undefined. What should I do?
I have tried adding these line:
componentDidMount(){
this.loadData = this.loadData.bind(this)
}
Still undefined. Please point out the error or if you have any suggestions/best practices I would highly appreciate that because I'm very new to this. Thanks!
In order to access the accounts state to be passed down from the App component, to your DisplayAcc commponent:
render() {return (
<div className="container">
<h1 className="title">Retail API</h1>
<DisplayAcc accounts = {this.state.accounts} />
</div>
)}
You should be able to access the state by you are trying to reach it by this.accounts
change it too:
this.state.accounts
It's more popular to use so called functional components nowadays in the community. By this approach you wouldnot need the constructor, really recommend reading about this since it will simplify your code quite a bit!
I don't see where loadData is called, which makes me suspicious about the value of accounts.
Consistency is key: if accounts is part of the component's state, then you should access it via this.state.accounts, rather than this.accounts.
Please notice that you console.log meaningfully. Hence, if you want to check this.account, there's no point printing res, as you mentioned in your comment.
First of all, this.loadData = this.loadData.bind(this) is unnecessary because loadData is an arrow function.
Secondly, setState is an async function so you can't read new state just after calling it. So, the following console log will be undefined
this.setState({accounts: res})
console.log(this.accounts) // also this must be this.state.accounts!
Moreover, you are trying to get state value in a wrong way, it must be:
<DisplayAcc accounts={this.state.accounts} />
If you want to read accounts prop in DisplayAcc component, you should add your console log to whether in render or componentDidUpate methods because constructor is only called on first render and your accounts props is empty at that time. But the ones I mentioned are called every time the props are changed.
I'm working on the freeCodeCamp drum machine app. In my app with function arrow components, I set state of display with the useState hook in the parent component and pass it as a prop to the child component. In the parent component, I try to render the display state in a div. However, when the method is triggered (on click of the "drum pad" div), the app crashes. In the console I get an error that says "Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {display}). If you meant to render a collection of children, use an array instead."
I've been following along a YouTube tutorial for this project but using arrow function components and Hooks instead of regular classes as used in the tutorial--in the tutorial (around 1:55 of this video) the person successfully does what I'm trying to do, so I think the issue is something to do with using Hooks or arrow function components.
// APP COMPONENT (PARENT)
const sounds = [
{ id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3' },
// etc.
];
const App = () => {
const [display, setDisplay] = useState(''); // <----
const handleDisplay = display => { // <----
setDisplay({ display });
}
return (
<div className="App">
<div className="drum-machine">
<div className="display">
<p>{display}</p> // <---- Related to error in console
</div>
<div className="drum-pads">
{sounds.map(sound => (
<DrumPad
id={sound.id}
letter={sound.letter}
src={sound.src}
handleDisplay={handleDisplay} // <----
/>
))}
</div>
</div>
</div>
);
}
// DRUMPAD COMPONENT (CHILD)
const DrumPad = ({ id, letter, src, handleDisplay }) => {
let audio = React.createRef();
const handleClick = () => {
audio.current.play();
audio.current.currentTime = 0;
handleDisplay(id); // <----
}
return (
<div
className="drum-pad"
id={id}
onClick={handleClick}
>
<p className="letter">{letter}</p>
<audio
ref={audio}
id={letter}
src={src}
>
</audio>
</div>
);
}
You're setting the state as an object instead of a string. Remove the curly brackets around it.
const handleDisplay = display => {
setDisplay(display);
}
This was already answered, but since you are following a tutorial, I am assuming you are learning React and wanted to point a couple of things to help you :)
The incorrect use of state was pointed out, but just for clarification (and the reason I think you were using an object): in the "old" way, with Class components, the state used to be an object, and you needed to update it like an object. This example here shows that. With Hooks, you don't need to set the whole State object, only that specific state property. More info here.
Another point is, in your CodePen example at least, you were missing the import for useState. You either need to import it like this import { useState } from React or use it like this React.useState, since this is a separate module, not imported by default when you import React.
The last point is, when creating components using a loop (like your <DrumPad> with the map) you need to provide a "key" attribute. that will help React keep track of things that needs to be updated or rerendered.
O updated your code with those changes in this link, if you wanna see it working:
https://codesandbox.io/s/reverent-browser-zkum2
Good luck and hope you are enjoying React Hooks :)
I see that my code breaks even though prop list is required.
So, should I check for the existence of list before mapping it as I'm doing below?
class Cart extends React.Component {
render() {
const { list } = this.props
return { list && list.map(e => <div> {e} </div>) }
}
}
Cart.propTypes = {
list: PropTypes.array.isRequired
}
UPDATE:
I see suggestions advising to add a default value.
Does it make sense though to both have isRequired and default value set?
Isn't it implied that if a value is required then it should always exists?
But the component seems to mount even though some required props are not satisfied.
So I guess setting default value makes sense, but so isRequire is only a flag for the developer, nothing more, Correct?
Yes, I think you should.
Other developers can still explicitly pass null to the list prop.
<Cart list={null}/>
Or ... a more real-life example:
// getListFromServer() <-- can return null
<Cart list={getListFromServer()}/>
You should use PropTypes which is imported from prop-types:
import PropTypes from 'prop-types'
Cart.propTypes = {
list: PropTypes.array.isRequired
}
So, should I check for the existence of list before mapping it as I'm doing below?
return { list && list.map(e => {e} ) }
Yes, you should check it. Because until your component is being rendered, the list may be undefined or null. And using map on undefined or null will throw you an error. When your component gets list data then your use of map will be correct usage.
It would even be better to check its length:
return { list && list.length && list.map(e => <div> {e} </div>) }
I would also suggest you to use defaultProps:
Cart.defaultProps = {
list: [] // or list: ['my','default','props']
}
If you use the default props, then you don't need to worry about checking it before using map. This will ensure you to map on array.
But what if user pass the props other than array?
Even in this case, user is notified through PropTypes.array.isRequired. So checking list before using map in this case is not necessary.
I think that depends on your way of programming, It's very subjective.
Some people prefer to have the responsibility on the caller to provide the right value, some prefer to be more defensive and check for all the possible values.
I prefer to have the caller provide the right value, otherwise, why have propTypes in the first place, it almost becomes useless.
Now if you can't control how your component will be called, then Yes check that the right value is passed.
I would do null checks when doing some side effects, like doing an Ajax call where I can't really control the result.
At the end, you need to do types/value checks in your program, the question is where do you do it, everywhere or where it matters.
Could you post the code where you are passing the list to the cart component.
If nothing works you can always try this
Cart.defaultProps = {
list: []
}
Although I would suggest to fix the underlying problem of why the code is crashing, could you provide an error log as well.
Yes you should check whether it has an array or not because your Cart component wants list as an array always and list should not be empty array and only then do map or do that check in parent component itself before passing List props to Cart so that you no need to check again in Cart component you can directly do map
class Cart extends React.Component {
render() {
const { list } = this.props; //should be inside render
return (list && list.length>0 && list.map(e => <div> {e} </div>)
}
}
Better keep your list as empty array in your parent component like for eg: this.state={list:[]} so that you no need to check whether it is undefined or null. You can just check the length of the array and do map
I have a recursively defined component tree which is something like this:
class MyListItem extends Component {
...
componentDidMount() {
this.listener = dataUpdateEvent.addListener(event, (newState) => {
if(newState.id == this.state.id) {
this.setState(newState)
}
})
}
...
render() {
return (
<div>
<h1>{this.state.title}</h1>
<div>
{this.state.children.map( child => {
return (<MyListItem key={child.id} data={child} />)
})}
</div>
</div>
)
}
}
So basically this view renders a series of nested lists to represent a tree-like data structure. dataUpdateEvent is triggered various ways, and is intended to trigger a reload of the relevant component, and all sub-lists.
However I'm running into some strange behavior. Specifically, if one MyListItem component and its child update in quick succession, I see the top level list change as expected, but the sub-list remains in an un-altered state.
Interestingly, if I use randomized keys for the list items, everything works perfectly:
...
return (<MyListItem key={uuid()} data={child} />)
...
Although there is some undesirable UI lag. My thought is, maybe there is something to do with key-based caching that causes this issue.
What am I doing wrong?
React uses the keys to map changes so you need those. There should be a warning in the console if you don't use unique keys. Do you have any duplicate ids? Also try passing all your data in as props instead of setting state, then you won't need a listener at all.
I have noticed a difference between the data before returning and after a return of a component.
class AComponent extends Component {
render() {
const body = <BComponent crmStatus={...}/>
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = <article className='large'>...</article>
debugger // log resultRender on the left
return resultRender
}
}
My former question was going to be "How to read rendered component's className?", but I have split the questions as answering what is actually happening and why is it like that really started to bug me and might even give me hints to solve my problem.
So the question is:
What is actually happening to the component and why is it like that? I can have really complicated logic in my render() function, but I guess working with the components isn't that easy.
const headerContact = isContactInCRM ? <p>..</p> : <div>..</div>
const headerCallBtnsOrInfo = isSipEnabled && <div>..buttons..</div>
const callTimer = callDuration && <span>{callDuration}</span>
const footerNotes = <footer>..</footer>
const someImportedComponent = <MyComponent />
const resultRender = <section>
{headerContact}
{headerCallBtnsOrInfo}
{callTimer}
{footerNotes}
{someImportedComponent}
</section>
// there is a difference in data between headerContact and someImportedComponent
// when traversing the resultRender's tree in console
Before answering the question, it's worth to look at what is JSX. It just provides syntactic sugar for the React.createElement(component, props, ...children) function.
<div>
<MyComponent/>
</div>
As an example, above JSX snippet will be transformed to following JavaScript code in the compilation process.
React.createElement(
"div",
null,
React.createElement(MyComponent, null)
);
You can try out this using Babel online repl tool. So if we rewrite your example code using normal JavaScript (after compiling JSX), it will be something like this.
class AComponent extends Component {
render() {
const body = React.createElement(BComponent, { crmStatus: '...' });
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = React.createElement('article',{ className: 'large' }, '...' );
debugger // log resultRender on the left
return resultRender
}
}
By looking at above code, we can understand that <BComponent crmStatus={...}/> doesn't create a new object of BComponent class or call render method of BComponent. It just create a ReactElement with BComponent type and crmStatus prop. So what is a ReactElement? ReactElement is a pain JavaScript object with some properties. I recommend you to read this post from official React blog to get an in-depth understanding of React components, elements, and instances.
An element is a plain object describing a component instance or DOM node and its desired properties. It contains only information about
the component type (for example, a Button), its properties (for
example, its color), and any child elements inside it.
Basically, what you have printed in the console is two React elements in different types. The left one is describing DOM node with type 'article' and the right one is describing BComponent type React component instance. So simply you can't expect them to be the same.
Then where does React create an instance of BComponent? Actually, this happens internally in the React code. Usually, we don't have access to these instances or what return by their render methods in our application code.
However, React still provide an escape hatch called 'refs' which you can explicitly access instances of child components. You might be able to use that approach to solve your original problem.
Hope this helps!