Get state from select form in child component with Gatsby - graphql

I code a Gatsby app with a Main page and two components. The value from a select form will be used to query a Postgresql database through a graphql query.
What I can already do: in the form component, I get the value from the select menu and pass it from this child component to the parent (the main page). In the data component, I can query the database with graphql and get the results with hardcoded values.
What I can't do yet: get the value from the select component to the data component and use it in my graphql query.
I tried different ways to get the value without success using this.props.value1 or this.state.value1. I also tested a simple component to make sure I could get the value from the parent to a child component and it worked seamlessly. So it's the way I try to import the value in a querying component that is the problem.
**//Data component**
let val = 88 //for hardcoded test. That works.
const DataPage = () => {
const data = useStaticQuery(query)
return (
<div>
<p>From Postgres: {data.postgres.allEstivalsList[val].nbr}</p>
</div>
)
}
const query = graphql`
{
postgres {
allAveragesList {
avg
}
allEstivalsList {
year
nbr
}
}
}
`;
export default DataPage;
**//Main page**
export default class App extends React.Component {
constructor(props) {
super(props);
}
state = {
value1: null,
// other values
}
render() {
return (
<div>
<p>Get state in main page: {this.state.value1}</p>
<DataPage val = {this.state.value1} />
<SelectForm clickHandler={y => { this.setState({ value1: y }); }} />
</div>
)
}
}
**//Form component**
export default class IndexPage extends React.Component {
state = {
value1: null,
}
handleClick = () => {
this.props.clickHandler(this.state.value1);
this.setState(prevState => {
return { value1: prevState.value1 };
});
};
render() {
return (
<Formlayout>
<p>Test -- Value for the selected year: {this.state.value1}</p>
<select onChange = {(e) => this.setState({ value1: e.target.value })}>
<option value="">-- Year --</option>
<option value="1">1901</option>
<option value="2">1902</option>
<option value="3">1903</option>
</select>
<button onClick={this.handleClick}>Go!</button>
</Formlayout>
)
}
}
I'd appreciate to get some directions to get the select value in the data component. As my test variable val is effectively working when used in the query, what I'd like to achieve is to apply to that variable the state from the component. And that's where I'm stuck right now.

The GraphQL query is executed at the build time, not at runtime. Have a look at the docs, it's possible to query data at build and at runtime.
https://www.gatsbyjs.org/docs/client-data-fetching/
// edit
Normally you would pre generate all (static) content pages - e.g. if you have data for 50 years, you let gatsby build those 50 pages and then you could navigate by path to the page.
If you want to pass a variable to the query, check the docs: https://www.gatsbyjs.org/docs/graphql-reference/#query-variables
query GetBlogPosts(
$limit: Int, $filter: MarkdownRemarkFilterInput, $sort: MarkdownRemarkSortInput
) {
allMarkdownRemark(
limit: $limit,
filter: $filter,
sort: $sort
) {
edges {
node {
frontmatter {
title
date(formatString: "dddd DD MMMM YYYY")
}
}
}
}
}

Related

graphql-codegen + typescript-svelte-apollo - Refetch not working?

(Edited)
Refetching with the generated query type seems to work different from the refetch function in Apollo Client useQuery. I don't understand how to phrase it - can anyone provide an example?
I'm realizing the problem is probably either my refetch is not properly phrased, or maybe the store is only hitting the cached query. I've been going over my code for days and I can't figure out what it could be. I've tried await blocks too.
The refetch worked with svelte-apollo, but i'm trying to eliminate that dependency. I've also tried Apollo Client's useQuery, but the whole point of graphql-codegen with typescript-svelte-apollo is to use the generated typescript wrapper for the query.
When I assign the generated query to a reactive constant in my Svelte front-end code,
$: observations = getObservations({ variables: { filter } });
the query does not refetch when i update the query variables, as I would expect.
This is how my svelte template is using the query. The filter object changes based on a form user input. I've tried this with an await block too.
<script lang="ts">
import { getObservations } from '$lib/generated';
$: observations = getObservations({ variables: { filter } });
function handleFilter(event) {
filter = event.detail;
}
</script>
{#if $observations.loading}
Loading...
{:else if $observations.error}
{$observations.error}
{:else if $observations.data}
{#each $observations.data['observations']['edges'] as edge}
<Item node={edge['node']} />
{/each}
{/if}
Since this plugin allows to use the query directly, without Apollo's useQuery, i'm not sure how to phrase a refetch.
If i do $observations.refetch(); inside handleFilter(e), i get an error
Property 'refetch' does not exist on type 'Readable<ApolloQueryResult<GetObservationsQuery> & { query: ObservableQuery<GetObservationsQuery, Exact<{ filter?: FilterObservationsInput; }>>; }>'.ts(2339)
There's nothing fancy in my config. Am I doing something wrong here?
schema: src/graphql/schema.graphql
documents:
- src/graphql/queries.graphql
- src/graphql/mutations.graphql
generates:
src/lib/generated.ts:
plugins:
- typescript
- typescript-operations
- graphql-codegen-svelte-apollo
config:
clientPath: src/lib/shared/client
# asyncQuery: true
scalars:
ISO8601Date: Date
ISO8601DateTime: Date
Here's the client:
export default new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
observations: relayStylePagination(),
},
},
},
})
});
The generated query:
export const getObservations = (
options: Omit<
WatchQueryOptions<GetObservationsQueryVariables>,
"query"
>
): Readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
> => {
const q = client.watchQuery({
query: GetObservationsDoc,
...options,
});
var result = readable<
ApolloQueryResult<GetObservationsQuery> & {
query: ObservableQuery<
GetObservationsQuery,
GetObservationsQueryVariables
>;
}
>(
{ data: {} as any, loading: true, error: undefined, networkStatus: 1, query: q },
(set) => {
q.subscribe((v: any) => {
set({ ...v, query: q });
});
}
);
return result;
}
Here's the query document that it's built from:
query getObservations($filter: FilterObservationsInput) {
observations(filter: $filter) {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
createdAt
updatedAt
when
where
imgSrcThumb
imgSrcSm
imgSrcMed
thumbImage {
width
height
}
name {
formatName
author
}
user {
name
login
}
rssLog {
detail
}
}
}
}
}

