how to get attributes from this javascript array - arrays

How to get attribute title from this array which has only one data record,
I just want to print title, for example
If I write console.log(data) it shows the result as shown in picture below, but when I write conosle.log(data[0].attributes.title) it shows 0 is undefined, how can I print title in console.log?
Full Code
const PostPage = ({ slug }) => {
// console.log(slug)
const QUERY = gql `query getPosts($slug: String!){
posts(filters: { slug: { eq: $slug } }) {
data {
id
attributes {
title
slug
description
}
}
}
}
`;
const { data, loading,error } = useQuery(QUERY,{ variables: {slug}});
console.log(data)
return (
<div class="card p-1">
</div>
);
};
export default PostPage;
export async function getServerSideProps({ query }) {
const slug = query.slug
console.log(slug)
return { props: { slug } };
}

Looks like you want:
console.log(data.posts.data[0].attributes.title)
updated based on your comment that the log in the image is of 'data'.
update 2: because data is populated using a hook that is probably asynchronous, you should expect it may be undefined or null until some promise is resolved.
if (data) {
console.log(data.posts.data[0].attributes.title)
} else {
console.log('Zzz... waiting for data')
}

posts.data[0].attributes.title = "Trump got some money..."
data[0].attributes.title = undefined

Related

Getting reference data from Graphql using Gatsby/Contentful

