Graphql query result comes back undefined - reactjs

I am getting the error Uncaught (in promise) TypeError: Cannot read property 'privateKey' of undefined
Employee query result comes back undefined when trying to console.log this.props.employee
I am using Graphql and Next.js. Am unsure whether or not componentWillMount is the correct lifecyle method to use as this.props.data.employee is undefined.
class EmployeeTable extends Component {
state = {
employeesList: [],
privateKey: ""
}
fetchEmployees = async () => {
console.log(this.props.data.employee);
console.log(this.props.data.employee.privateKey);
const adminWallet = new ethers.Wallet(this.state.privateKey, provider);
const EmployeeStore = new ethers.Contract(address, abi, adminWallet);
let count;
await EmployeeStore.functions.employeesCount().then(function(value) {
count = value;
});
let employeesList = [];
for(let i = 1; i<=count; i++) {
await EmployeeStore.getEmployeeByIndex(i).then(function(result) {
employeesList.push(result);
});
};
console.log(employeesList);
return {employeesList};
};
componentWillMount = async () => {
var employees = await this.fetchEmployees();
this.setState({employeesList: employees});
};
renderRows() {
return this.state.employeesList.map((employee, index) => {
return (<EmployeeRow
key={index}
employee={employee}
/>
);
});
};
render() {
const { Header, Row, HeaderCell, Body } = Table;
return(
<div>
<h3>Employees</h3>
<Table>
<Header>
<Row>
<HeaderCell>Name</HeaderCell>
<HeaderCell>Employee ID</HeaderCell>
<HeaderCell>Address</HeaderCell>
<HeaderCell>Authenticated</HeaderCell>
</Row>
</Header>
<Body>{this.renderRows()}</Body>
</Table>
</div>
)
}
}
const employee = gql`
query employee($employeeID: String){
employee(employeeID: $employeeID) {
privateKey
}
}
`;
export default graphql(employee, {
options: {
variables: {employeeID: "1234"}
},
})
(EmployeeTable);

The first time a component wrapped with a Query operation, like in your code, is rendered it receives the data prop but without the results yet.
data contains a field called loading. If it is set to true, it means the query didn't receive all the results from the server yet. If the operation is successful, next time your component is rendered this.props.data will have loading === false and this.props.data.employee should be have a value as you expect.
Basically, you should check if this.props.data.loading is true or false before calling fetchEmployees() and before rendering child components that rely on the results.

Related

Rendering elements of array fetched with React does not work

