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.
I am following a tutorial and they set a static object named defaultprops and after setting it this.props could be used in the same component, Why is this possible, I mean what's the function of the static defaultProps and is it a bulit in function in React.
class TestComponent extends Component {
static defaultprops = {
food: ["goatmeat", "yam"]
}
render() {
let categories = this.props.defaultprops.food.map(foods => {
return <option key={foods}>{foods}</option>
});
let {test} = this.props;
return (
<p>
{this.props.test}
</p>
);
};
}
Default props are nice to not have to specify all of the props when passing them to a component. Just as the name implies, it allows you to set nice defaults for your props which will be used in the even that overriding values are not passed in. Please keep in mind that leaving out the prop will result in the default value being used, whereas passing in null will result in the null value to be used.
More info may be found here.
Edit
To answer the questions asked more explicitly:
This is possible because this is how React works. This is in-built functionality for programmer and logical convenience.
For the TestComponent in your example, imagine that it is used in another component. If you just use <TestComponent />, the component will have a food value of ["goatmeat","yam"]. However, you may always override this as you wish by passing in a different value for the prop when calling it. For example, you could use <TestComponent food={["cheese", "eggs", "cabbage"]}/>. This will result in this instance of the component having the food value of ["cheese", "eggs", "cabbage"].
I think it is also a good point to note that it should be defaultProps and not defaultprops because I am fairly certain capitalization matters but if anyone would like to correct me I would be happy to redact this point.
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 am encountering several issues in a very basic color harmony picker I am developing. I am still a beginner in React and JSX. I initially had it put up on GitHub so the full files are on there, but I moved it over to Codepen instead.
Here is the Codepen
I made a lot of comments so sorry if they're a bit much, but hopefully they help. My problems don't begin until line 41, the displayHarmonies() method of the DataStore class. The values passed to it come from my App (parent) component:
displayHarmonies(color, harmony) {
//color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.data);
this.registeredWatchers.map((watcher) => {
let result = "not green"; //result and resultHex will be determined with an underscore statement that will associate the color & harmony choice (primary + foreign key concept) and will return correct harmony color(s)
let resultHex = "#HEX";
appState.harmonyColor = result;
appState.harmonyHex = resultHex;
//call to app component's onDataChange() method, where new states will be set using the the appState data we just set in lines 49 and 50
watcher.onDataChange();
})
}
As you can see from my first comment, the only part that doesn't log to the console is this.data, which is set in the constructor for the DataStore:
constructor(data) {
//store that data in the object
//data is not being received from object instance of dataStore on line 187
this.data = data;
On line 187 I make an instance of the DataStore and pass it a variable named data. Prior to being used, this variable is initialized and then assigned to parsed JSON data via Fetch API:
let data = [];
//use polyfill for older browsers to do Ajax request
fetch("data/data.json").then((response) => {
//if we actually got something
if (response.ok) {
//then return the text we loaded
return response.text();
}
}).then((textResponse) => {
data = JSON.parse(textResponse);
});
If I console out the data in the second fetch .then() method, the JSON comes back just fine. As soon as I try to use the data variable anywhere else in the application, it returns nothing, as shown in the displayHarmonies() method's console.log(). So that's my first issue, but before I wanted to get to that, I wanted to solve the other issue I was having.
After the appState object (initialized prior to the DataStore, under the fetch statement) values get set to the result variables, displayHarmonies() runs watcher.onDataChange() (in the App component/parent) where the harmonyColor and harmonyHex states get assigned to the new appState values:
onDataChange() {
console.log("onDataChange() in App called");
this.setState({
harmonyColor: appState.harmonyColor,
harmonyHex: appState.harmonyHex
})
}
If I log these states out to the console, they are the right values, so that's not the problem. I then pass my states to the Display child component to be used as properties:
<Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} />
I then set the Display component states in the constructor, assigning them to the props that are being sent to it with each new rendition of the application. I then display the data onto the DOM with the Display component's render method. What's odd is that the application will display the initial states (color: red, harmony: direct, harmonyColor: green, etc.) just fine, but as soon as a change is made, the data on the DOM does not update. The initial data is loaded in the same way though: by passing the parent's states into the child's properties. I have a few console.log()s in place that seem to prove why this should work, however, it does not. So what am I doing wrong?
Thanks, and hope this is not too much for one question!
First a bit to your current code, at the end of the post, I have added an alternative solution, so if this is tl;dr; just skip to the snippet at the end :)
A first remark would be on the data variable that you wish to pass on to your DataStore, nl (I left out some parts, as they are irrelevant to the discussion)
let data = [];
fetch("data/data.json").then(( response ) => {
data = JSON.parse( response.text() );
});
//... later down the code
var store = new DataStore(data);
Here you are reassigning the data variable inside the then promise chain of your fetch call. Although the assignment will appear to work, the data that now is on store.data will be an empty array, and the global variable will data will now contain the parsed response.text(). You should probably just push in the data you have just parsed (but in my example, I didn't even include the DataStore so this is just for future reference)
In your CodePen, you seem to mixing props & state for your Display component. That is in essence a no-op, you shouldn't mix them unless you really know what you are doing. Also note, that by calling this.setState inside the componentWillReceiveProps life cycle method, the app will automatically re-render more than needed. I am referring to this code:
componentWillReceiveProps(nextProps) {
this.setState({
color: nextProps.colorChoice,
harmony: nextProps.harmonyChoice,
harmonyColor: nextProps.harmonyColor,
harmonyHex: nextProps.harmonyHex
});
}
But you are then rendering like this:
render() {
return (
<div>
{/* these aren't changing even though states are being set */}
<p><b>Color:</b> {this.state.color}</p>
<p><b>Harmony:</b> {this.state.harmony}</p>
<p><b>Harmony Color(s):</b> {this.state.harmonyColor} ({this.state.harmonyHex})</p>
</div>
)
}
Here you should remove the componentWillReceiveProps method, and render values from this.props as you are passing these along from your App.
Alternative solution
As mentioned in the comments, your code currently is doing a lot more than it should do to pass state between parent and child components.
One thing you should keep in mind, is that when a component state gets changed, react will re-render the component automatically. When it sees that the virtual DOM has discrepancies with the real DOM it will automatically replace those components.
In that sense, your DataStore is not necessary. Depending on how you want to manage state, the component will react on those changes.
Since your app uses Component State (which is fine for small applications, once you want to move to bigger applications, you will probably want to move on to something like Redux, or MobX), the only thing you need to do, is to make sure that you set the correct components state to trigger the rendering.
As an example, I remade your code in a cleaner way:
const Choice = ({ header, values, onChange, activeValue }) => {
return <ul>
<li><h1>{ header }</h1></li>
{ values.map( (value, key) => <li
key={key+value}
className={classNames( { active: value === activeValue, item: true } )}
onClick={() => onChange( value )}>{ value }</li> ) }
</ul>
};
const colors = ['red', 'green', 'black', 'blue', 'yellow'];
const harmonies = ['direct', 'split', 'analogous'];
class App extends React.Component {
constructor(...args) {
super(...args);
this.state = {
activeColor: undefined,
activeHarmony: undefined
};
}
onColorChanged( color ) {
this.setState({ activeColor: color });
}
onHarmonyChanged( harmony ) {
this.setState({ activeHarmony: harmony });
}
render() {
let { activeColor, activeHarmony } = this.state;
return <div>
<Choice
header="Choose color"
values={colors}
activeValue={activeColor}
onChange={(...args) => this.onColorChanged(...args)} />
<Choice
header="Choose harmony"
values={harmonies}
activeValue={activeHarmony}
onChange={(...args) => this.onHarmonyChanged(...args)} />
</div>;
}
}
ReactDOM.render( <App />, document.querySelector('#container'));
h1 { margin: 0; padding: 0; }
ul {
list-style-type: none;
}
.item {
cursor: pointer;
padding: 5px;
}
.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
Now, there are some things in this sample code that might need some explanation. For one, this code has 2 component types, 1 presentational component called Choice which is stateless, and one container component called App which delegates it's state to it's children.
A bit more information about container & presentational components can be found on the blog of Dan Abramov (redux creator)
The essence of the above concept is just this, the App component is responsible for the state, and for sharing it with it's children. So, all state changes need to be made on the App component. As you can see in the render, the App simply passes its state along:
render() {
let { activeColor, activeHarmony } = this.state;
return <div>
<Choice
header="Choose color"
values={colors}
activeValue={activeColor}
onChange={(...args) => this.onColorChanged(...args)} />
<Choice
header="Choose harmony"
values={harmonies}
activeValue={activeHarmony}
onChange={(...args) => this.onHarmonyChanged(...args)} />
</div>;
}
The App passes a change handler along to the Choice component that can be called when a selection should occur, this gets forwarded to the App, the state changes, and app re-renders, allowing the Choice component to update it's elements.
const Choice = ({ header, values, onChange, activeValue })
Based on the props passed into it, the Choice component can decide which is the active item at the moment of rendering. As you can see, the props are destructed. header, values, onChange and activeValue are all properties on the props of the component, but to save time, we can assign these values at ones to a variable and use them in the rendering.
I tried cloning your repo, but it seems to be nested in another repo. With your current setup, this may work:
In your App component, you can put this lifecycle method to fetch the data, and then set the state with the received data.:
componentDidMount(){
fetch("data/data.json").then((response) => {
//if we actually got something
if (response.ok) {
//then return the text we loaded
return response.text();
}
}).then((textResponse) => {
this.setState({
data : JSON.parse(textResponse);
})
});
}
In the return statement, you can render the data store as a child so App can pass the data like this:
return (
<div className="App">
<DataStore data={this.state.data} />
<h1>Color Harmonies</h1>
{/* assigns this.colorChosen() & this.harmonyChosen() methods as properties to be called in Picker component */}
<Picker colorChosen={this.colorChosen.bind(this)} harmonyChosen={this.harmonyChosen.bind(this)}/>
{/* give Display component props that are dynamically set with states */}
<Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} />
</div>
);
Then, your data store should receive the data as a prop, so you can use it like this:
displayHarmonies(color, harmony) {
//color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.props.data); //data is received in the properties so you can use it.
//other code
})
Doing this, you should also be able to remove this.data from the constructor of the DataStore component.
Also in Data store, youll want to to allow it to accept props like this:
constructor(props){
super(props)
}
I want to check all properties and state if they are changed, return true if any changed and make a base component for all my root components.
I'm wondering if it won't be the best practice and make my components slow.
Also, what I did always returns true:
shouldComponentUpdate: function(newProps, newState) {
if (newState == this.state && this.props == newProps) {
console.log('false');
return false;
}
console.log('true');
return true;
},
Is there anything wrong with my code?
Should I check for every variable inside props and state?
Won't check for objects inside them make it slow depending on their size?
It is considered best practice to compare props and state in shouldComponentUpdate to determine whether or not you should re-render your component.
As for why it's always evaluating to true, I believe your if statement isn't performing a deep object comparison and is registering your previous and current props and state as different objects.
I don't know why you want to check every field in both objects anyway because React won't even try to re-render the component if the props or state hasn't changed so the very fact the shouldComponentUpdate method was called means something MUST have changed. shouldComponentUpdate is much better implemented to check maybe a few props or state for changes and decide whether to re-render based on that.
I think there's a problem in most of the tutorials I've seen (including the official docs) in the way that stores are accessed. Usually what I see is something like this:
// MyStore.js
var _data = {};
var MyStore = merge(EventEmitter.prototype, {
get: function() {
return _data;
},
...
});
When I used this pattern, I found that the newProps and newState in functions like shouldComponentUpdate always evaluate as equal to this.props and this.state. I think the reason is that the store is returning a direct reference to its mutable _data object.
In my case the problem was solved by returning a copy of _data rather than the object itself, like so:
get: function() {
return JSON.parse(JSON.stringify(_data));
},
So I'd say check your stores and make sure you're not returning any direct references to their private data object.
There is a helper function to do the comparison efficiently.
var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}