I am rather new to Gatsby. I am using Strapi as backend and in my frontend Gatsby, in the GraphQL playground I am executing the following query with expected outcome:
query MyQuery {
strapiAssessment {
title
rating {
id
topic
rate
}
}
}
In my Gatsby frontend code I have the following:
import React from "react"
import Title from "../components/Title"
import { graphql } from "gatsby"
const SelfAssesment = ({ data }) => {
const {
strapiAssessment: { title, rating },
} = data
console.log(data)
return (
<section className="section jobs">
<Title title={title} />
<div className="about-stack">
<p>Rankning i olika teknologier:</p>
{rating.map(item => {
return <span key={item.id}>{item.topic}</span>
})}
</div>
<div className="about-stack">
{rating.map(item => {
return <span key={item.id}>{item.rate}</span>
})}
</div>
</section>
)
}
export const query = graphql`
{
strapiAssessment {
title
rating {
id
topic
rate
}
}
}
`
export default SelfAssesment
I am getting the following error:
I am getting undefined, do I have something undeclared or a typo somewhere?
You are running a page query in a component that is not in a top-level (components/SelfAssesment). As you can see from the docs:
Querying data with a Page Query
You can use the graphql tag to query data in the pages of your Gatsby
site. This gives you access to anything included in Gatsby’s data
layer, such as site metadata, source plugins, images, and more.
Querying data with the StaticQuery Component
StaticQuery is a component for retrieving data from Gatsby’s data
layer in non-page components, such as a header, navigation, or any
other child component.
That said, you have two options here. Use the query in a top-level component (page) and drill-down the props down to the component or use a StaticQuery (or useStaticQuery hook).
Related
What is the best way to load a page without refreshing using graphql and dynamic routing.
I have a file called kindergarten that loads perfectly without refreshing the whole page :
<script context="module">
import { gql, GraphQLClient } from 'graphql-request'
export async function load() {
const graphcms = new GraphQLClient(import.meta.env.VITE_GRAPHCMS_URL, {
headers: {},
})
const query = gql`
query MyQuery {
terms(where: { taxonomies: CATEGORY }) {
nodes {
slug
name
termTaxonomyId
}
}
}
`
const { terms } = await graphcms.request(query)
return {
props: {
posts: terms.nodes,
},
}
}
</script>
<script>
import { SITE_NAME } from '$lib/store.js'
let date = new Date()
const [month, day, year] = [
date.getMonth() + 1,
date.getDate(),
date.getFullYear(),
]
export let posts = []
</script>
<svelte:head>
<title>Sample Title - {SITE_NAME}</title>
<meta
name="description"
content="Sample description [Update: {year}/{month}/{day}]" />
</svelte:head>
{#each posts as post (post.termTaxonomyId)}
<a
tax-id={post.termTaxonomyId}
href="/kindergarten/province/{post.slug}"
target="blank">
{post.name}
</a>
<br />
{/each}
and also I have another page called [slug].svelte :
<script context="module">
import { gql, GraphQLClient } from 'graphql-request'
export async function load(ctx) {
let slug = ctx.page.params.slug
const graphcms = new GraphQLClient(import.meta.env.VITE_GRAPHCMS_URL, {
headers: {},
})
const query = gql`
query MyQuery {
terms(where: { taxonomies: CATEGORY, slug: "${slug}" }) {
nodes {
name
description
}
}
}
`
const { terms } = await graphcms.request(query)
return { props: { slug, post: terms.nodes } }
}
</script>
<script>
import { SITE_NAME } from '$lib/store.js'
export let slug
export let post
</script>
<svelte:head>
<title>{post[0].name} - {SITE_NAME}</title>
</svelte:head>
<h1>Slug : {slug}</h1>
{#each post as data}
<p>Name: {data.name}</p>
<br />
{#if data.description}
<p>Description: {data.description}</p>
{:else}
<p>Ther is no Description</p>
{/if}
{/each}
When I click a link on kindergarten page it goes to the subpage but refreshes the whole site.
How can I optimize the [slug].svelte file to prevent refreshing the page?
As I'm new to Svelte and Sveltekit, any ideas for optimizing the whole code is appreciated.
You're linking to a new page, so it makes sense it refreshes, because it's going to a whole new page ([slug].svelte). It sounds like you're trying to load data into your kindergarten.svelte page? In that case, make a component, not a page, where you can pass in data to the component, and the component will be updated, rather than the entire page. Check out an example from the docs here: https://svelte.dev/tutorial/component-bindings
When following official strapi-gridsome tutorial, trying to create new pages from ID numbers
Steps to reproduce:
npm: '6.14.9',
node: '14.14.0',
strapi '3.5.0'
Expected result:
http://localhost:8080/categories/1
should show data from API but instead shows 404 page
Actual result:
When running gridsome develop...
ERROR Failed to compile with 1 errors
This dependency was not found:
..\my-gridsome-site\src\templates\Category.vue in ./src/.temp/routes.js
gridsome.server.js
module.exports = function (api) {
api.loadSource(({ addCollection }) => {
// Use the Data Store API here: https://gridsome.org/docs/data-store-api/
})
api.createPages(async ({ graphql, createPage }) => {
const { data } = await graphql(`
{
allStrapiCategory {
edges {
node {
id
name
}
}
}
}
`);
const categories = data.allStrapiCategory.edges;
categories.forEach(category => {
createPage({
path: `/categories/${category.node.id}`,
component: './src/templates/Category.vue',
context: {
id: category.node.id,
},
});
});
});
};
src\templates\category.vue
<template>
<Layout>
<div>
<h1>{{ $page.category.name }}</h1>
<ul>
<li v-for="restaurant in $page.category.restaurants">{{ restaurant.name }}</li>
</ul>
</div>
</Layout>
</template>
<page-query>
query Category($id: ID!) {
category: strapiCategory(id: $id) {
name
restaurants {
id
name
}
}
}
</page-query>
I had this same issue.
I was running yarn develop and hot-reload was working for general playing with data, layout etc. But when editing gridsome.server.js these changes aren't reflected.
This is solved by re-starting the server. And now you'll have your dynamic pages.
I am building a React form in the CRM pane of Flex which will POST data to an external service when the agent fills out the form and hits a submit button. Using the example code from create-flex-plugin from Plugin Builder v3, I have successfully persisted the data from a form field in redux. However, as an agent in Flex, if I have multiple reservations open, when I toggle between them they use the same data. How do I design my form so that an agent can enter data in the form for different reservations and they are kept separately?
I am relatively new to Flex, React and Redux, and not sure if there is anything Flex-specific about what I need to do, especially when handling multiple concurrent reservations. I've thought of keeping a Map keyed by reservationId or taskId in redux, but it's not clear how I'd pass the taskId into the reducer. I am also not sure if other tools like redux-form will play nice with Flex's design.
Being pointed in the right direction or getting some sample code would be a great help.
My current implementation, which persists a field called 'subcategory', looks like this:
HrmFormState.js
const UPDATE_FORM = 'UPDATE_FORM';
const initialState = {
subcategory: 'my category',
};
export class Actions {
static updateForm = (e) => ({ type: UPDATE_FORM, text: e.target.value });
}
export function reduce(state = initialState, action) {
switch (action.type) {
case UPDATE_FORM: {
return {
...state,
subcategory: action.text,
};
}
default:
return state;
}
}
HrmForm.Container.js
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Actions } from '../../states/HrmFormState';
import HrmForm from './HrmForm';
const mapStateToProps = (state) => ({
subcategory: state['hrm-form'].hrmForm.subcategory,
});
const mapDispatchToProps = (dispatch) => ({
updateForm: bindActionCreators(Actions.updateForm, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(HrmForm);
HrmForm.jsx (snippet)
render() {
if (!this.props.task) {
return null;
}
return (
<HrmFormComponentStyles>
<form onSubmit={this.handleSubmit}>
<label>
Subcategory:
<input type="text" value={this.props.subcategory}
onChange={this.props.updateForm} />
</label>
<input type="submit" value="Submit" />
</form>
</HrmFormComponentStyles>
);
}
I have an application with one subscription already using subscribeToMore
Query Component:
<Query query={GET_MESSAGES}>
{({ data, subscribeToMore }) => {
return (
<MessageList
messages={data.allMessages}
subscribeToMore={subscribeToMore}/>
);
}}
</Query>
That query loads a list of messages where as far as I know we attach the subscriber in the ComponentDidMount so whenever I add a new element into my list inside the Query, my subscription will listen to the subscription and do whatever I want (in this case add the new message into my current list of messages).
List of Messages Component:
export default class MessageList extends React.Component {
componentDidMount() {
this.props.subscribeToMore({
document: MESSAGE_CREATED,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
return {
allMessages: [
subscriptionData.data.messageCreated,
...prev.allMessages
],
};
},
});
}
render() {
return (
<div>
{this.props.messages.map(message => (
<MessageElement key={message.id}
message={message}/>
))}
</div>
);
}}
I would like to add another subscription so if I edit my message the information is updated in real time. In order to achieve that I have created the following component using Subscription
Message component (where I would like to add another subscription based on an updated message)
export default class MessageElement extends Component{
componentDidMount() {
this.messageUpdatedSubscription();
}
messageUpdatedSubscription = () => (
<Subscription
subscription={MESSAGE_UPDATED}>
{({ data: { messageUpdated } }) => (
console.log('Do smthg???',messageUpdated)
)}
</Subscription>
);
render(){
return(
<Mutation mutation={UPDATE_MESSAGE} key={this.props.message.id}>
{updateMessage => (
<div>
<div>{this.props.message.text}</div>
<div>
<Star active={this.props.message.isFavorite}
onClick={() => { updateMessage({ variables: {
id: this.props.message.id,
text:this.props.message.text,
isFavorite:!this.props.message.isFavorite } });
}}/>
</div>
</div>
)}
</Mutation>);
}}
My subscriptions on the server are working as I already have the subscription for MESSAGE_CREATED on the Query working correctly and I have tested that on the server my subscription for the MESSAGE_UPDATED is triggered. However, I cannot figure out why the UI is not displaying or console.log anything as if it is not listening to the subscription.
Can I achieve this with a subscription component or with a subscribeToMore?
Thanks in advance
The subscription component cannot be initiated in ComponentDidMount. It has to reside in a render() lifecycle method. It's parameter can be used in ComponentDidMount, but not the component.
I can think of 3 possible solutions:
1) You could try to put the Subscription method in your render method. You would just need to nest this inside or outside of your Mutation component.
2) You could initiate the Subscription Component in this component's parent and pass its parameter (messageUpdate) down to the component MessageElement. You could then use messageUpdate off of props in ComponentDidMount.
3) You could use Apollo's higher order component. You could then access messageUpdate in props. (Disclaimer - I have not tried this before).
I hope this helps!
Based on the suggestion of #cory-mcaboy I nested my subscription into the mutation.
I also found out that as I had a list of messages and I just wanted to trigger the subscription based on the message I am updating and not the entire list; I had to modified my subscription on the backend and on the front end in the following way:
Server schema
const typeDefs = gql` type Subscription {
messageUpdated(id: Int!): Message}`;
Server resolver
Subscription: {
messageUpdated: {
subscribe: withFilter(
() => pubsub.asyncIterator([MESSAGE_UPDATED]),
(payload, variables) => {
return payload.messageUpdated.id === variables.id;
},
),
},
}
Client component
const MESSAGE_UPDATED = gql` subscription messageUpdated($id: Int!){
messageUpdated(id:$id)
{
id
text
isFavorite
}}`;
export default class MessageElement extends Component{
render(){
return(<Mutation mutation={UPDATE_MESSAGE} key={this.props.message.id}>
{updateMessage => (
<div>
<div>{this.props.message.text}</div>
<Subscription subscription={MESSAGE_UPDATED}
variables={{ id: this.props.message.id }}>
{() => {
return <Star active={this.props.message.isFavorite}
onClick={() => { updateMessage({ variables: {
id: this.props.message.id,
text: this.props.message.text,
isFavorite: !this.props.message.isFavorite } });}}/>
}}
</Subscription>
</div>
)}
</Mutation>
);
}}
You can see the code in the following repo: back-end itr-apollo-server, front-end itr-apollo-client
I saw a lot of users struggling to use useSubscription in Class Component as Apollo deprecated the Subscription component in the newer version. So, I decided to create quick gist of how to use it.
https://gist.github.com/syedadeel2/a9ec6ff0d9efade2b83d027f32ce21dc
Hi I am using axios to fetch JSON data in React, the issue is that I am not able to search within the fetched data.
I tried fetching the data in the parent component but the request is asynchronous so it loads child components first and then fetches data.
Below is my code :
axios.get("/url.json")
.then(function(result) {
teams= result.data.teams
});
ReactDOM.render(
<div>
<App teams={teams}/>
</div>
,document.getElementById('app')
)
If I fetch data using axios inside the child component how do I save the data for search? i.e I need to search in the unfiltered data.
Call ReactDOM.render after request succeeding : I mean inside the callback of axios NOT out side.
axios.get("/url.json")
.then(function(result) {
const teams= result.data.teams;
ReactDOM.render(
<div>
<App teams={teams}/>
</div>
,document.getElementById('app')
)
});
..... OR.....
As BEST PRACTICES, you can add another layer (super-parent) which handles this call inside its componentDidMount :
class Root extends React.Component {
constructor() {
super(...arguments);
this.state= {teams : []};
}
componentDidMount() {
axios.get("/url.json")
.then((result) => {
this.setState({teams: result.data.teams})
});
}
render() {
return (
<div>
<App teams={this.state.teams}/>
</div>
)
}
}
ReactDOM.render(
<Root />
,document.getElementById('app')
)