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

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.

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.

React passes wrong parameter along function

I'm using a for loop to create some svg paths but have some trouble passing a parameter alongside the function. Everything added with the props uses the correct i value, except selectRegion(i). This gets 2 as value, which I think is the final value of i after finishing the loop. How do I pass the correct i value?
componentDidMount() {
var regions = []
for (var i = 0; i < this.state.regionNames.length; i++) {
var region = <Region id={i} border={this.state.regionBorders[i]} color={this.state.regionColors[i]} selectRegion={() => this.selectRegion(i)}/>;
regions.push(region);
}
this.setState({regions: regions});
}
// Select region.
selectRegion(id) {
alert(id);
this.setState({selRegion: id});
}
Region component
import React, { Component } from 'react'
export default class Region extends Component {
constructor (props) {
super(props);
this.state = {
id: props.id,
color: props.color,
border: props.border,
opacity: "0.3",
is_selected: false
}
}
mouseEnter = (is_enter) => {
if(is_enter) {
this.setState({opacity: "0.5"});
alert(this.state.id);
this.props.selectRegion();
} else if (!this.state.is_selected) {
this.setState({opacity: "0.3"});
}
}
mouseClick = () => {
this.setState({is_selected: !this.state.is_selected})
}
render() {
return (
<path d={this.state.border}
fill={this.state.color}
fill-opacity={this.state.opacity}
onClick={() => this.mouseClick()}
onMouseEnter={() => this.mouseEnter(true)}
onMouseLeave={() => this.mouseEnter(false)}/>
)
}
}
#Norse was correct I've fixed it by doing the following:
// Generate map regions.
componentDidMount() {
var regions = []
for (let i = 0; i < this.state.regionNames.length; i++) {
var region = <Region id={i} border={this.state.regionBorders[i]} color={this.state.regionColors[i]} selectRegion={() => this.selectRegion(i)}/>;
regions.push(region);
}
this.setState({regions: regions});
}

React.js + Socket.io updating state changes position of list items

export default class extends React.Component {
constructor(props) {
super(props)
this.state = {
status: [],
services: []
}
getAppData((err,opt, data) => {
function Exists(list, id) {
return list.some(function(el) {
return el.data.id == id;
});
}
if (opt == "sysinfo"){
var filtered = this.state.status;
if (Exists(filtered, data.id)){
filtered = this.state.status.filter(function(el) { return el.data.id != data.id; });
}
filtered.push({ data })
this.setState({status: filtered})
} else if (opt == "init_services"){
this.setState({services: data})
}
});
}
render() {
const timestampforuse = this.state.status
const totalList = this.state.services
console.log(totalList)
const mainList = totalList.map((link) =>
<ListGroup.Item key={link.id} keyProp={link.id}>Name: {link.name} Node: {link.node}</ListGroup.Item>
);
console.log(totalList)
const listItems = timestampforuse.map((link) =>
<ListGroup.Item ><p key={link.data.id}>ID: {link.data.pid} Node: {link.data.node} <br/>Ram usage: {link.data.p_ram.toFixed(2)} / 100% Cpu usage: {link.data.p_cpu.toFixed(2)} / 100%</p></ListGroup.Item>
);
return (
<div>
<ListGroup>
{mainList}
</ListGroup>
</div>
);
}
}
Data from sysinfo:
{
cores: 16,
cpu: 0,
id: "00ffab6ca93243f08eb10670d9c491d54cf674173d13c24a0a663ebb3f5e54d042ae",
node: "1",
p_cpu: 0,
p_ram: 0.18230482881430612,
pid: 29216,
ram: 28.78515625,
threads: 5,
time: 1609179904302,
time_from_startup: 1609179876.271594,
time_since_boot: 1608562209.0201786
}
Data for init:
add_game: true
description: "a test script"
id: "00ffab6ca93243f08eb10670d9c491d54a0a663ebb3f5e54d042ae"
name: "test331112321"
node: "1"
Socket script:
import openSocket from 'socket.io-client';
const socket = openSocket('http://localhost:3000');
function getAppData(cb) {
socket.on('update_system', data => cb(null,"sysinfo", data));
socket.on('init_services', data => cb(null,"init_services", data));
socket.emit('updated', 1000);
}
export { getAppData };
I have tried using a map and using it as a list but when it updates every second it updates too fast to even read. How would I make the name appear, then once data gets sent have that update the list but not update the entire list? At the moment, it allows it to update and change, and no issues if it's 1 item being updated but if there are 2 or more it updates too fast to see. How do I get around this?
I have fixed this by updating an array of objects on the server-side. Updating a service on that list and returning the entire list. This tackled the issue of having it update too fast.
End code front end code:
export default class extends React.Component {
constructor(props) {
super(props)
this.state = {
services: []
}
getAppData((err,opt, data) => {
if (opt == "sysinfo"){
this.setState({services: data})
}
});
}
componentDidMount() {
fetch("http://localhost:3000/api/v1/bot/me/getservices").then(res => res.json()).then(data =>{
console.log(data)
this.setState({services: data})
})
}
render() {
const totalList = this.state.services
const listItems = totalList.map((link) =>
<ListGroup.Item key={link.id}>Name: {link.name} Node: {link.node} <br/>Ram usage: {link.p_ram.toFixed(2)} / 100% Cpu usage: {link.p_cpu.toFixed(2)} / 100%</ListGroup.Item>
);
return (
<div>
<ListGroup>
{listItems}
</ListGroup>
</div>
);
}
}

How to render object that pass through props in ReactJS?

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;

javascript/ReactJS: Show results from backend in a list

I am sending a GET request on a Node API with a MongoDB server. I am getting the response as JSON in an array of object format. I want to show all those results in a list. Right now i am making a function like this
class VendorDashboard extends React.Component {
constructor() {
super();
this.state = {
paginationValue: '86',
title: ""
}
this.handleLogout = this.handleLogout.bind(this);
this.gotoCourse = this.gotoCourse.bind(this);
}
componentDidMount() {
axios.get('/vendor/showcourses') //the api to hit request
.then((response) => {
console.log(response);
let course = [];
course = response.data.map((courseres) => {
this.setState({
title: courseres.title
});
})
});
Right now what is happening is it is showing just one result. I want to show all results on that api. How can i do it?
This segment here is overriding the title per course.
course = response.data.map((courseres) => {
this.setState({
title: courseres.title
});
})
You can keep the state as an array of titles and do;
course = response.data.map((courseres) => {
return courseres.title;
})
this.setState({titles: course});
And then you can repeat on the array of titles in your component.
Like so in the render method;
const { titles } = this.state;
return <div>{titles.map((title, index) => <div key={index}>{title}</div>)}</div>
You need to collect all the server response and set that as an array of data to the state and use this state data to render:
class VendorDashboard extends React.Component {
constructor() {
super();
this.state = {
paginationValue: '86',
course: []
}
this.handleLogout = this.handleLogout.bind(this);
this.gotoCourse = this.gotoCourse.bind(this);
}
componentDidMount() {
axios.get('/vendor/showcourses') //the api to hit request
.then((response) => {
const course = response.data.map((courseres) => ({
id: courseres.id,
title: courseres.title
}));
this.setState({
course
});
});
}
render() {
return (
<ul>
{
this.state.course.map((eachCourse) => {
return <li key={eachCourse.id}>{eachCourse.title}</li>
})
}
</ul>
)
}
}
In each map iteration you rewrite your piece of state, it is wrong.
Just put courses in your state:
console.log(response);
this.setState({ courses: response.data });
In render method go through your state.courses:
render(){
return(
<div>
{this.state.courses.map(course => <h2>{course.title}</h2>)}
</div>
);
}

Categories

Resources