I'm successfully fetching an array of objects from a local ethereum blockchain (successful as in, I've logged the data in componentDidMount and it is what its supposed to be). Its a dynamically sized array
(inventory), so I gotta fetch it element by element. Here are the relevant parts:
async componentDidMount() {
this.setState({fetching: true}, () => {
this.loadData()
.then(result => this.setState({fetching: false}))
})
}
async loadData() {
if (typeof window.ethereum !== 'undefined') {
const web3 = new Web3(window.ethereum)
const netId = await web3.eth.net.getId()
const accounts = await web3.eth.getAccounts()
this.setState({account: accounts[0]})
if(typeof accounts[0] !== 'undefined'){
const balance = await web3.eth.getBalance(accounts[0])
this.setState({balance: balance})
}
else {
window.alert('Please login with MetaMask')
}
try {
const plotRepository = new web3.eth.Contract(PlotRepository.abi, PlotRepository.networks[netId].address)
const plot = await plotRepository.methods.claimPlot(this.state.account).send({from: this.state.account})
this.setState({plot: new web3.eth.Contract(Plot.abi, _plot.plot)})
const size = await this.state.plot.methods.size().call()
const tiles = await this.state.plot.methods.getTiles().call()
await this.loadInventory() // this is where I'm loading the array in question
this.setState({size: size})
this.setState({tiles: tiles})
// logging the array here works
} catch (e) {
window.alert(e)
}
} else {
window.alert('Please install MetaMask')
}
}
async loadInventory() {
for (const [key, value] of Object.entries(inventoryinfo)) {
const item = await this.state.plot.methods.getInventoryItem(key).call()
this.setState({inventory: [...this.state.inventory, {
name: key,
value: parseInt(item[0][1]),
count: item[1]
}]})
}
}
Again, logging the array in the fetching functions works just fine. I'm also using a flag (fetching) which denotes whether all data has been successfully loaded or not, and I only try to render data once everything is loaded:
render() {
const { plot, account, fetching, tiles, inventory } = this.state
return (
<div className="App">
<p>Currently logged in with account: { this.state.account }</p>
<p>Balance: { this.state.balance } </p>
{ fetching ? // make sure things are fetched before rendering them
<p>Loading</p> :
<div>
<UserPlot tileData={tiles} plot={plot} account={account}/>
{
inventory[0].name // inventory[0] is undefined
// inventory.length is not
}
</div>
}
</div>
);
}
Trying to display any element of the array gives me an undefined error. Rendering the array length works though ({inventory.length}). This is weird since I'm doing the same thing with another array that I'm fetching and displaying in the UserPlot component and that works just fine (only difference is that in this case the array is static and I can load it all in one go but I don't think that has anything to do with it)

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

React: An component attribute is not properly storing the data (from a query) that I want

I recently started learning react and I have encountered something that I do not understand. So when I declare a component I also declare an attribute in the constructor. Then, after executing the first query (I am using Apollo client - GraphQL ) I want to store the result (which I know that will be always an email) in the attribute declared so I can use it as a parameter in the second query.
The app logic is that I want to show all the orders of a given email, but first I get the email with a query.
Here is the code:
export default class Orders extends Component {
constructor(){
super();
this.email = '';
}
render() {
return (
<div>
<Query query = { GET_MAIL_QUERY }>
{({data, loading}) => {
if (loading) return "Loading...";
this.email = data.me.email;
return <h1>{this.email}</h1>
}}
At this point a header containing the email is returned, so all good. But when I execute the second query (or try to display the email in the second header for that matter) it seems that the value is not properly stored.
</Query>
<h1>{this.email}</h1>
<Query query = { GET_ORDERS_QUERY }
variables = {{
email: this.email
}}>
{({data, loading}) => {
if (loading) return "Loading...";
console.log(data);
let orders = data.ordersByEmail.data;
console.log(orders);
return orders.map(order =>
<div>
<h1>{order.date}</h1>
<h1>{order.price}</h1>
<h1>{order.conference.conferenceName}</h1>
<h1>{order.user.email}</h1>
<br></br>
</div>)
}}
</Query>
</div>
)
}
}
const GET_MAIL_QUERY = gql`
query getMyMail{
me{
email
}
}
`;
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference{
conferenceName
}
}
}
}
`;
I would love an explanation for this and maybe a solution (to store a value returned from a query to use it in another)
Thanks in anticipation :)
In my experience, you should use useQuery imported from #apollo/react-hooks with functional component because it's easy to use, it makes your code more cleaner
If your want to use <Query/> component with class component, it's ok. But, if you want to store data received from server, you should create a variable in state of constructor and when you want to update to state, you should use this.setState({email: data.me.email}). Don't use this.state.email = data.me.email, it's anti-pattern, React will not trigger re-render when you use it to update your state.
This is the code:
import React, { useState } from 'react'
import gql from 'graphql-tag'
import { useQuery, useMutation } from '#apollo/react-hooks'
const GET_MAIL_QUERY = gql`
query getMyMail {
me {
email
}
}
`
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference {
conferenceName
}
}
}
}
`
const Orders = () => {
const [email, setEmail] = useState('')
const { data: getMailQueryData, loading, error } = useQuery(GET_MAIL_QUERY, {
onCompleted: data => {
setEmail(data.me.email)
},
onError: err => alert(err),
})
const { data: getOrdersQueryData } = useQuery(GET_ORDERS_QUERY, {
variables: { email: email },
})
if (loading) return <div>Loading...</div>
if (error) return <div>Error...</div>
return ...
}

React - Render happening before data is returned and not updating component

I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

Resources