Use the first query result as a condition for the second query

I have 2 queries like this
const featuredArticles = gql `
query featureArticles() {
articles(where: {limit: 4, feature: true, sort: "published_at:desc") {
id
}
}
`;
const NO_FEATURE_ARTICLES_QUERY = gql`
query noFeatureArticles($slug: String!) {
articles(where: { id_nin: ??? },limit: 4, sort: "published_at:desc") {
${SINGLE_ARTICLE_MODEL}
}
}
`;
The first query will get the ID of 4 latest featured articles, the second one will get all articles available in the database. Now, I'm trying to change the second query to get all articles EXCEPT 4 articles from the first query but I just don't know how to use the result as a condition. Can you guy give me some hint? Thanks in advance!
My index.jsx looks like this:
const Home = () => (
<div style={{
overflowX: 'hidden',
}}
>
let arr=[]
<Query query={FEATURE_ARTICLES_QUERY}>
{({ data: { articles } }) => {
if (!articles.length) {
return null;
}
arr=[articles[0].id,.......,articles[10].id];
return <SectionFeature articles={articles} />;
}}
</Query>
<Query query={ARTICLES_QUERY} id={arr}>
{({ data: { quotes } }) => {
if (!quotes.length) {
return null;
}
return <SectionQuote quotes={quotes} />;
}}
</Query>
</div>
);
export default Home;
For example, I want to pass 1st query's result to arr[], then pass it to 2nd query. But the array will be gone right after the 1st query ends.
UPDATE: I'd done it. Thank you so much for the hint

using a single query to call multiple queries using apollo react hooks

I am new to this graphql, so what i am trying to achieve here is, i need to call two queries parallelly basically combining the queries and get the loading at a single time.
here i have seperated the queries and using two loading state, i need to combine it like compose.
The use case is, user clicks on the button on the button click i will pass the id, and hit both queries and get the list of data
<Button
text="Details"
onClick={() => {
getNames({ variables: { location_id: Number(location_id) } });
getSchools({ variables: { location_id: Number(location_id) } });
}}
>
const [getNames, { loading: namesLoading, data: namesData }] = useLazyQuery(
GET_NAMES
);
const [
getSchools,
{ loading: schoolsLoading, data: schoolsData },
] = useLazyQuery(GET_SCHOOLS);
const GET_NAMES = gql`
query get_names($location_id: Int!) {
get_names(location_id: $location_id) {
id
name
}
}
`;
const GET_SCHOOLS = gql`
query get_schools($location_id: Int!) {
get_schools(location_id: $location_id) {
schools {
id
name
}
}
}
`;
With splitting i am able to see the data, i am using "#apollo/react-hooks" to get the data with useLazyQuery, how can i achieve with this a single query. So instead of multiple loading , error data i can have a single one
Didn't find any compose hook from this library

How to get 'Last Update Date' of a blog post in GATSBY.js

