Im creating a electron-react app and I'm using this boilerplate https://github.com/willjw3/react-electron
Im trying to fetch data from an API, Im able to get the data but im unable to update state using setState. componentDidMount is not firing as well. Am I setting something up wrong? Heres my code.
import React from "react"
import '../css/table.css'
const fs = window.require('fs');
const electron = window.require('electron')
const shell = electron.shell
const stockxAPI = window.require('stockx-api');
const stockX = new stockxAPI();
const scrapeStockx = (link, size) => {
let lowestAsk
return stockX.fetchProductDetails(link)
.then((response) => {
let productData = response.variants
//console.log(productData)
for (let product of productData){
if (product.size == size){
lowestAsk = product.market.lowestAsk
return '$' + lowestAsk
}
}
})
}
const fetchNewData = async (myProducts) => {
for (let product of myProducts){
let goatUrl = 'https://www.goat.com/web-api/v1/product_variants?productTemplateId='
let goatSKU = product.Goat[0].split('sneakers/')[1]
let ogUrl = goatUrl + goatSKU
let price = await scrapeStockx(product.Stockx[0], product.Size)
product.Stockx[1] = price
console.log('Product Size: ' + product.Stockx[1])
}
return myProducts
}
class ProductTable extends React.Component{
constructor(){
super()
this.state = {
Products : ''
}
this.renderTableData = this.renderTableData.bind(this)
this.updateProducts = this.updateProducts.bind(this)
}
async componentDidMount(){
this.setState({Products : 'Loading...'});
let myProducts = await this.updateProducts()
console.log('Component ' + myProducts)
this.setState({Products : myProducts})
console.log('Component' + this.state)
}
async updateProducts () {
let rawData = fs.readFileSync('/Users/yunioraguirre/Desktop/Lucky Cops Item Tracker V1/Lucky Item Tracker/MyProducts.json')
let myProducts = JSON.parse(rawData)
//Updates Goat and Stockx Prices
myProducts = await fetchNewData(myProducts)
try {
await fs.writeFileSync('MyProducts.json', JSON.stringify(myProducts, null, 2))
console.log('Success!')
console.log(myProducts)
} catch (error) {
console.log(error)
}
return myProducts
}
renderTableData = () => {
return this.state.Products.map( product => {
const {Id, Item, sku, Size, Sold, Goat, Stockx} = product
return (
<tr key={Id}>
<td>{Id}</td>
<td>{Item}</td>
<td>{sku}</td>
<td>{Size}</td>
<td>{product["Total Amount Paid"]}</td>
<td>{Sold}</td>
<td>{product['Price Sold For']}</td>
<td> {Goat[1]}</td>
<td> {Stockx[1]}</td>
<td> {product['Flight Club']}</td>
</tr>
)
})
}
renderTableHeader = () => {
console.log('State in Render' + JSON.stringify(this.state.Products))
let header = Object.keys(this.state.Products[0])
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
render(){
return (
<div id='Table-Wrapper'>
<h1 id='TableTitle'>Total Products</h1>
<table id='Products Table'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
}
export default ProductTable
Heres what I get in the console
The issue is you provide an empty string as initial state. The first console log is from the render function and the second is from this.renderTableHeader. The problem happens when you hit this line: let header = Object.keys(this.state.Products[0])
Object.keys(""[0])
You may want to try creating a separate "isLoading" state, and conditionally render JSX on that.
this.state = {
Products : '',
isLoading: false,
}
...
async componentDidMount(){
this.setState({isLoading: true});
let myProducts = await this.updateProducts()
console.log('Component ' + myProducts)
this.setState({Products: myProducts, isLoading: false})
}
....
render(){
if (this.state.isLoading) {
return <div>Loading...</div>;
}
return (
<div id='Table-Wrapper'>
<h1 id='TableTitle'>Total Products</h1>
<table id='Products Table'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
I was able to fix my code. As Drew stated, I needed a isLoading variable in state so I could render and fetch my data. Heres my updated State and ComponentDidMount
class ProductTable extends React.Component{
constructor(){
super()
this.state = {
Products : '',
isLoading : true
}
this.renderTableData = this.renderTableData.bind(this)
this.updateProducts = this.updateProducts.bind(this)
}
async componentDidMount(){
let myProducts = await this.updateProducts()
console.log('Component ' + JSON.stringify(myProducts))
console.log('\n')
await this.setState({Products : myProducts, isLoading : false})
//console.log(JSON.stringify(this.state))
}
}
Related
I have a simple word/definition app in React. There is an edit box that pops up to change definition when a user clicks on "edit". The new definition provided is updated in the state when I call getGlossary(), I see the new definition in inspector and a console.log statement in my App render() function triggers too. Unfortunately, I still have to refresh the page in order for the new definition to be seen on screen. I would think that calling set state for this.state.glossary in the App would trigger a re-render down to GlossaryList and then to GlossaryItem to update it's definition but I'm not seeing it :(.
App.js
class App extends React.Component {
constructor() {
super();
this.state = {
glossary: [],
searchTerm: '',
}
this.getGlossary = this.getGlossary.bind(this); //not really necessary?
this.handleSearchChange = this.handleSearchChange.bind(this);
this.handleAddGlossaryItem = this.handleAddGlossaryItem.bind(this);
this.handleDeleteGlossaryItem = this.handleDeleteGlossaryItem.bind(this);
//this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
}
getGlossary = () => {
console.log('getGlossary fired');
axios.get('/words').then((response) => {
const glossary = response.data;
console.log('1: ' + JSON.stringify(this.state.glossary));
this.setState({ glossary }, () => {
console.log('2: ' + JSON.stringify(this.state.glossary));
});
})
}
componentDidMount = () => {
//console.log('mounted')
this.getGlossary();
}
handleSearchChange = (searchTerm) => {
this.setState({ searchTerm });
}
handleAddGlossaryItem = (glossaryItemToAdd) => {
//console.log(glossaryItemToAdd);
axios.post('/words', glossaryItemToAdd).then(() => {
this.getGlossary();
});
}
handleDeleteGlossaryItem = (glossaryItemId) => {
console.log('id to delete: ' + glossaryItemId);
axios.delete('/words', {
data: { glossaryItemId },
}).then(() => {
this.getGlossary();
});
}
render() {
console.log('render app fired');
const filteredGlossary = this.state.glossary.filter((glossaryItem) => {
return glossaryItem.word.toLowerCase().includes(this.state.searchTerm.toLowerCase());
});
return (
<div>
<div className="main-grid-layout">
<div className="form-left">
<SearchBox handleSearchChange={this.handleSearchChange} />
<AddWord handleAddGlossaryItem={this.handleAddGlossaryItem} />
</div>
<GlossaryList
glossary={filteredGlossary}
handleDeleteGlossaryItem={this.handleDeleteGlossaryItem}
getGlossary={this.getGlossary}
//handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
/>
</div>
</div>
);
}
}
export default App;
GlossaryItem.jsx
import React from 'react';
import EditWord from './EditWord.jsx';
const axios = require('axios');
class GlossaryItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isInEditMode: false,
}
this.glossaryItem = this.props.glossaryItem;
this.handleDeleteGlossaryItem = this.props.handleDeleteGlossaryItem;
this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
this.handleEditClick = this.handleEditClick.bind(this);
}
handleUpdateGlossaryDefinition = (updateObj) => {
console.log('update object: ' + JSON.stringify(updateObj));
axios.put('/words', {
data: updateObj,
}).then(() => {
this.props.getGlossary();
}).then(() => {
this.setState({ isInEditMode: !this.state.isInEditMode });
//window.location.reload();
});
}
handleEditClick = () => {
// display edit fields
this.setState({ isInEditMode: !this.state.isInEditMode });
// pass const name = new type(arguments); data up to App to handle with db
}
render() {
return (
<div className="glossary-wrapper">
<div className="glossary-item">
<p>{this.glossaryItem.word}</p>
<p>{this.glossaryItem.definition}</p>
<a onClick={this.handleEditClick}>{!this.state.isInEditMode ? 'edit' : 'cancel'}</a>
<a onClick={() => this.handleDeleteGlossaryItem(this.glossaryItem._id)}>delete</a>
</div>
{this.state.isInEditMode ?
<EditWord
id={this.glossaryItem._id}
handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
/> : null}
</div>
);
}
}
EditWord
import React from 'react';
class EditWord extends React.Component {
constructor(props) {
super(props);
this.state = {
definition: ''
};
this.handleUpdateGlossaryDefinition = this.props.handleUpdateGlossaryDefinition;
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
let definition = event.target.value;
this.setState({ definition });
}
handleSubmit(event) {
//console.log(event.target[0].value);
let definition = event.target[0].value;
let update = {
'id': this.props.id,
'definition': definition,
}
//console.log(update);
this.handleUpdateGlossaryDefinition(update);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit} className="glossary-item">
<div></div>
<input type="text" name="definition" placeholder='New definition' value={this.state.definition} onChange={this.handleChange} />
<input type="submit" name="update" value="Update" />
</form>
);
}
}
export default EditWord;
Thank you
One possible way I can see to fix this is to map the data to make the id uniquely identify each list item (even in case of update). We can to do this in getGlossary() by modifying the _id to _id + definition.
getGlossary = () => {
console.log('getGlossary fired');
axios.get('/words').then((response) => {
// Map glossary to uniquely identify each list item
const glossary = response.data.map(d => {
return {
...d,
_id: d._id + d.definition,
}
});
console.log('1: ' + JSON.stringify(this.state.glossary));
this.setState({ glossary }, () => {
console.log('2: ' + JSON.stringify(this.state.glossary));
});
})
}
In the constructor of GlossaryItem I set
this.glossaryItem = this.props.glossaryItem;
because I am lazy and didn't want to have to write the word 'props' in the component. Turns out this made react loose reference somehow.
If I just remove this line of code and change all references to this.glossaryItem.xxx to this.pros.glossaryItem.xxx then it works as I expect! On another note, the line of code can be moved into the render function (instead of the constructor) and that works too, but have to make sure I'm accessing variables properly in the other functions outside render.
In the project I watch, they work with class component, but I want to do these operations with functional component using hooks. How can you help me guys? I tried many times but couldn't do this translation. I'm still trying
My code (imported data is "ingredients"):
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
ingredients: [],
totalPrice: 0
}
this.addIngredients = this.addIngredients.bind(this)
this.removeIngredients = this.removeIngredients.bind(this)
this.calculateTotal = this.calculateTotal.bind(this)
}
addIngredients(product) {
this.setState({
ingredients: [...this.state.ingredients].concat([
{ ...product, displayId: Math.random() }
])
})
}
removeIngredients(product) {
const selectedProduct = this.state.ingredients.find((ingredient) => {
return ingredient.name === product.name
})
const targetId = selectedProduct.displayId
this.setState({
ingredients: this.state.ingredients.filter((ingredient) => {
return ingredient.displayId !== targetId
})
})
}
calculateTotal() {
let total = 4
this.state.ingredients.forEach((item) => {
total += item.price
})
return total.toFixed(2)
}
render() {
return (
<div>
<Hamburger ingredients={this.state.ingredients} />
<TotalPrice total={this.calculateTotal} />
<ItemList
items={ingrediends}
addIngredients={this.addIngredients}
removeIngredients={this.removeIngredients}
selectedIngredients={this.state.ingredients}
/>
</div>
)
}
}
export default App
Navarrro I hope this helps you! I couldn't test it but Is a good started for you, I use ES6 syntax...
import React, { useState } from 'react';
import { Hamburger, TotalPrice, ItemList } from './SuperComponents.jsx';
const App = () => {
const [ingredients, setIngredients] = useState([]);
// You are not using this state
// const [totalPrice, setTotalPrice] = useState(0);
const addIngredients = (product) => {
setIngredients([...ingredients, { ...product, displayId: Math.random() }]);
};
const removeIngredients = (product) => {
const selectedProduct = ingredients.find(
(ingredient) => ingredient.name === product.name
);
const { targetId } = selectedProduct;
setIngredients(
ingredients.filter((ingredient) => ingredient.displayId !== targetId)
);
};
const calculateTotal = () => {
let total = 4;
ingredients.forEach((item) => (total += item.price));
return total.toFixed(2);
};
return (
<>
<Hamburger ingredients={ingredients} />
<TotalPrice total={() => calculateTotal()} />
<ItemList
items={ingredients}
addIngredients={(i) => addIngredients(i)}
removeIngredients={(i) => removeIngredients(i)}
selectedIngredients={ingredients}
/>
</>
);
};
export default App;
I am using an API to fetch some data. When the page loads it fetches some random data, but I want to allow the user to sort the data by clicking a button. I have made a function to sort these data from the API I am using. What I want to do now is: When the button to sort data is clicked, I want the new data to be replaced with the old data.
Here is my current code:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
offset: 0, perPage: 12 // ignore these two
};
}
// The random data that I want to be displayed on page load
receivedData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
const slice = data.slice(this.state.offset, this.state.offset + this.state.perPage) // ignore this
const postData = slice.map(item =>
<tr key={Math.random()}>
<td>{item.province}, {item.country}</td>
<td>{item.stats.confirmed}</td>
<td>{item.stats.deaths}</td>
<td>{item.stats.recovered}</td>
</tr>
)
this.setState({
pageCount: Math.ceil(data.length / this.state.perPage), // ignore this
postData
})
});
}
// The data to be sorted when the "country" on the table head is clicked
sortData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
var someArray = data;
function generateSortFn(prop, reverse) {
return function (a, b) {
if (a[prop] < b[prop])
return reverse ? 1 : -1;
if (a[prop] > b[prop])
return reverse ? -1 : 1;
return 0;
};
}
// someArray.sort(generateSortFn('province', true))
const tableHead = <tr>
<th onClick={() => someArray.sort(generateSortFn('province', true))}>Country</th>
<th>Confirmed Cases</th>
<th>Deaths</th>
<th>Recovered</th>
</tr>
this.setState({
tableHead
})
});
}
componentDidMount() {
this.receivedData()
this.sortData() // This function should be called on the "Country - table head" click
}
render() {
return (
<div>
<table>
<tbody>
{this.state.tableHead}
{this.state.postData}
</tbody>
</table>
</div>
)
}
}
export default Data;
Think a litte bit different. In the componentDidMount get you're Data in some form. Set it with setState only the raw Data not the html. Then resort the data on button click. React rerenders if the state changes automatically
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data
}
}
getData() {
fetchData('url').then(res) {
this.setState({data: res.data})
}
}
componentDidMount() {
this.getData()
}
sort() {
let newSorted = this.state.data.sort //do the sorting here
this.setState({data: newSorted})
}
render() {
return() {
<table>
<tablehead><button onClick={this.sort.bind(this)}/></tablehead>
{this.state.data.map(data => {
return <tablecell>{data.name}</tablecell>
})}
</table>
}
}
}
I'm trying to render the data from my database get this instead Failed to compile.
./src/components/list-pets.component.js
Line 38:5: Expected an assignment or function call and instead saw an expression no-unused-expressions
Search for the keywords to learn more about each error.enter code here
Here is my code from the trouble component
import React, { Component } from 'react';
import axios from 'axios';
export default class ListPets extends Component {
constructor(props) {
super(props);
this.state = {
pets: []
};
}
componentDidMount = () => {
this.getPets();
};
getPets = () => {
axios.get('http://localhost:5000/pets')
.then((response) => {
const data = response.data;
this.setState({ pets: data });
console.log('Data has been received!');
})
.catch((err) => {
console.log(err);
});
}
displayPet = (pets) => {
if (!pets.length) return null;
return pets.map((pet, index) => {
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
});
};
render() {
console.log('State: ', this.state);
return (
<div className='adopt'>
{this.displayPet(this.state.pets)}
</div>
)
}
}
You need to return a value at each pets.map iteration, currently you’re returning undefined.
return pets.map((pet, index) => {
return (
<div key={index}>
<h3>{pet.name}</h3>
<p>{pet.species}</p>
</div>
)
});
You have to wait until fetching data is completed.
You should have to define the loading bar while fetching.
class App extends Component {
constructor() {
super();
this.state = {
pageData: {},
loading: true
}
this.getData();
}
async getData(){
const res = await fetch('/pageData.json');
const data = await res.json();
return this.setState({
pageData: data,
loading: false
});
}
componentDidMount() {
this.getData();
}
render() {
const { loading, pageData } = this.state;
if (loading){
return <LoadingBar />
}
return (
<div className="App">
<Navbar />
</div>
);
}
}
I am trying to parse data from an xml file in my React JS app, but it seems to return a full xml object comprising of 25 or so 'cube' elements. I'm interested in accessing the 'currency' and 'rate' attribute of each cube, and output each of these inside of a dropdown. Is there a way of looping over all the cubes and somehow targeting these? I'm trying to build a currency converter that automatically converts a price entered by the user.
My code:
import React, { Component } from 'react';
import "../../App.css"
class Countries extends Component {
constructor() {
super();
this.state = {
countrycodes: [],
exchangerates: []
};
}
componentDidMount(){
fetch('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')
.then(response => response.text())
.then(str => (new window.DOMParser()).parseFromString(str, "text/xml"))
.then(data => {
const cubes = data.getElementsByTagName("Cube")
for( const element of cubes) {
if (!element.getAttribute('currency')) {
continue;
}
let countrycodes = element.getAttribute('currency')
let exchangerates = element.getAttribute('rate')
this.setState({
countrycodes: countrycodes,
exchangerates: exchangerates
})
}
});
}
render() {
return (
<div className="container2">
<div className="container1">
<select>{this.state.countrycodes.map((country) => {
<option>{country}</option>})
}
</select>
</div>
</div>
)
}
}
export default Countries;
Thanks,
Robert
Use getAttribute:
class Countries extends React.Component {
constructor() {
super();
this.state = {
countryCodes: []
};
}
componentDidMount(){
fetch({url: 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'})
.then(response => response.text())
.then(str => (new window.DOMParser()).parseFromString(str, "text/xml"))
.then(data => {
const countryCodes = [];
const cubes = data.getElementsByTagName("Cube");
for (const element of cubes) {
if (!element.getAttribute('currency')) {
// skip cube with no currency
continue;
}
countryCodes.push({
currency:element.getAttribute('currency'),
rate: element.getAttribute('rate')
});
}
this.setState({countryCodes});
});
}
render() {
const options = this.state.countryCodes.map(
({currency, rate}) => (<option value={rate}>{currency} - {rate}</option>));
return (
<div className="container2">
<div className="container1">
<select>
{options}
</select>
</div>
</div>
)
}
}
To check you can open http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml and run fetch(...) directly in browser's console: