In the ArticlesGrid example we have the following render function:
render () {
return this.state.articles && (
<div className='articles'>
{ this.state.articles.map( function (article) {
return <Article article={article} key={article.id} />;
})}
</div>
);
}
So we have a component Article which has as one of it's props article which equals the object article, so far so good
Could someone explain what's going on with the next statement:
var Article = function({article}) {
why isn't it
var Article = function(this.props.article) {
??
in fact, when I try to console.log this.props I get undefined. I thought Article was a component too.
UPDATE: Here's the entire code:
class ArticlesGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
articles: []
};
}
componentDidMount () {
var url =
'http://api.nytimes.com/svc/search/v2/articlesearch.json?'
+ 'api-key=d68154fxxxxxxxxxxxxxxxxc7f5';
$.getJSON(url, function(data, status) {
return this.setState({articles: this.parse(data)});
}.bind(this));
}
parse(results) {
if(!results || !results.response) return [];
var articles = results.response.docs;
var parsedArticles = [];
for (var i = 0; i < articles.length;i++){
var article = articles[i];
if (article.multimedia.find(this.isXL)) {
parsedArticles.push({
id: article._id,
title: article.headline.main || 'Untitled',
imageURL: article.multimedia.find(this.isXL).url || '#',
webURL: article.web_url || '#'
});
}
}
return parsedArticles;
}
isXL (image) {
return image.subtype === 'xlarge';
}
render () {
return this.state.articles && (
<div className='articles'>
{ this.state.articles.map( function (article) {
return <Article article={article} key={article.id} />;
})}
</div>
);
}
}
var Article = function({article}) {
var imgURL = 'https://static01.nyt.com/' + article.imageURL;
return (
<div className='article'>
<a className='article-link' href={article.webURL}>
<img className='article-image'
title={article.title}
src={imgURL} />
</a>
</div>
);
}
ReactDOM.render(<ArticlesGrid/>,document.getElementById('container'));
when I try to console.log this.props I get undefined. I thought
Article was a component too.
Yes Article is a component too but it is a functional component. props to this are passed as argument to this function. So if you want to log props of this component then you can do it following way
var Article = function(props) {
console.log(props)
var imgURL = 'https://static01.nyt.com/' + article.imageURL;
What is var Article = function({article}) {
This is called object destructuring. It means that if an object is passed to this function then take article property of that object. So basically
var Article = function({article}) {
roughly means
var Article = function(props) {
var article = props.article
...
Related
i have a prent comp and a child cmponent. as follows
parent
export class ExpressionMenu extends Component {
constructor(props) {
super(props)
}
state = {
apiArray: [],
}
updateStateFromChild = (arrayType, propertyType, value) => {
let { apiArray } = this.state
let currentArray = []
let idx = apiArray.findIndex((q) => q.id === id)
currentArray = apiArray
switch(propertyType) {
case 'updateObject': {
currentArray = value
break;
}
}
this.setState({
apiArray: currentArray
})
}
render () {
const {
apiArray
} = this.state
return (
<React.Fragment>
<div >
<div>
<ApiPanel
apiArray={apiArray}
updateStateFromChild={this.updateStateFromChild}
/>
</div>
</div>
</React.Fragment>
)
}
}
ExpressionMenu.propTypes = {
styleOverride: PropTypes.object,
eventHandler: PropTypes.func,
};
export default ExpressionMenu;
child
export class ApiPanel extends Component {
constructor(props) {
super(props),
}
removeApi = (id) => {
let { apiArray } = this.props
apiArray = apiArray.filter((q) => q.id !== id);
this.props.updateStateFromChild('api', 'updateObject', apiArray)
};
addApi = () => {
let { apiArray } = this.props
const id = uniqid();
let obj = {}
obj.id = id
apiArray.push(obj)
this.props.updateStateFromChild('api', 'updateObject', apiArray)
};
render() {
const { apiArray } = this.props
return (
<React.Fragment>
{
apiArray.map((apiObj, i) =>
<div key={i} >
<span onClick={() => this.removeApi(apiObj.id) } className={[classes.deleteRow,'material-icons'].join(' ')}>
close
</span>
<div>
<label><b>Hi</b></label>
</div>
<div onClick={this.addApi}>+Add Api</div>
}
</React.Fragment>
)
}
}
ApiPanel.propTypes = {
apiArray: PropTypes.array,
updateStateFromChild: PropTypes.func
}
export default ApiPanel
Now when i call addApi(), it updates the parent but doesnt rerenders the child.
But when i call removeApi() , it updates parent as well as rerenders the child component properly.
in the first case when i manually reload the componnt i can see the change.
Dont understand why this is happening
Try to change your addApi function.
addApi = () => {
let { apiArray } = this.props
this.props.updateStateFromChild('api', 'updateObject', [...apiArray, {id : uniqid()} ])
};
You need to return an enriched copy of your array
Whenever we are updating the stating using arrays, objects. We need to always create a new array [...array], a new object {...obj}. Because if we update value in the array or obj without creating it won't change the reference value hence it assumes the state is not update and won't re-render.
I am trying to display both of these arrays. Everytime you click the button a new user is generated and I added to the array. I would like for it to render the entire array and not only the newest lines. I have tried many things. I keep seeing online about mapping but I can not get any of that to work correctly.
import React from "react";
import ReactDOM from "react-dom";
import $ from "jquery";
var personlist = [];
var imglist = [];
var i = 0;
class People extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "firstlastname",
image: "image"
};
}
static getDerivedStateFromProps(props, state) {
getUser = () => {
var usersname;
var thumbnail;
$.ajax({
url: "https://randomuser.me/api/?inc=name,picture",
data: "json",
async: false,
success: function (data) {
usersname = JSON.stringify(
data.results[0].name.first + " " + data.results[0].name.last
);
thumbnail = JSON.stringify(data.results[0].picture.thumbnail);
newUser = usersname;
newImage = thumbnail;
var eventstring = new String(); //remove quotes from image string
eventstring = newImage.toString().replace(/"/g, ""); //remove quotes from image string
personlist.push([newUser, eventstring]);
imglist.push([eventstring]);
console.log(personlist);
},
error: function (errormsg) {
console.log(errormsg);
}
});
};
this.getUser();
var eventstring = new String(); //remove quotes from image string
eventstring = newImage.toString().replace(/"/g, ""); //remove quotes from image string
return { name: newUser, image: eventstring };
}
changeColor = () => {
//change state to rerender
this.setState({ name: "updt" });
i++;
return { name: "updt" };
};
render() {
return (
<div>
<button onClick={this.changeColor}>Generate New Person</button>
<div>
<h1>{personlist[i][0]}</h1>
<img src={personlist[i][1]} alt="none" />
</div>
</div>
);
}
}
export default People;
React components can store data in their state variable and in classes, you can access the state using this.state and if you want to modify the state, you can use this.setState function. You can read more on the react site.
Below is the refactored code you provided where I've initialized the people array in the state. And in the fetchPerson method, you can see it after fetching the data, it calls this.setState method to update the current state which will cause the component to re-render. I've replaced the changeColor method to onGetNewPerson which you used to render the component in order to get a new person. In the render method, you can see I am using the map function which is available to arrays, you can read more about this in the Mozilla Javascript documentation. If you don't know about classes, you can check about functional components.
class People extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "firstlastname",
image: "image",
people: [],
};
this.onGetNewPerson = this.onGetNewPerson.bind(this);
}
componentDidMount() {
this.fetchPerson();
}
fetchPerson() {
$.ajax({
url: "https://randomuser.me/api/?inc=name,picture",
data: "json",
async: false,
success: function (data) {
// if you want to replace the people array in the state
// this.setState({ people: data.results });
// if you want to append to the people array
this.setState((prevState) => {
return {
people: prevState.people.concat(data.results)
};
});
}
});
}
parsePerson(person) {
const name = JSON.stringify(
person.name.first + " " + person.name.last
);
const image = JSON.stringify(person.picture.thumbnail).toString().replace(/"/g, "");
return { name, image };
}
onGetNewPerson = () => {
// on click => fetch and append new person
this.fetchPerson();
};
render() {
return (
<div>
<button onClick={this.onGetNewPerson}>Generate New Person</button>
<div>
{this.state.people.map((person, i) => {
const p = this.parsePerson(person);
return (
<div key={"person-" + i}>
<h1>{p.name}</h1>
<img src={p.image} alt="none" />
</div>
);
})}
</div>
</div>
);
}
}
Front End - Front End
Upon clicking the star, I want to update the state of nested object, with the new rating value of star.
I tried many things but it didnt work as states are immutable.
Nested State
Can some upon please suggest how can I update the value in nested object
onStarClicked = (kTypName, subItemId1, newRating) => {
//console.log(subItemId.split("_"));
let evaluation = subItemId1.split("_")[0];
let subItemId = subItemId1.split("_")[1];
console.log(subItemId);
const r = { ...this.state.ratings };
let kT = r.knowledgeTypes;
let sub = '', kTN = '', kIN = '';
kT.map(knowledgeType => {
//console.log(knowledgeType.knowledgeTypeId);
knowledgeType.knowledgeItems.map(knowledgeItem => {
//console.log(knowledgeItem.knowledgeItemId);
knowledgeItem.subItems.map(knowledgeSubItem => {
//console.log(knowledgeSubItem.subItemId);
if (subItemId === knowledgeSubItem.subItemId) {
kTN = knowledgeType.knowledgeTypeName;
kIN = knowledgeItem.knowledgeItemName;
sub = knowledgeSubItem;
if (evaluation === "self") {
sub.evaluation.self.rating = newRating;
}
else if (evaluation === "evaluator") {
sub.evaluation.evaluator.rating = newRating;
}
//alert(evaluation + subItemId + ' ' + newRating);
//return;
}
})
})
});
this.setState({
...this.state,
ratings: {
...this.state.ratings,
knowledgeTypes: [
...this.state.ratings.knowledgeTypes,
this.state.ratings.knowledgeTypes.filter(kt => kt.knowledgeTypeName !== kTN),
{
...this.state.ratings.knowledgeTypes.knowledgeItems.
filter(ki => ki.knowledgeItemName !== kIN),
knowledgeItems: {
...this.state.ratings.knowledgeTypes.knowledgeItems.subItems.
filter(si => si.subItemId !== subItemId),
sub
}
}]
}
});
}
You basically have to create a new empty array of knowledgeTypes and use the current state to find which item of the state you need to change using Object.keys/map/filter functions.
You'd use the current state in a variable and modify that variable only. You'd likely not mess with the actual state object in any way.
After you have done that, simply append it to the empty array. Then you can setState() the new array to the actual state property.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
financialYear: "2019-20",
quarter: "Q1",
isCurrentQuarter: true,
knowledgeTypes: [
{
knowledgeTypeName: "Technology",
knowledgeItems: [
{
knowledgeItemName: "Java",
subItems: [
{
subItemId: "2",
subItemName: "Collections",
evaluation: {
self: {
ntnet: "Joe",
rating: 1,
isEditable: true
}
}
}
]
}
]
}
]
};
}
handleClick = e => {
const { knowledgeTypes } = this.state;
// transformation
const itemToChange = knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
return item;
}
});
const currItems = itemToChange[0].knowledgeItems[0].subItems;
const subItem = currItems.map(item => {
if (item.subItemId === "2") {
return item;
}
});
const person = subItem[0].evaluation;
person.self.rating = 55; //change
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
newKnowledgeTypes.push(itemToChange);
}
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
};
render() {
return (
<div>
MyComponent
<button onClick={this.handleClick}>Hello</button>
</div>
);
}
}
The sandbox can be found on https://codesandbox.io/s/musing-dew-8r2vk.
Note: It is advisable you do not use nested state objects because state objects are something more lightweight so that they do not have performance considerations.
import React, { Component } from 'react';
import Auxilary from '../../../hoc/Auxilary/auxilary';
import KnowledgeItems from '../KnowledgeItems/KnowledgeItems';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import knowledge from '../../../assests/staticdata.json';
import './QuarterLog.css';
class QuarterLog extends Component {
constructor() {
super();
this.state = {
"financialYear": "",
"quarter": "",
"isCurrentQuarter": "",
"knowledgeTypes": []
}
}
onStarClicked = (kTypName, kItemName, subItemIdName, newRating) => {
let evaluation = subItemIdName.split("_")[0];
let subItemId = subItemIdName.split("_")[1];
const { knowledgeTypes } = this.state;
// transformation
let knowledgeTypeToChange = knowledgeTypes.map(kType => {
if (kType.knowledgeTypeName === kTypName) {
return kType;
}
});
knowledgeTypeToChange = knowledgeTypeToChange.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeTypeToChange[0]);
let knowledgeItemToChange = knowledgeTypeToChange[0].knowledgeItems.map(item => {
if (item.knowledgeItemName === kItemName) {
return item;
}
});
knowledgeItemToChange = knowledgeItemToChange.filter(function (element) {
return element !== undefined;
});
let knowledgeSubItem = knowledgeItemToChange[0].subItems.map(subItem => {
if (subItem.subItemId === subItemId) {
return subItem;
}
});
knowledgeSubItem = knowledgeSubItem.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeSubItem);
let personEvaluations = knowledgeSubItem[0].evaluation;
if (evaluation === "self") {
personEvaluations.self.rating = newRating.toString(); //change
}
else if (evaluation === "evaluator") {
personEvaluations.evaluator.rating = newRating.toString(); //change
}
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === kTypName) {
newKnowledgeTypes.push(knowledgeTypeToChange[0]);
}
else
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
}
componentDidMount() {
// TODO: remove staticdata.js and call REST API and set the response in state
this.setState({
...this.state,
"financialYear": knowledge.financialYear,
"quarter": knowledge.quarter,
"isCurrentQuarter": knowledge.isCurrentQuarter,
"knowledgeTypes": knowledge.knowledgeTypes
})
}
onSubmitRatings = () => {
console.log(this.state);
}
render() {
let data = knowledge; //remove this code, once REST API is implemented
const posts = this.state.knowledgeTypes.map(knowledgeType => {
return (
<Tab key={knowledgeType.knowledgeTypeName} eventKey={knowledgeType.knowledgeTypeName}
title={knowledgeType.knowledgeTypeName}>
<KnowledgeItems
kTypeName={knowledgeType.knowledgeTypeName}
kItems={knowledgeType.knowledgeItems}
ratings={this.state.ratings}
onstarclicked={this.onStarClicked}
/>
</Tab>)
});
return (
<Auxilary>
<div className="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div><h1>Financial Year : {data.financialYear}</h1></div>
<div><h2>Quarter : {data.quarter}</h2></div>
</div>
<div>
<Tabs defaultActiveKey="Domain" id="uncontrolled-tab-example">
{posts}
</Tabs>
</div>
<button onClick={this.onSubmitRatings}> Submit </button>
</Auxilary>
);
}
}
export default QuarterLog;
In method render() I call cycle with the children elements with this.state.objects.map(item, id)
conversationTemplate = this.state.objects.map(function (item, index) {
return (
<Panel eventKey={index}>
<h4> {item.msg_text} </h4>
<ReactMessageChat conversationId={item.pk}> {item.pk} </ReactMessageChat>
</Panel>
)
}
When new item appears render reboot, but prop conversationId in child element ReactMessageChat always stays as the first in cycle.
class ReactMessageChat extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: [],
loaded: false,
};
this.addNewMsg = this.addNewMsg.bind(this);
this.subscribe = this.subscribe.bind(this);
}
subscribe() {
return centrifuge;
}
loadDataFromServer(conversationId) {
let self = this;
var url = '/api/support_messages/?msg_conversation=' + conversationId
...
}
addNewMsg(message) {
let self = this;
if (message.msg_conversation == self.props.conversationId) {
var newArray = self.state.objects;
newArray.push(message);
self.setState({objects: newArray});
}
}
componentDidMount() {
this.loadDataFromServer(this.props.conversationId);
this.subscribe();
}
render() {
let self = this;
var messagesTemplate;
if (self.state.objects.length > 0) {
let reversedObjects = self.state.objects.reverse();
messagesTemplate = reversedObjects.map(function (item, index) {
return (
<li className="list-group-item" key={index}>
{item.msg_text}</li>
)
})
}
It happens because of in different methods of cycle you have to give attribute "key" to repetitive element.
https://facebook.github.io/react/docs/lists-and-keys.html
Try show in table my array. Array get from AJAX request in Action. I'm using Redux
class IncomeProfile extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.props.IncomeListProfile();
}
render() {
var elems = this.props.items.course_list;
console.log(elems);
return (
<div>
<table>
{elems.map((item) => (
<tr key={item.course_id}>
<td>{item.name}</td>
</tr>
))}
</table>
</div>
)
}
}
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList())
}
}
const mapStateToProps = function(state) {
var mystore = state.toArray();
//console.log(mystore[6]);
return {
items: mystore[6]
};
}
export default connect(mapStateToProps, mapDispatchToProps)(IncomeProfile);
Console.log first print "undefined" then it print this:
Try add condition in render method if (elems) { } not helps
Make use of the following
var elems = this.props.items['course_list'];
var copy = Object.assign({}, elems);
console.log(elems.course_list);