How should I access this promise result (React, DataStore, AWS Amplify) - javascript

Following this tutorial https://www.youtube.com/watch?v=QrkkNte1onA&t=742s (2:36:48).
I am trying to access a promise that is found in useState value: dishes.
Here is my code on the third (and last useEffect) I query order DataDish (which contains the following info: quantity, orderID, orderDishDishId) with the condition that the orderID equals the order id of the order I'm returning. I set the dishes into a state with .then() but when accessing dishes the Dish data (name of the item, price, etc) is a promise. (See image below)
import {Card, Descriptions, Divider, List, Button} from 'antd';
import { useEffect, useState } from 'react';
import {useParams} from 'react-router-dom';
import {DataStore} from '#aws-amplify/datastore';
import {Order, OrderDish, User} from '../../models';
const DetailedOrder = () => {
const {id} = useParams();
const [order, setOrder] = useState(null);
const [customer, setCustomer] = useState(null);
const [dishes, setDishes] = useState([]);
useEffect(() => {
DataStore.query(Order, id).then(setOrder);
}, [id]);
useEffect(() => {
if(order?.userID) {
DataStore.query(User, order.userID).then(setCustomer);
}
}, [order?.userID]);
useEffect(() => {
if(!order?.id){
return;
}
DataStore.query(OrderDish, c => c.orderID.eq(order.id)).then(setDishes);
}, [order?.id])
console.log(dishes);
return (
<Card title={`Order #${id}` } style={{ margin: 20 }}>
<Descriptions bordered column={{ lg:1, md:1, sm:1 }}>
<Descriptions.Item label="Customer">{customer?.name}</Descriptions.Item>
<Descriptions.Item label="Customer Address">{customer?.address}</Descriptions.Item>
</Descriptions>
<Divider />
<List
dataSource={dishes}
renderItem={(dishItem) => (
<List.Item>
<div style={{fontWeight: 'bold'}}>{dishItem.Dish.name} x{dishItem.quantity}</div>
<div>${dishItem.Dish.price}</div>
</List.Item>
)}/>
<Divider />
<div style={styles.totalSumContainer}>
<h2 style={{fontWeight: '400'}}>Total:</h2>
<h2 style={styles.totalPrice}>${order?.total?.toFixed(2)}</h2>
</div>
<Divider />
<div style={styles.buttonsContainer}>
<Button block type='danger' size='large' style={styles.button}>
Decline Order
</Button>
<Button block type='primary' size='large' style={styles.button}>
Accept Order
</Button>
</div>
<Button block type='primary' size='large'>
Ready For Pickup
</Button>
</Card>
);
};
const styles = {
totalSumContainer: {
flexDirection: 'row',
display: 'flex',
},
totalPrice: {
marginLeft: 'auto',
fontWeight: 'bold',
},
buttonsContainer: {
display: 'flex',
paddingBottom: 30,
},
button: {
marginRight: 10,
marginLeft: 10,
color: 'white',
},
};
export default DetailedOrder;
The console.log(dishes) returns the folowing in the console:
I am trying to access the data found in Dish but its a promise and cant figure out how to bring it into state.
I've tried another .then() but I couldn't figure it out (just starting out with javascript). I've read up on async/await but I couldn't understand how to implement it.
Let me know if you might know but need more info. I'm happy to provide whatever is necessary. This is my first question on StackOverflow and it was much harder to formulate a 'good question' than I thought.
Vadim (The guy in the youtube video) does not encounter this problem (2:37:08). I suspect it is because of updates to amplify. But I'm not 100% sure.
Any info would be extremely appreciated.
Thanks for reading!