I'm struggling to get the data from the reference part of my Graphql. I got a Contentful content model that includes references as you can see in the query below.
Now, I got a simple page like this, where I query data from Contentful:
import React from "react"
import { graphql } from "gatsby"
const PageTemplate = ({
data: {
islands: {
id,
title,
featuredActivities: { id, title },
},
},
}) => {
return (
<article key={id}>
<div>
<h3>{title}</h3>
</div>
</article>
<article key={featuredActivities.id}>
<div>
<h3>{featuredActivities.title}</h3>
</div>
</article>
)
}
export const query = graphql`
query GetSinglePage($slug: String) {
islands: contentfulPage (slug: {eq: $slug}) {
id
title
featuredActivities {
id
title
category
price
image {
fluid {
...GatsbyContentfulFluid
}
}
}
}
}
`
export default PageTemplate
And now I want to get the data for this part of the query:
featuredActivities {
id
title
category
price
image {
fluid {
...GatsbyContentfulFluid
}
}
}
I've tried this:
const PageTemplate = ({
data: {
islands: {
featuredActivities: { id },
featuredActivities: { title },
},
},
and added it like this into the :
<article key={featuredActivities.id}>
<h3>{featuredActivities.title}</h3>
</article>
But it's not working, does someone knows what I'm doing wrong?
Thanks in advance
[![enter image description here][1]][1]
And now I want to get the data for this part of the query
You can't destructure all items of the array like this:
const PageTemplate = ({
data: {
islands: {
featuredActivities: { id },
featuredActivities: { title },
},
},
Why? Because it's an array of objects and the structure ({}) doesn't match the type of the nested item. And, in case it was, and you will need to enter each specific position.
You may need to:
const PageTemplate = ({
data: {
islands: [{
featuredActivities: { id },
featuredActivities: { title },
}],
},
Notice the wrapping square brackets ([]).
However, as I said, it's not ideal since you need to print each specific position of the array of an unknown length. The best and optimal solution is to destructure until featuredActivities and loop through all elements:
const PageTemplate = ({
data: {
islands: {
id,
title,
featuredActivities,
},
},
}) => {
return <>
{featuredActivities.map(({title, id})=>{
return <div key={id}>{title}</div>
})}
</>
}
In that way, you can destructure inside the same loop in ({title, id}), since you are getting for each specific position (featuredActivity, the iterable variable), the title and the id (and so on for the rest of the needed fields).
Assuming that Islands and featuredActivities are not array or objects and you are getting the correct data from GraphQL query, you just need to correct how you destructure and use the values. Also since you cannot have clashing variable names, you will have to rename the variables from featuredActivities
const PageTemplate = ({
data: {
islands: {
id, title
featuredActivities: { id: featuredId , title: featuredTitle },
},
},
...
<article key={featuredId}>
<h3>{featuredTitle}</h3>
</article>

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 ...
}

How to select single item from a GraphQL query and pass it in to Apollo?

I'm trying to figure out how to select a single item from a query, but I'm getting stuck. I have a list of items (pokemons), but when I click on one I will like to select this particular item (pokemon), rerun a query with the selected item so I can get all its details.
Here is a code sandbox with the example.
And here is some of the code:
// graphql queries
import gql from 'graphql-tag'
const GET_ALL_POKEMONS = gql`
{
Pokemons(first: 151) {
id
name
image
types(first: 1) {
name
}
abilities {
name
}
stats {
name
value
}
}
}
`
const GET_POKEMON_DETAILS = gql`
query PokemonDetails($pokemonName: String!) {
Pokemon(name: $pokemonName) {
name
types {
name
}
abilities {
name
}
stats {
name
value
}
}
}
`
export { GET_ALL_POKEMONS, GET_POKEMON_DETAILS
}
// Pokemon Detail view
function PokemonDetails(props): ReactNode {
const { pokemonName } = props.name
// This is the Query I will like to use to get a detail view of my pokemon:
// Having problems hooking it up to the onClick
const { loading, error, data, networkStatus } = useQuery(GET_POKEMON_DETAILS, {
variables: { name: pokemonName },
})
if (loading) return <Paragraph>Loading...</Paragraph>
if (error) return <Paragraph textColor="red">Error: {error.message}</Paragraph>
return <div>{/* Need to put the pokemon details here */}</div>
}
// This gives me a single Pokemon
function PokemonItem(props: PokemonItemProps): ReactNode {
const { pokemon } = props
function onHandleSave() {
console.log(`I was save: ${pokemon.name}`)
}
function onHandleSelect() {
console.log(`I was selected: ${pokemon.name}`)
}
return (
<CardArticle>
<Avatar alt="pokemon-image" src={pokemon.image} />
<Paragraph>ID: {pokemon.id}</Paragraph>
<Paragraph>Name: {pokemon.name}</Paragraph>
{/* [TODO]: Need to either use lodash or a `.map()` for the following data */}
<Paragraph>Type: {pokemon.types.name}</Paragraph>
<Paragraph>Abilities: {pokemon.abilities.name}</Paragraph>
<Paragraph>Stats: {pokemon.stats.name}</Paragraph>
<Button onClick={onHandleSelect}>Select this Pokemon</Button>
<Button onSubmit={onHandleSave}>Save this Pokemon</Button>
</CardArticle>
)
}
// This gives me a list of all my Pokemons
function PokemonList(props: PokemonListProps): ReactNode {
const { loading, error, data } = useQuery(GET_ALL_POKEMONS) // This works
if (loading) return <Paragraph>Loading...</Paragraph>
if (error) return <Paragraph textColor="red">Error: {error.message}</Paragraph>
return (
<CardsSection>
{data.Pokemons.map((pokemon) => (
<PokemonItem key={pokemon.id} pokemon={pokemon} />
))}
</CardsSection>
)
}

Why does react Apollo on update subscription returns the already updated value as prev?

I'm using apollo for a react project and with subscription on the creation, update and deletion of object. I use the subscribeToMore functionality to start the subscriptions. The prev value of the updateQuery callback is the correct value for the creation and deletion subscription. But for the update subscription the prev value already contains the updated value. Overall this is really nice, as I don't need to add my custom implementation on how to update the object and can just return prev, but I don't understand why this happens. From my understanding the previous value should be returned. Is this just backed in functionality of apollo or is this some weird bug?
Here is the component which implements the subscriptions:
import { useEffect } from 'react';
import { useQuery } from '#apollo/react-hooks';
import * as Entities from './entities';
import * as Queries from './queries';
interface IAppointmentData {
appointments: Entities.IAppointment[];
error: boolean;
loading: boolean;
}
function getAppointmentsFromData(data) {
return (data && data.appointments) || [];
}
export function useAllAppointments(): IAppointmentData {
const initialResult = useQuery(Queries.GET_APPOINTMENTS);
const { data, error, loading, subscribeToMore } = initialResult;
useEffect(() => {
const unsubscribeNewAppointments = subscribeToMore({
document: Queries.NEW_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const { newAppointment } = subscriptionData.data;
return Object.assign({}, prev, {
appointments: [...prev.appointments, newAppointment],
});
},
});
const unsubscribeUpdateAppointment = subscribeToMore({
document: Queries.UPDATE_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
return prev
},
});
const unsubscribeDeleteAppointments = subscribeToMore({
document: Queries.DELETE_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const { deleteAppointment: {
id: deletedAppointmentId
} } = subscriptionData.data;
return Object.assign({}, prev, {
appointments: prev.appointments.filter(item => item.id !== deletedAppointmentId),
});
},
});
return function unsubscribe() {
unsubscribeNewAppointments()
unsubscribeDeleteAppointments()
unsubscribeUpdateAppointment()
}
}, [subscribeToMore]);
return {
appointments: getAppointmentsFromData(data),
error: !!error,
loading,
};
}
And these are my graphql queries / subscriptions:
import { gql } from 'apollo-boost';
export const GET_APPOINTMENTS = gql`
{
appointments {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const NEW_APPOINTMENTS_SUB = gql`
subscription newAppointment {
newAppointment {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const UPDATE_APPOINTMENTS_SUB = gql`
subscription updateAppointment {
updateAppointment {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const DELETE_APPOINTMENTS_SUB = gql`
subscription deleteAppointment {
deleteAppointment {
id
}
}
`;
According to the official Apollo documentation
Note that the updateQuery callback must return an object of the same
shape as the initial query data, otherwise the new data won't be
merged.
After playing around for a bit it seems that if the id you are returning is the same as in your cache and the object has the same shape, the update happens automatically.
But if you actually follow the CommentsPage example in the link I provided you will see that the id that is returned is new, that is why they explicity assign the object with the new data.
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
entry: {
comments: [newFeedItem, ...prev.entry.comments]
}
});
Ive tested this with my own message app Im working on, when receiving new messages with new id I have to return the merged data myself. But when im updating the chatRooms to display which chatRoom should be at the top of my screen (which one has the newest message), then my update happens automatically and I just return prev.
If however you want a work around to explicitly check the data before updating it you could try this workaround I found on GitHub. You will just need to use a reverse lookup id or just a different variable other than id.
This is what I would do in your example to achieve this:
updateAppointment {
// was id
idUpdateAppointment // im sure updateAppointmentId should also work... i think
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
}

Graphql query result comes back undefined

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.

Resources