How to render object that pass through props in ReactJS? - javascript

I am trying to render the data (object) that comes through props. However, I have got the following error:
Uncaught TypeError: Cannot convert undefined or null to object Some how, I do not know why the data or object is null although the state of the data is updated during componentDidMount(). Would you help me why the data is null?
Please look class A to see how the data is consumed
class A extends React.Component {
state = {
data: null
};
componentDidMount() {
this.data = this.props.location.state.data;
this.setState({ data: this.props.location.state.data });
}
render() {
return (
<div>
{Object.keys(this.data).map((key, index) => (
<p key={index}> value is {this.data[key]}</p>
))}
hello
</div>
);
}
}
A.propTypes = {
data: PropTypes.object,
location: PropTypes.object
};
export default A;
Assume, this.data contains the data in the following format
{
id: 1,
userName: "ABDXY",
date: "01/12/2020",
time: "21:00"
}

this.data is not defined. You can access the data that is set in the state using this.state.data
Please ensure that this.props.location.state.data is not null
class A extends React.Component {
state = {
data: {}
};
componentDidMount() {
// this.data = this.props.location.state.data; => not required.
this.setState({
data: this.props.location.state.data
});
}
render() {
return ( <
div > {
Object.keys(this.state.data).map((key, index) => ( <
p key = {
index
} > value is {
this.state.data[key]
} < /p>
))
}
hello <
/div>
);
}
}

Get data from state instead of this.data since it will not trigger render when this.data will update. Also use {} as default value
class A extends React.Component {
state = {
data: {}
};
componentDidMount() {
const data = {
id: 1,
userName: "ABDXY",
date: "01/12/2020",
time: "21:00"
};
this.setState({ data });
}
render() {
const { data } = this.state;
return (
<div>
{Object.keys(data).map((key, index) => (
<p key={index}> value is {data[key]}</p>
))}
hello
</div>
);
}
}
export default A;

Related

Parent scope not triggering child rerender in React

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.

what's the reason to get this error in React js - TypeError: this.state.data.map is not a function