Hello I'm not a dev so may the question will be easy for you guys. I used the advance starter from gatsby site. The blog is working perfect but I need to provide the LAST UPDATED time under my title. Searched for some solutions but none of them worked. Could you Provide some help?
gatsby-node.js
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type !== 'MarkdownRemark') {
return;
}
const fileNode = getNode(node.parent);
createNodeField({
node,
name: 'modifiedTime',
value: fileNode.mtime
});
};
`````````````````````````
PostListing.jsx
class PostListing extends React.Component {
getPostList() {
const postList = [];
this.props.postEdges.forEach(postEdge => {
postList.push({
path: postEdge.node.fields.slug,
tags: postEdge.node.frontmatter.tags,
cover: postEdge.node.frontmatter.cover,
title: postEdge.node.frontmatter.title,
date: postEdge.node.fields.date,
excerpt: postEdge.node.excerpt,
timeToRead: postEdge.node.timeToRead,
modifiedTime:postEdge.node.modifiedTime
});
});
return postList;
}
render() {
const postList = this.getPostList();
return (
<div className='posts'>
{/* Your post list here. */
postList.map(post => (
<Fragment>
<div className='singlePost__date'>
<h4 style={{color:'white'}}> {post.modifiedTime}</h4>
</div>
<div className='singlePost__Title'>
<Link classname='singlePost' to={post.path} key={post.title}>
<h1 className='singlePost__title'>{post.title}</h1>
</Link>
</div>
</Fragment>
))}
</div>
);
}
}
export default PostListing;
I expect something like
TITLE
last updated : 3/2/2019
You can use information stored in Git to get the latest time when a file was modified.
1st approach
Track it manually, but this can be error-prone if you forget to edit the modified time. So I would recommend that as the last option if you can't get others to work.
2nd approach
You can edit your gatsby-node.js to pull information from Git like so:
const { execSync } = require("child_process")
exports.onCreateNode = ({ node, actions }) => {
// ...
if (node.internal.type === "MarkdownRemark") {
const gitAuthorTime = execSync(
`git log -1 --pretty=format:%aI ${node.fileAbsolutePath}`
).toString()
actions.createNodeField({
node,
name: "gitAuthorTime",
value: gitAuthorTime,
})
}
// ...
}
Then, in your template, you can fetch it:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
fields {
gitAuthorTime
}
# ...
}
}
And, finally, use it in JSX like so:
import React from "react"
const BlogPost = (props) => {
const { gitAuthorTime } = props.data.markdownRemark.fields
render(<p>Updated at: ${gitAuthorTime}</p>)
}
export default BlogPost
3rd approach
Similar to the previous one but it uses a plugin gatsby-transformer-info. It does a similar thing as in the 2nd approach, but you need to access the modified time differently this time. Like so:
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
# ...
parent {
... on File {
fields {
gitLogLatestDate
}
}
}
# ...
}
}
I wrote more about this in my blog post "Add Updated At To Your Gatsby Blog" if you want to check it out.
Edit: The answer below is actually wrong, since File. modifiedTime is the modifiedTime of the markdown file itself & not the modifiedTime for your content. For example, if you deploy your blog on say, Netlify, then the modifiedTime of your files there will be different than in your local environment.
I think the right answer is to track it separately. If you're using a CMS like NetlifyCMS, you can create a field that automatically update the date/time on every edit.
Wherever you're querying for your markdown files, you can use the below field:
query {
allMarkdownRemark {
edges {
node {
frontmatter { /* other stuff */ }
parent {
... on File {
modifiedTime(formatString: "MM/DD/YYYY")
}
}
}
}
}
}
And access it in your via postEdge.node.parent.modifiedTime

Descending orderByChild() on firebase and Ionic 2

I need get items sorted by date, but obviously I need descending sorted to show the posts in correct order...
import {
AngularFireDatabase
} from 'angularfire2/database';
import 'rxjs/add/operator/map';
/*
Generated class for the FirebaseProvider provider.
See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
#Injectable()
export class FirebaseProvider {
constructor(public afd: AngularFireDatabase) {}
getPostsItems() {
return this.afd.list('/Posts/', {
query: {
orderByChild: "date",
}
});
}
This query returns a ascendent order and I need a descendent order that it's not explained in Firebase web.
Which are queries I need?
One approach could be reversing the order in your component's template. First, you get a list of posts directly in your component:
posts.component.ts
export class PostsComponent {
posts: FirebaseListObservable<any>;
constructor(db: AngularFireDatabase) {
this.posts = db.list('/posts', {
query: {
orderByChild: 'date'
}
});
}
}
Then, you can use the reverse method to reverse your posts' order in your template:
posts.component.html
<div *ngFor="let post of (posts | async)?.slice().reverse()">
<h1>{{ post.title }}</h1>
</div>
Leaving this for Ionic 3 + AngularFire 4
posts.component.ts
import { AngularFireDatabase } from 'angularfire2/database';
export class PostsComponent {
posts;
constructor(db: AngularFireDatabase) {
this.posts = db.list('/posts', ref => ref.orderByChild('date')).valueChanges();
}
}
posts.component.html
<div *ngFor="let post of (posts | async)?.slice().reverse()">
<h1>{{ post.title }}</h1>
</div>
You should also use limitTolast() instead of limitToFirst() to ensure that your array will start with the last value that match with your filter in your DB (by default the filter is increasing order) and then use .reverse on your array to reverse it order:
1 - let array = db.list('/posts')ref => ref.orderByChild('date').limitToLast(5);
array = array.reverse();
Working without async in the ngFor
posts.component.ts
export class PostsComponent {
posts: FirebaseListObservable<any>;
constructor(db: AngularFireDatabase) {
this.posts = db.list('/posts', {
query: {
orderByChild: 'date'
}
});
}
}
Then, you can use the reverse method to reverse your posts' order in your template:
posts.component.html
<div *ngFor="let post of posts?.slice().reverse()">
<h1>{{ post.title }}</h1>
</div>

Resources