If you want to convert the api calls to async then you can try the following
useEffect(() => {
const fetchOrder = async () => {
const orderResponse = await DataStore.query(Order, id);
console.log('order', orderResponse);
setOrder(orderResponse)
// ideally fetch all data here to avoid unnecessary rerenders
// and check if the response user is different from previous use to prevent refetch of user
if(orderResponse?.userID) {
const customerResponse = await DataStore.query(User, orderResponse.userID);
console.log('order', customerResponse);
setCustomer(customerResponse)
}
if(!orderResponse?.id) {
const dishResponse = await DataStore.query(OrderDish, c => c.orderID.eq(orderResponse.id));
console.log('dish', dishResponse);
setDishes(dishResponse)
}
fetchOrder();
}, [id]);
I am hoping this will help you to debug to solve your issue and the write maintainable code.
Cheers

Related

Trouble with React Native and Firebase RTDB

I'm using React Native (0.68) and Firebase RTDB (with the SDK, version 9), in Expo.
I have a screen that needs to pull a bunch of data from the RTDB and display it in a Flatlist.
(I initially did this without Flatlist, but initial rendering was a bit on the slow side.)
With Flatlist, initial rendering is super fast, huzzah!
However, I have an infinite loop re-render that I'm having trouble finding and fixing. Here's my code for the screen, which exists within a stack navigator:
export function GroupingsScreen () {
... set up a whole bunch of useState, database references (incl groupsRef) etc ...
onValue(groupsRef, (snapshot) => {
console.log('groups onValue triggered')
let data = snapshot.val();
if (loaded == false) {
console.log('--start processing')
setLoaded(true);
let newObject = []
for (let [thisgrouping, contents] of Object.entries(data)) {
let onegroupingObject = { title: thisgrouping, data: [] }
for (let [name, innerdata] of Object.entries(contents.ingredients)) {
onegroupingObject.data.push({ name: name, sku: innerdata.sku, size: innerdata.size,
quantity: innerdata.quantity,
parent: thisgrouping
})
}
newObject.push(onegroupingObject)
}
console.log('--done processing')
setGroupsArray(newObject)
}
});
.... more stuff excerpted ....
return (
<View style={styles.tile}>
<SectionList
sections={groupsArray}
getItemLayout={getItemLayout}
renderItem={ oneRender }
renderSectionHeader={oneSection}
initialNumToRender={20}
removeClippedSubviews={true}
/>
</View>
)};
I'm using loaded/setLoaded to reduce re-renders, but without that code, RN immediately dumps me out for excessive re-renders. Even with it, I get lots of extra renders.
So...
Can someone point me at what's triggering the rerender? The database is /not/ changing.
Is there a better way to get RTDB info into a Flatlist than the code I've written?
I have some code that actually does change the database. That's triggering a full rerender of the whole Flatlist, which is visibly, painfully slow (probably because parts are actually rendering 10x instead of once). Help?
For completeness, here's the OneItem code, so you can see just how complex my Flatlist items are:
const OneItem = (data) => {
// console.log('got data',data)
return (
<View style={[styles.rowView, { backgroundColor: data.sku?'white': '#cccccc'}]} key={data.name}>
<TouchableOpacity style={styles.nameView} onPress={() => {
navigation.navigate('AddEditItemScreen', {purpose: 'Grouping', itemname: data.name, parent: data.parent, mode: 'fix'})
}}>
<View style={styles.nameView}>
<Text style={styles.itemtext}>{data.name}</Text>
{data.sku? null: <Text>"Tap to add SKU."</Text>}
{data.size?<Text>{data.size} </Text>: <Text>no size</Text>}
</View>
</TouchableOpacity>
<View style={styles.buttonView}>
<Button style={styles.smallButton}
onPress={() => { changeQuant(data.quantity ? data.quantity - 1 : -1, data.parent + '/ingredients/' + data.name) }}
>
{data.quantity > 0 ? <Text style={[styles.buttonText, { fontSize: 20 }]}>-</Text>
:<Image source={Images.trash} style={styles.trashButton} />}</Button>
<Text style={styles.quantitytext}>{data.quantity}</Text>
<Button style={styles.smallButton}
onPress={() => {
changeQuant(data.quantity? data.quantity +1 : 1, data.parent+'/ingredients/'+data.name)}}>
<Text style={[styles.buttonText, {fontSize: 20}]}>+</Text></Button>
</View>
</View>
)
};```
I worked out how to stop the rerender (question #1). So, within my Screen functional component, I needed to make another function, and attach the state hook and useEffect to that. I'm not totally sure I understand why, but it gets rid of extra renders. And it's enough to get #3 to tolerable, although perhaps not perfect.
Here's the new code:
export function GroupingsScreen () {
... lots of stuff omitted ...
function JustTheList() {
const [groupsArray, setGroupsArray] = useState([])
useEffect(() => {
const subscriber = onValue(groupsRef, (snapshot) => {
console.log('groups onValue triggered')
let data = snapshot.val();
let newObject = []
for (let [thisgrouping, contents] of Object.entries(data)) {
let onegroupingObject = { title: thisgrouping, data: [] }
for (let [name, innerdata] of Object.entries(contents.ingredients)) {
onegroupingObject.data.push({ name: name, sku: innerdata.sku, size: innerdata.size,
quantity: innerdata.quantity,
parent: thisgrouping
})
}
newObject.push(onegroupingObject)
}
setGroupsArray(newObject)
})
return () => subscriber();
}, [])
return(
<View style={styles.tile}>
<SectionList
sections={groupsArray}
getItemLayout={getItemLayout}
renderItem={ oneRender }
renderSectionHeader={oneSection}
initialNumToRender={20}
removeClippedSubviews={true}
/>
</View>
)
}
And then what was my return within the main functional screen component became:
return (
<JustTheList />
)
I'm still very interested in ideas for improving this code - am I missing a better way to work with RTDB and Flatlist?

React state is undefined when fetching data from sanity cms

I followed a tutorial recently about integrating a cms into your website. The tutorial used sanity cms which made the process very intuitive. Once I was done with the tutorial I was ready to use it in my own projects.
however when I try to fetch data with the useEffect hook I get an error: Cannot read properties of undefined. I know this is because fetching data is done async. But the thing I can't wrap my head around is I did it the exact same way as the tutorial. He didn't use any state for loading or isFetched. So my question is what did I do different than the tutorial and how should I solve it?
I don't really want to use a loading state because that doesn't really look that good...
This is the JSON object I receive from the api:
[{…}]
0:
buttonlabel: "Start learning"
description: "Ranging from beginner to pro level tricks. Wanna know the best way to learn a trick? You can search for it down below and find a tutorial from one of our trainers as well as a detailed explanation. Still stuck? Come ask us at a Westsite training moment."
intro: "Welcome to the Westsite trick progression guide. Here you can find a collection of all the wakeboarding tricks you can think of. "
_createdAt: "2022-05-24T16:26:13Z"
_id: "a4f8cf02-4b86-44d5-a63d-c95a3a7d3293"
_rev: "QYLgvM20Eo53w3noOOj0MB"
_type: "hero"
_updatedAt: "2022-05-24T17:29:10Z"
This is the tutorial component:
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { urlFor, client } from "../../client";
import { AppWrap, MotionWrap } from "../../wrapper";
import "./About.scss";
const About = () => {
const [abouts, setAbouts] = useState([]);
useEffect(() => {
const query = '*[_type == "abouts"]';
client.fetch(query).then((data) => setAbouts(data));
}, []);
return (
<div>
<h2 className="head-text">
I know that
<span> Good Design </span>
<br />
means
<span> Good Business</span>
</h2>
<div className="app__profiles">
{abouts.map((about, index) => {
return (
<motion.div
whileInView={{ opacity: 1 }}
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.5, type: "tween" }}
className="app__profile-item"
key={about.title + index}
>
<img src={urlFor(about.imgUrl)} alt={about.title} />
<h2 className="bold-text" style={{ marginTop: 20 }}>
{about.title}
</h2>
<p className="p-text" style={{ marginTop: 10 }}>
{about.description}
</p>
</motion.div>
);
})}
</div>
</div>
);
};
export default AppWrap(
MotionWrap(About, "app__about"),
"about",
"app__whitebg"
);
And this is mine:
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { BiRightArrowAlt } from "react-icons/bi";
import { client } from "../../client";
import "./Hero.scss";
const Hero = () => {
const [heroContent, setHeroContent] = useState([]);
useEffect(() => {
const query = '*[_type == "hero"]';
client.fetch(query).then((data) => setHeroContent(data));
}, []);
const content = heroContent[0];
return (
<div className="app__hero">
<motion.div
className="app__hero-content-container"
whileInView={{ opacity: [0, 1], x: [500, 0] }}
transition={{ duration: 1, ease: "easeOut" }}
>
<div className="app__hero-content">
<h2 className="heading-text">
Learn
<span className="highlighted"> wakeboarding </span>
the right way
</h2>
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
</div>
</motion.div>
</div>
);
};
export default Hero;
This line will cause issues before the data is applied to state asynchronously
const content = heroContent[0];
On the initial render, heroContent is an empty array, so content will be undefined until your data is loaded. A couple options -
1 - render some sort of loading state until heroContent has been populated -
if (!heroContent.length) return <LoadingSpinner />
2 - wrap the portion that is trying to use content with a guard clause
{content && (
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
)}
The issue comes when you try to access properties from content when it's undefined. If you don't want to show any loading indicator, I would go with showing some fallback for when content is not defined. e.g.
Instead of:
<p className="p-text">{content.intro}</p>
You could go with:
<p className="p-text">{content?.intro ?? '-'}</p>
or something like that.
Problem is that you breakdown your state on different level that create problem with state changes. So, you have to do this
Either you call state as map function or save your state with specfic index 0.
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { BiRightArrowAlt } from "react-icons/bi";
import { client } from "../../client";
import "./Hero.scss";
const Hero = () => {
const [heroContent, setHeroContent] = useState([]);
useEffect(() => {
const query = '*[_type == "hero"]';
// this is giving response as array
client.fetch(query).then((data) => setHeroContent(data));
}, []);
return (
<div className="app__hero">
{heroContent.map((content,index)=>
<motion.div
className="app__hero-content-container"
whileInView={{ opacity: [0, 1], x: [500, 0] }}
transition={{ duration: 1, ease: "easeOut" }}
key={index}
>
<div className="app__hero-content">
<h2 className="heading-text">
Learn
<span className="highlighted"> wakeboarding </span>
the right way
</h2>
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
</div>
</motion.div>}
</div>
);
};
export default Hero;

Having a problem with loading audios from Realtime database and playing with touchableopacity in react native

I have some pronunciations stored in firebase storage and then from there with the help of access token, they're stored in realtime database.
In UI, I've touchable opacity (in react native) to play those pronunciations one after the other. But pronunciations don't get played immediately, they take a good 2 or 3 seconds to load and if I don't wait for 2-3 seconds and press the button immediately and consecutively more than once like 2,3 or 4 times, I've noticed that then none of my pronunciations are playing (current one,previous one, next one,none by the way I have next and back buttons to show my data in order from database) as long as I refresh my app. But my other data (Text and images) is displayed correctly and normally, there's only a problem with audios. Can someone help me how to solve this problem? Also I have the same problem whether I use ref().on or ref().once.
HERE'S App.js:
import Sound from 'react-native-sound';
import database from '#react-native-firebase/database';
import storage from '#react-native-firebase/storage';
import React , {useEffect, useState} from 'react';
import {
ScrollView,
StyleSheet,
Alert,
Text,
View,
Image,
TouchabelOpacity,
Button
} from 'react-native';
const App = () => {
const [myData,setData] = useState({
letter:'',
pronun:'',
word:'',
image:''
});
const [img,setimg] = useState(null);
const [pronunn,setpronun] = useState(null);
const [hey,sethey] = useState(1);
function inchey(){
sethey(hey + 1);
}
function decchey(){
sethey(hey - 1);
}
useEffect(() => {
getDatabase();
}, [hey]);
function getDatabase() {
database().ref('users/'+hey+'/').on('value' , (snapshot) => {
Sound.setCategory('Playback', true);
var poo=new Sound(snapshot.val().pronun);
setData({
letter: snapshot.val().letter,
word: snapshot.val().word,
image: setimg(snapshot.val().image),
pronun: setpronun(poo)
});
});
}
return (
<View style={{flex:1, backgroundColor:'#000000', alignContent:'center', alignItems:'center', justifyContent:'center'}}>
<Text style={{color:'#ffff'}}>
Letter: {myData ? myData.letter : 'loading...' }
</Text>
<Text style={{color:'#ffff'}}>
Word: {myData ? myData.word : 'loading...' }
</Text>
<Image style={{width:200, height:200}}
source={{uri: img}}
/>
<View>
<TouchableOpacity onPress={() => {
return pronunn.play();
}}
>
<Text>Pronunciation</Text>
</TouchableOpacity>
<Button title='Next' onPress={
() => {
if (hey>2) {
Alert.alert('no more records');
}
else {
inchey();
}
}
}
>
</Button>
<Button title='back' onPress={
async () => {
if (hey<2) {
Alert.alert('no more records to go back');
}
else {
decchey();
}
}
}
>
</Button>
</View>
</View>
);
};
export default App;
I couldn't understand if taking too long is the problem or you want your app to handle that waiting moment. If you want your app to handle the issue, I would suggest creating a boolean useState that keeps the status between the start and end of the database request. In your case, setting this boolean to true in your useEffect right before getDatabase(); and setting it to false right after you set your data. This way, you are able to provide a boolean to your button components disable prop. If your boolean is true it should be disabled and when you get data it will become enabled.

React router component calls function multiple times

I am trying to learn blockchain development, so I started to learn Solidity a few weeks ago and I had to create a front-end application for my contract I made with React, which I also don't know.
So, I have read documents and watched tutorials to make use of web3 libraries and some page transitions. Now I can navigate between pages in my application, but when I route to a specific page, my functions get called multiple times.
This is my index.js which gets loaded every time I run the application. I have set my routes like so.
index.js (I don't have app.js and use index.js like app.js)
import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';
import Web3 from 'web3'
import { ConnectPage } from './ConnectPage';
import { MintPage } from './MintPage';
import { AllCryptonauts } from './AllCryptonauts';
import { MyCryptonauts } from './MyCryptonauts';
import { NotFound } from './NotFound';
import { BrowserRouter, Route, Switch } from 'react-router-dom'
function App() {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum)
window.ethereum.enable()
} else {
alert("Could not detect MetaMask. Please install MetaMask before proceeding!");
}
return (
<div>
<Switch>
<Route exact path="/" component={ConnectPage} />
<Route exact path="/MintPage" component={MintPage} />
<Route exact path="/MyCryptonauts" component={MyCryptonauts} />
<Route exact path="/AllCryptonauts" component={AllCryptonauts} />
<Route path="*" component={NotFound} />
</Switch>
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<BrowserRouter><App /></BrowserRouter>
</React.StrictMode>,
document.getElementById("root")
);
I have made a connect button in my first page, which redirects me to the mint section.
ConnectPage.js
import React from 'react'
export const ConnectPage = (props) => {
async function Connect() {
const web3 = window.web3
const networkId = await web3.eth.net.getId()
if (networkId === 4) {
props.history.push("/MintPage")
} else {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x4' }],
});
props.history.push("/MintPage")
}
}
return (
<div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}><img src="https://nftornek.000webhostapp.com/frontend/cnlogo.png"></img></div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: '5%'}}><button className="connectButton" onClick={Connect}>Enter the Universe</button></div>
</div>
)
}
This is where I make my mint transactions. I have put console.log("called checkChainID") to see how many times checkChainID function gets called. And it gets called 12 times every time page is loaded, and twice after I try to navigate to the same page.
As a beginner in all of these, I have gathered the information around I got from tutorials which are not much obviously, and tried to make a test application with routing(which I have made following by a tutorial too)
The routing worked as I exactly done everything in tutorial, and I wanted to keep going on this routing example for my app, but probably due to my lack of fundamental knowledge of React, I am doing something wrong inside the pages. I have been researching this issue for hours, but couldn't really understand what I can do to solve it.
I think it is because of useState because it renders the application every time when I call it, but I'm not sure if there is any way to prevent that from happening, or come up with a smarter way.
MintPage.js
import React, { useState } from 'react';
import Web3 from 'web3'
import Contract from "../src/build/Contract.json";
export const MintPage = (props) => {
const web3 = window.web3;
var [currentAccount, setCurrentAccount] = useState("0x0");
var [currentBalance, setCurrentBalance] = useState("0");
var [mintAmount, setMintAmount] = useState(1);
const [addAmount, setAddAmount] = useState(true);
const [subtractAmount, setSubtractAmount] = useState(false);
window.ethereum.on('chainChanged', (_chainId) => checkChainID());
window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
checkChainID();
async function checkChainID() {
const networkId = await web3.eth.net.getId();
if (networkId !== 4) {
props.history.push("/")
} else {
loadBlockchainData();
}
console.log("called checkChainID")
}
async function loadBlockchainData() {
window.web3 = new Web3(window.ethereum);
const accounts = await web3.eth.getAccounts();
setCurrentAccount(accounts[0]);
getBalance(accounts[0]);
}
async function getBalance(acc) {
const balance = await web3.eth.getBalance(acc);
var balanceEth = web3.utils.fromWei(balance, 'ether');
setCurrentBalance(parseFloat(balanceEth).toFixed(3) + " ETH");
const SmartContractObj = new web3.eth.Contract(Contract.abi, "0x187FF2d65dd7204f11ea0487F2EED36378946902");
}
function MintPage() {
props.history.push("/MintPage")
}
function MyCryptonauts() {
props.history.push("/MyCryptonauts")
}
function AllCryptonauts() {
props.history.push("/AllCryptonauts")
}
function Disconnect() {
props.history.push("/")
}
return (
<div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}><img src="https://nftornek.000webhostapp.com/frontend/cnlogo.png" width='500' height='180'></img></div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button className="regularButton divide" onClick={MintPage}>Mint</button>
<button className="regularButton divide" onClick={MyCryptonauts}>My Cryptonauts</button>
<button className="regularButton divide" onClick={AllCryptonauts}>All Cryptonauts</button>
<button className="regularButton divide" onClick={Disconnect}>Disconnect</button>
</div>
<div style={{ display: 'flex', justifyContent: 'center' }}><p className="accountText">Current Account: {currentAccount}</p></div>
<div style={{ marginTop: '25%' }}></div>
<div style={{ display: 'flex', justifyContent: 'center' }}><p className="accountText">Mint {mintAmount} Cryptonaut for XXX ETH</p></div>
<div style={{ display: 'flex', justifyContent: 'center' }}><button className="amountButton divide" disabled={subtractAmount ? 0 : 1} onClick={() => {
if (mintAmount <= 1) {
setMintAmount(1);
setSubtractAmount(false);
} else {
setMintAmount(mintAmount - 1);
} if (mintAmount === 2) {
setSubtractAmount(false);
} if (mintAmount >= 1) {
setAddAmount(true);
}
}}>-
</button>
<button className="mintButton divide" onClick={() => {
console.log(mintAmount);
}}>MINT
</button>
<button className="amountButton divide" disabled={addAmount ? 0 : 1} onClick={() => {
if (mintAmount >= 5) {
setMintAmount(5);
} else {
setMintAmount(mintAmount + 1);
}
if (mintAmount === 4) {
setAddAmount(false);
} if (mintAmount >= 1) {
setSubtractAmount(true);
}
}}>+
</button>
</div>
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '7px' }}><p className="accountText">Current Balance: {currentBalance}</p></div>
</div>
)
}
The problematic piece is these three lines:
window.ethereum.on('chainChanged', (_chainId) => checkChainID());
window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
checkChainID();
You usually don't want to call a function directly in the render cycle of a component, since components can re-render for lots of different reasons. Every time this component renders it's adding an event listener to a global ethereum object, so you'll get one additional listener per component render. Since this is a functional component, you should wrap those lines in an effect hook so you can control when they run.
useEffect(() => {
window.ethereum.on('chainChanged', (_chainId) => checkChainID());
window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
checkChainID();
return () => {
// remove the 'chainChanged' and 'accountsChanged' event listeners
}
}, [])
The empty array tells this effect to only run once, when the component first renders. If you need it to run when some state changes, or you need access to any state variables inside the functions run in the effect, you need to list those in the dependency array.
You'll also want to return a function from the hook that removes your listeners so they don't hang around and continue to multiply every time a user visits the page, leaves, and returns.
You can read more about useEffect here.

How can I include my existing table into my export function?

I am relatively new to React-JS and was wondering how I could pass my variables to my export function. I am using the jsPDF library.
At the time the Summary page is showing up, every thing is already in the database.
The Summary page creates in every round an IdeaTable component, writes it into an array and renders it bit by bit if the users click on the Next button (showNextTable()).
This component can use a JoinCode & playerID to assemble the table that was initiated by this player.
import React, { Component } from "react";
import { connect } from "react-redux";
import { Box, Button } from "grommet";
import IdeaTable from "../playerView/subPages/ideaComponents/IdeaTable";
import QuestionBox from "./QuestionBox";
import { FormUpload } from 'grommet-icons';
import jsPDF from 'jspdf';
export class Summary extends Component {
state = {
shownTable: 0
};
showSummary = () => {};
showNextTable = () => {
const { players } = this.props;
const { shownTable } = this.state;
this.setState({
shownTable: (shownTable + 1) % players.length
});
};
exportPDF = () => {
var doc = new jsPDF('p', 'pt');
doc.text(20,20, " Test string ");
doc.setFont('courier');
doc.setFontType('bold');
doc.save("generated.pdf");
};
render() {
const { topic, players } = this.props;
const { shownTable } = this.state;
const tables = [];
for (let i = 0; i < players.length; i++) {
const player = players[i];
const table = (
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={player.id} />
</Box>
);
tables.push(table);
}
return (
<Box
style={{ wordWrap: "break-word" }}
direction="column"
gap="medium"
pad="small"
overflow={{ horizontal: "auto" }}
>
<QuestionBox question={topic} />
{tables[shownTable]}
<Button
primary
hoverIndicator="true"
style={{ width: "100%" }}
onClick={this.showNextTable}
label="Next"
/>
< br />
<Button
icon={ <FormUpload color="white"/> }
primary={true}
hoverIndicator="true"
style={{
width: "30%",
background: "red",
alignSelf: "center"
}}
onClick={this.exportPDF}
label="Export PDF"
/>
</Box>
);
}
}
const mapStateToProps = state => ({
topic: state.topicReducer.topic,
players: state.topicReducer.players
});
const mapDispatchToProps = null;
export default connect(mapStateToProps, mapDispatchToProps)(Summary);
So basically how could I include the IdeaTable to work with my pdf export?
If you want to use html module of jsPDF you'll need a reference to generated DOM node.
See Refs and the DOM on how to get those.
Alternatively, if you want to construct PDF yourself, you would use data (e.g. from state or props), not the component references.
Related side note:
On each render of the parent component you are creating new instances for all possible IdeaTable in a for loop, and all are the same, and most not used. Idiomatically, this would be better:
state = {
shownPlayer: 0
};
Instead of {tables[shownTable]} you would have:
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={shownPlayer} ref={ideaTableRef}/>
</Box>
And you get rid of the for loop.
This way, in case you use html dom, you only have one reference to DOM to store.
In case you decide to use data to generate pdf on your own, you just use this.props.players[this.state.shownPlayer]
In case you want to generate pdf for all IdeaTables, even the ones not shown, than you can't use API that needs DOM. You can still use your players props to generate your own PDF, or you can consider something like React-Pdf

Categories