Programmatically create Gatsby pages from Contentful data - javascript

I am looking for help with GatsbyJS and Contentful. The docs aren't quite giving me enough info.
I am looking to programmatically create pages based on contentful data. In this case, the data type is a retail "Store" with a gatsby page at /retail_store_name
The index.js for each store is basically a couple of react components with props passed in e.g. shop name and google place ID.
Add data to contentful. Here is my example data model:
{
"name": "Store"
"displayField": "shopName",
"fields": [
{
"id": "shopName",
"name": "Shop Name",
"type": "Symbol",
"localized": false,
"required": true,
"validations": [
{
"unique": true
}
],
"disabled": false,
"omitted": false
},
{
"id": "placeId",
"name": "Place ID",
"type": "Symbol",
"localized": false,
"required": true,
"validations": [
{
"unique": true
}
],
"disabled": false,
"omitted": false
}
}
I've added the contentful site data to gatsby-config.js
// In gatsby-config.js
plugins: [
{
resolve: `gatsby-source-contentful`,
options: {
spaceId: `your_space_id`,
accessToken: `your_access_token`
},
},
];
Query contentful - I'm not sure where this should happen. I've got a template file that would be the model for each store webpage created from contentful data.
As mentioned this is just some components with props passed in. Example:
import React, { Component } from "react";
export default class IndexPage extends Component {
constructor(props) {
super(props);
this.state = {
placeId: "",
shopName: "",
};
}
render (){
return (
<ComponentExampleOne shopName={this.state.shopName} />
<ComponentExampleTwo placeId={this.state.placeId} />
);
}
I'm really not sure how to go about this. The end goal is auto publishing for non-tech users, who post new stores in Contentful to be updated on the production site.

You can create pages dynamically at build time and to do that you need to add some logic to the gatsby-node.js file. Here is a simple snippet.
const path = require('path')
exports.createPages = ({graphql, boundActionCreators}) => {
const {createPage} = boundActionCreators
return new Promise((resolve, reject) => {
const storeTemplate = path.resolve('src/templates/store.js')
resolve(
graphql(`
{
allContentfulStore (limit:100) {
edges {
node {
id
name
slug
}
}
}
}
`).then((result) => {
if (result.errors) {
reject(result.errors)
}
result.data.allContentfulStore.edges.forEach((edge) => {
createPage ({
path: edge.node.slug,
component: storeTemplate,
context: {
slug: edge.node.slug
}
})
})
return
})
)
})
}
the createPages that was exported is a Gatsby Node API function you can find the complete list in the docs here.
For the query allContentfulStore it's called like that because your contentType name is store the gatsby query will be allContentful{ContentTypeName}.
Finally, I created a youtube video series explaining how you can build a Gatsby website with Contentful. You can find it here
I hope this answer your question.
Cheers,
Khaled

Related

LwcWebpackPlugin with AliasModuleRecord for lwc.dev isn't loading HTML file for component

I use LwcWebpackPlugin to load Lightning Web Components. Everything works fine but because I want to load compoennts from 'lwc' direcotry the namescape won't work. I must get components from /lwc directory and I bypysed this with AliasModuleRecord (described here) where I import all components by name. Problem is that only JS file is loading. Browser don't throw any exception but HTML body is not rendered. I know that JS file works because console log from connectedCallback is showed in browser console. What I'm doing wrong?. Maybe I haven't imported some library? Here is fragment form my webpack.config.js:
new LwcWebpackPlugin({
modules: [
{ dir: 'src/modules' },
{ npm: 'lightning-base-components' },
...glob.sync(
`./{${sfdxProjectJSON.packageDirectories?.map(package => package.path).join(',')}}/main/default/lwc/**/*.js`,
{ ignore: ['./**/__tests__/**'] }
).map(path => {
const name = path.split('/').at(-2);
return {
"name": `c/${name}`,
"path": path.replace('./', '')
};
}, {})
]
})
and this all resoults with:
[{
name: 'ui/helloWorldExampleOne',
path: 'pb-base/main/default/lwc/helloWorld/__docs__/examples/helloWorldExampleOne/helloWorldExampleOne.js'
},
{
name: 'ui/helloWorld',
path: 'pb-base/main/default/lwc/helloWorld/helloWorld.js'
},
{
name: 'ui/exampleOne',
path: 'pb-commerce/main/default/lwc/exampleOne/exampleOne.js'
}]

WebDataRock TypeError: Cannot read property element of null

WebDataRock TypeError: Cannot read property 'element' of nul
AND
WebDataRocks: Pivot cannot be drawn.
Grettings.
I try to implement WebDataRocks in my react-app project, i can implementate the WebDataRock.Pivot Component but since begin to render de component the console print this message
webdatarocks.js:180 Uncaught TypeError: Cannot read property 'element' of null
at new e (webdatarocks.js:180)
at b.setControls (webdatarocks.js:1006)
at webdatarocks.js:992
and afther a few seconds the console print this:
index.js:1 WebDataRocks:
Pivot cannot be drawn.
import React from "react";
import ReactDOM from "react-dom";
import WebDataRocks from "webdatarocks";
export class Pivot extends React.Component<WebDataRocks.Params, any> {
webdatarocks: WebDataRocks.Pivot;
componentDidMount() {
const tempProps : any = this.props;
const pivotData = tempProps.pivotData;
const columnsData : any[] = tempProps.columnsData;
const tempColums = [];
const tempKeys = [];
columnsData.forEach(cd => {
const tempLabelColumn = cd.title;
tempColums.push(tempLabelColumn);
tempKeys.push(cd.key);
});
this.webdatarocks = new WebDataRocks({
toolbar: true,
report: {
dataSource: {
data: pivotData
},
slice: {
rows: tempKeys,
expands: {
expandAll: true
},
measures: [{
"uniqueName": "Price",
"aggregation": "sum",
"format": "currency"
}, {
"uniqueName": "Discount",
"aggregation": "sum",
"format": "currency"
}],
columns: tempColums,
},
options: {
"grid": {
"type": "flat"
},
},
formats: [{
"name": "",
"thousandsSeparator": " ",
"decimalSeparator": ".",
"decimalPlaces": 2,
"maxSymbols": 20,
"currencySymbol": "",
"currencySymbolAlign": "left",
"nullValue": " ",
"infinityValue": "Infinity",
"divideByZeroValue": "Infinity"
}]
},
container: ReactDOM.findDOMNode(this)
});
}
componentWillUnmount() {
// console.log('componentWillUnmount this.webdatarocks', this.webdatarocks.dispose);
if (this.webdatarocks.dispose) {
// this code break the system
// this.webdatarocks.dispose();
}
}
shouldComponentUpdate() {
return false;
}
render() {`enter code here`
return <div>Pivot</div>;
}
}
export default Pivot;
And this is my response.
Some one can help me with this issue?
Thanks and I appreciate any help.
This error means that WebDataRocks cannot find the DOM element where it should be initialized. I have tried the following GitHub sample: https://github.com/WebDataRocks/pivot-react/tree/master/typescript and it worked fine for me. It seems that your code sample is also based on it.
My recommendation for you is the following: start with dividing your business logic from the WebDataRocks component code. Here is the code of the proxy between vanilla WebDataRocks and your React application: https://github.com/WebDataRocks/pivot-react/blob/master/typescript/src/webdatarocks/webdatarocks.react.tsx. You can use it as a basic component for creating custom WebDataRocks visualizations. Therefore, there are not so many cases when it should be modified.
And here is the sample of the client code where you can initialize WebDataRocks reports and other configs: https://github.com/WebDataRocks/pivot-react/blob/master/typescript/src/App.tsx. 

Gatsby i18next plugin No codeFrame could be generated

I'm developing a website with Gatsby and I wanted to implement multi-language support.
So I used the gatsby-plugin-react-i18next plugin.
I followed all the steps, but it doesn't work, once I log into my website this error message shows:
error message
Right now, my code is the next one.
gatsby-config.js
module.exports = {
siteMetadata: {
title: "Space",
},
plugins: [
"gatsby-plugin-postcss",
{
resolve: `gatsby-source-filesystem`,
options: {
name: `locale`,
path: `${__dirname}/locales`
}
},
{
resolve: `gatsby-plugin-react-i18next`,
options: {
localeJsonSourceName: `locale`, // name given to `gatsby-source-filesystem` plugin.
languages: [`en`, `es`],
defaultLanguage: `en`,
// if you are using Helmet, you must include siteUrl, and make sure you add http:https
siteUrl: `https://example.com/`,
// you can pass any i18next options
// pass following options to allow message content as a key
},
pages: [
{
matchPath: '/:lang?/blog/:uid',
getLanguageFromPath: true,
excludeLanguages: ['es']
},
{
matchPath: '/preview',
languages: ['en']
}
]
}
],
};
index.js
import * as React from "react"
import { graphql } from "gatsby"
import { useTranslation } from "gatsby-plugin-react-i18next"
export default function IndexPage() {
const { t } = useTranslation();
return (
<h1>{t("Space")}</h1>
)
}
export const query = graphql`
query($language: String!) {
locales: allLocale(filter: {language: {eq: $language}}) {
edges {
node {
ns
data
language
}
}
}
}
`;
And of course I have the translation folders project structure
Im trying this plugin on a new blank project, not on my main project, so I don't understand why the plugin fails.
Any thoughts? Thanks in advice!
Edit: I add the translation.json of the two languages
English
Spanish
Your JSONs looks and the implementation too (couldn't be wrong being that simple). So to me, the issue relies on the configuration. Try something simpler such as:
{
resolve: `gatsby-plugin-react-i18next`,
options: {
localeJsonSourceName: `locale`,
path: `${__dirname}/locales`,
languages: [`en`, `es`],
defaultLanguage: `en`,
i18nextOptions: {
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
keySeparator: false,
nsSeparator: false,
},
},
},

Can I use a state value to reference a value inside of a lookup table that is stored locally in my project?

I have an API response that contains an item hash. I want to be able to cross reference that item hash with a lookup table that is stored locally in my project, so I am able to display a name and icon path of the item that matches the hash.
I have tried storing the item hash in state and tried to reference it in my render function like so const weaponURL = hashdata[{weapon}.displayProperties.icon];. I'm getting an error saying the Cannot read property 'icon' of undefined.
I have also tried to reference the icon path using this code in render {baseURL + {weapon && hashdata[weapon].displayProperties.icon}} but I get an error from my IDE to say that : is expected
Here is my entire JS file:
import React, {Component} from 'react';
import hashdata from '../data/data';
import ReactTooltip from 'react-tooltip';
import apiKey from '../data/apiKey';
class XUR extends Component {
state = {
isLoading: false,
error: null,
xurInventory: null,
weapon: null,
gear1: null,
gear2: null,
gear3: null
};
async componentDidMount() {
this.setState({
isLoading: true
});
try {
const xurFetch = await fetch('https://www.bungie.net/Platform/Destiny2/Vendors/?components=402', {
headers: {
'X-API-KEY': apiKey
}
});
if (!xurFetch.ok) {
throw Error(xurFetch.statusText);
}
const xurInvJson = await xurFetch.json();
this.setState({
xurInventory: xurInvJson,
weapon: xurInvJson.Response.sales.items.data[2190858386].saleItems[54].itemHash,
isLoading: false
});
} catch (error) {
this.setState({error: true});
}
}
render() {
const {isLoading, error, weapon} = this.state;
const baseURL = 'https://www.bungie.net';
const weaponURL = hashdata[{weapon}.displayProperties.icon];
if (isLoading) {
return (
<div>
<p>LOADING</p>
</div>
)
}
if (error) {
return(
<div>
<p>Error</p>
</div>
)
}
return (
<div>
<div className={"row justify-content-center"}>
<div className={"col-6 text-center py-2"}>
<img alt="icon" src={baseURL + {weapon && hashdata[weapon].displayProperties.icon}}/>
<ReactTooltip/>
</div>
</div>
</div>
)
}
}
export default XUR;
Here is a snippet from my data hash file:
const hashdata = {
"31953744": {
"displayProperties": {
"description": "",
"name": "Holy Ground",
"icon": "/common/destiny2_content/icons/d76ab9a89d00451c6a0c1d779a3e5f98.jpg",
"hasIcon": true
},
"scope": 1,
"sourceString": "Source: Complete activities and earn rank-up packages on Io.",
"sourceHash": 315474873,
"itemHash": 31953744,
"acquisitionInfo": {
"runOnlyAcquisitionRewardSite": false
},
"stateInfo": {
"requirements": {
"entitlementUnavailableMessage": ""
},
},
"presentationInfo": {
"presentationNodeType": 1,
"parentPresentationNodeHashes": [
631010939
],
"displayStyle": 3
},
"hash": 259147463,
"index": 2171,
"redacted": false,
"blacklisted": false
},
"31953747": {
"displayProperties": {
"description": "",
"name": "Ballet Lover",
"icon": "/common/destiny2_content/icons/c89eb559068c19f8ed62d56a47f33cfa.jpg",
"hasIcon": true
},
"scope": 1,
"sourceString": "Source: Complete activities and earn rank-up packages on Io.",
"sourceHash": 315474873,
"itemHash": 31953747,
"acquisitionInfo": {
"runOnlyAcquisitionRewardSite": false
},
"stateInfo": {
"requirements": {
"entitlementUnavailableMessage": ""
},
},
"presentationInfo": {
"presentationNodeType": 1,
"parentPresentationNodeHashes": [
631010939
],
"displayStyle": 3
},
"hash": 259147462,
"index": 2170,
"redacted": false,
"blacklisted": false
},
"32806262": {
"displayProperties": {
"description": "\"Our mysterious defender slew a Kell today. Kandak has banned other Risen and put a bounty on the so-called Red Moon Phantom's head.\" —Annals of the Saharan Contested Zone",
"name": "Cloak of Five Full Moons",
"icon": "/common/destiny2_content/icons/8e0e7dfa87d5c68262b6027cd22efd40.jpg",
"hasIcon": true
},
"scope": 1,
"sourceString": "Source: Open Legendary engrams and earn faction rank-up packages.",
"sourceHash": 3334812276,
"itemHash": 32806262,
"acquisitionInfo": {
"runOnlyAcquisitionRewardSite": false
},
"stateInfo": {
"requirements": {
"entitlementUnavailableMessage": "Requires Destiny 2: Forsaken"
},
},
"presentationInfo": {
"presentationNodeType": 2,
"parentPresentationNodeHashes": [
3988275539
],
"displayStyle": 3
},
"hash": 39866533,
"index": 510,
"redacted": false,
"blacklisted": false
}
}
Is anyone able to tell me where I'm going wrong? Is it possible to reference the hash table this way or will I have to store the state hash value in a variable and then reference that variable when making my call to the hash table?
I am using ReactJS.
EDIT:
I'm trying to call the icon path and place it behind a base URL in the renders img tag, so I can show the icon of specific items. I have managed to get this working using localStorage and storing the value of the hash but I was wondering if it's possible without using localStorage as the code was incredibly messy and it seemed like a long winded way of doing things, especially when I was having to create localStorage items for many items at a time.

The vue.resource() method and the api.json file

I'm new to vue.js and I'm confused about what to do next in the program. I want to add a new view page to collect customer information (see code below). I don't know how to write the function after I click the "confirm" button. I think I should go the DataService.js file to get the data or update the rules.
Here is the url for the project:
https://coding.net/u/benbenshi/p/vue-admin-test/git
// baseConfig.vue
<template>
<div class="system-base">
<el-input type="text">
<template slot="prepend">userInput:</template>
</el-input>
</div>
<div>
<el-button #click="somefunction">confirm</el-button>
</div>
</template>
<script>
import dataservice from '../../services/DataService'
export default {
components: {},
data () {
return {}
},
computed: {},
methods: {
// what should I do here? Should I write some function to handle the confirm event?
},
}
</script>
Right now I've got an api.json file with a structure like this: should I write the function in the dataServices.js file based on the API offered in the api.json file?
//api.json
{
"id": "",
"name": "api",
"description": "",
"order": [],
"folders": [],
"timestamp": 1234567,
"owner": "1234567",
"public": false,
"requests": [
// a lot of request api here.
]
}
Any help will be appreciated!
You can take a reference from the vue-hackernews-2.0 code, You can do whatever action you want to do, and than call this.$router.replace("/your/path") to redirect to desired page. Here is the stripped code from the repo:
methods: {
loadItems (to = this.page, from = -1) {
this.loading = true
this.$store.dispatch('FETCH_LIST_DATA', {
type: this.type
}).then(() => {
this.$router.replace(`/${this.type}/1`)
return
this.loading = false
})
}

Categories

Resources