I am new to React js and I am trying to fetch data from API and I am getting all data in this.state but while displaying in dropdown I am getting an above error. Please go through the code and let me know where I went wrong.
Json data:
{
"status":true,
"message":"Success",
"data":{
"id":37,
"pan_no":"12345",
"pan_name":"abhishek",
"pan_front_image":"C:\\fakepath\\download.jpg",
"customer_image":"C:\\fakepath\\download.jpg",
"document_type":"Driving License",
"document_front_image":"C:\\fakepath\\download.jpg",
"document_back_image":"C:\\fakepath\\download.jpg",
"bank_name":"ada",
"account_no":"12345",
"confirmed_acc_no":"12345",
"ifsc_code":"MITM2250451",
"account_holder_name":"fasfdas",
"phone_no":"1234567890",
"nick_name":"213123"
}
}
Code:
import React, { Component } from 'react';
class Home extends Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
fetch("http://127.0.0.1:8003/api/kyc/")
.then(results => results.json())
.then(data => this.setState({ data: data }));
// .catch(()=>this.setState({hasErrors:true}));
}
render() {
console.log(this.state.data);
return ( <div>
<div class = "ab-info-con" >
<h4> Menu </h4>
<select> {
this.state.data.map((obj) => { <
option value = { obj.id } > { obj.pan_no } </option>
})
} </select>
</div>
</div>
)
}
}
export default Home;
The error is being thrown because .map is an array method and you are calling it on an object. From the structure of your response and your code, I can guess that what you should do is this.
import React, {Component} from 'react';
class Home extends Component {
constructor(){
super();
this.state = {
id: '',
panNo: ''
};
}
componentDidMount() {
fetch("http://127.0.0.1:8003/api/kyc/")
.then(results=>results.json())
.then(data=>this.setState({ id: data.data.id, panNo: data.data.pan_no }));
// .catch(()=>this.setState({hasErrors:true}));
}
render(){
return (
<div>
<div class="ab-info-con">
<h4>Menu</h4>
<select>
{
<option value={this.state.id}>{this.state.panNo}</option>
}</select>
</div>
</div>
)
}
}
export default Home;
import React, { Component } from 'react';
class Home extends Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentDidMount() {
fetch("http://127.0.0.1:8003/api/kyc/")
.then(results => results.json())
.then(data => this.setState({ data: data }));
// .catch(()=>this.setState({hasErrors:true}));
}
renderOptions = () => {
const { data } = this.state;
let options = [];
if (typeof data !== "undefined") {
if (Object.keys(data).length === 0) {
for (const property in data) {
options.push(<option value={property.id}>{property.pan_no}</option>)
}
}
}
return options;
}
render() {
console.log(this.state.data);
return (
<div>
<div class="ab-info-con">
<h4>Menu</h4>
<select>
{this.renderOptions()}
</select>
</div>
</div>
)
}
}
export default Home;
try this, i have added the empty check before looping the data.
Your example doesn't need mapping at all, as you are having only one record. To get the id and panNo you should use this.state.data.data.id and this.state.data.data.panNo.
If you would need to use map() on an object, I would suggest using Object.keys:
Instead of this:
<select> {
this.state.data.map((obj) => { <
option value = { obj.id } > { obj.pan_no } </option>
})
} </select>
Use this (doesn't make sense with this data structure, only to show the idea):
<select> {
Object.keys(this.state.data).map((obj) => { if(obj == 'data'){ <
option value = { this.state.data[obj].id } > { this.state.data[obj].pan_no } </option>
}})
} </select>

In React Context, how can I use state variables in state functions?

I have a React Context which looks like this:
import React, { Component } from 'react'
const AlertsContext = React.createContext({
categoryList: [],
setCategoryList: () => {}
})
export class AlertsProvider extends Component {
state = {
categoryList: [],
setCategoryList: categoryString => (
this.categoryList.includes(categoryString)
? this.setState({ categoryList: this.categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: this.categoryList.concat([categoryString]) })
)
}
render() {
const { children } = this.props
const {categoryList, setCategoryList } = this.state
return (
<AlertsContext.Provider value={{categoryList, setCategoryList}}>
{children}
</AlertsContext.Provider>
)
}
}
export const AlertsConsumer = AlertsContext.Consumer
So, categoryList is an array of strings, each representing a category. setCategoryList should take a string; if that string is already in the array, it removes it, and if it's not in the array it adds it.
In one of my components the user can select categories from a list of checkboxes. When a checkbox is clicked, the AlertsContext setCategoryList should be called with the value of the clicked box:
import React, { Component } from 'react'
import { AlertsConsumer } from '../../../context/alerts-context'
class AlertFilters extends Component {
constructor(props) {
super(props)
this.state = {
categories: props.categories
}
}
render() {
const { categories } = this.state
return (
<AlertsConsumer>
{({ categoryList, setCategoryList }) => (
<>
{
categories.map(category => (
return (
<div key={category.id}>
<Checkbox id={category.id} value={category.value} onChange={e => setCategoryList(e.target.value)} checked={categoryList.includes(category.value)} />
<label htmlFor={category.id}>{category.value}</label>
</div>
)
))
}
</>
)}
</AlertsConsumer>
)
}
}
export default AlertFilters
This compiles ok, but when I run it and click a checkbox I get the following error:
alerts-context.jsx:77 Uncaught TypeError: Cannot read property 'includes' of undefined
This is in the line:
this.categoryList.includes(categoryString)
in the Context Provider, suggesting that "this.categoryList" is undefined at this point.
I tried changing it to
this.state.categoryList.includes(categoryString)
but it said I had to use state destructuring, so I changed to:
setCategoryList: (categoryString) => {
const { categoryList } = this.state
categoryList.includes(categoryString)
? this.setState({ categoryList: categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: categoryList.concat([categoryString]) })
}
which highlighted the ternary operator and gave the following lint error:
Expected an assignment or function call and instead saw an expression.
What am I doing wrong?
Use if/else syntax to update the state.
setCategoryList: categoryString => {
const { categoryList } = this.state;
if (categoryList.includes(categoryString)) {
this.setState({
categoryList: categoryList.filter(value => value !== categoryString)
});
} else {
this.setState({ categoryList: categoryList.concat([categoryString]) });
}
};

Setting State with Objects from Firebase

I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;

How to access variable inside React Component? Initialized it outside Component

I'm currently a beginner working on a project in React/Redux. I'm trying to call JSON from an API file, save it as an array of objects, and then pass it into another file to start pulling data out of it. I recently got stuck in one place
Below is my class, which is accessing the JSON data and pulling it out to put into an array. I initialized the array outside of the class, but it's not being written to. I'm not really sure how to 'throw' the array that I need out of my class.
numberendpoint.json (an array of objects)
[
{
color: "red",
value: "#f00"
},
{
color: "green",
value: "#0f0"
},
{
color: "blue",
value: "#00f"
},
{
color: "cyan",
value: "#0ff"
},
{
color: "magenta",
value: "#f0f"
},
{
color: "yellow",
value: "#ff0"
},
{
color: "black",
value: "#000"
}
]
In index.js
let productJSON = [] //initialize productJSON array here
class Hue extends React.Component {
constructor() {
super();
this.state = {
elements: [],
productJSON: []
};
}
componentWillMount() {
fetch('numberendpoint.json')
.then(results => {
return results.json();
}).then(data => {
let colorArray = [] //initialize array to receive json data
for (let i =0; i < data.length; i++) {
colorArray.push(data[i])
}
productJSON = JSON.stringify(productArray) //here is where I try to assign the productJSON array
let elements = data.map((rainbow) => {
return (
<div key={rainbow.results}>
<p>{raindow.color}</p>
<p>{rainbow.value}</p>
</div>
)
})
this.setState({elements: elements});
console.log("state", this.state.elements[0]);
})
}
render() {
return (
<div>
<div className="container2">
{this.state.elements}
</div>
</div>
)}
}
How can I access the JSONproduct array? or alternatively, how do I 'pop' it out of this class so I can use it?
Update: used the solution suggested by Rahamin. Now I have this code below, all contained within the the "Hue" class. But I'm still getting errors.
import React from 'react'
const TIMEOUT = 100
let productJSON;
class Hue extends React.Component {
constructor() {
super();
this.state = {
products: [],
};
this.getColors = this.getColors.bind(this)
}
componentDidMount() {
fetch('http://tech.work.co/shopping-cart/products.json')
.then(results => {
return results.json();
}).then(data => {
let colorArray = []
for (let i =0; i < data.length; i++) {
colorArray.push(data[i])
}
console.log("jsonproduct=" + JSON.stringify(productArray))
productJSON = JSON.stringify(productArray)
this.setState({productJSON: productJSON});
});
}
render() {
return (
<div>
<div className="container2">
{this.state.productJSON}
</div>
</div>
)
}
}
export default {
getProducts: (cb, timeout) => setTimeout(() => cb(({ productJSON: value})), timeout || TIMEOUT), // here is where I am getting an error -- "value" is undefined. I'm not sure I was meant to put "value" there or something else...very new to React so its conventions are still foreign to me.
buyProducts: (payload, cb, timeout) => setTimeout(() => cb(), timeout || TIMEOUT)
}
let productJSON = [] //initialize productJSON array here
class Hue extends React.Component {
constructor() {
super();
this.state = {
elements: [],
productJSON: []
};
}
componentDidMount() {
fetch('numberendpoint.json')
.then(res => {
this.setState({elements: res.data});
})
}
render() {
if(this.state.elements.length > 0){ //once the data is fetched
return (
<div>
<div className="container2">
{this.state.elements.map((rainbow) => {
return (
<div key={rainbow.results}>
<p>{raindow.color}</p>
<p>{rainbow.value}</p>
</div>
)
})}
</div>
</div>
)
}
else{ // initial render
return null;
}
}
I don't really understand why you are trying to put array OUTSIDE of a class but I think you need to understand when each event gets called in React.
componentDidMount is an event that gets called when all the components have mounted in the class. So at this stage, render() function has already run. Which means your productJSON is undefined at this stage. What you really wanna do is that make sure your component changes when the state gets updated to something other than undefined.
Try the following code.
let productJSON = [] //initialize productJSON array here
class Hue extends React.Component {
constructor() {
super();
this.state = {
elements: [],
};
}
componentWillMount() {
fetch('numberendpoint.json')
.then(results => {
return results.json();
}).then(data => {
let colorArray = [] //initialize array to receive json data
for (let i =0; i < data.length; i++) {
colorArray.push(data[i])
}
this.setState({productJSON:colorArray});
let elements = data.map((rainbow) => {
return (
<div key={rainbow.results}>
<p>{raindow.color}</p>
<p>{rainbow.value}</p>
</div>
)
})
this.setState({elements: elements});
console.log("state", this.state.elements[0]);
})
}
render() {
return (
<div>
<div className="container2">
{this.state.productJSON ? 'state not ready' : this.state.productJSON} //this is the important part. It will render this.state.productJSON only when it is a truthy value.
</div>
</div>
)}
}
Given that you do get a valid colorArray from your call, this will work.

Categories

Resources