I am creating pages from a csv file in Gatsby JS. So far so good. When trying to output the data onto those generated pages, something always is undefined and I just can't figure out how to get it to work.
With markdown or other sources I get it to work just fine but somehow I am stuck here. I tried modifying all kinds of other blog templates to get my data in but to no avail :/
This is what I got in my template:
import React from "react"
class ProductTemplate extends React.Component {
render() {
const data = this.props.productsCsv
return (
<div>
<h1>{data.name}</h1>
</div>
)
}
}
export default ProductTemplate
export const SingleProductQuery = graphql`
query ProductByPath($slug: String!) {
productsCsv(slug: {eq: $slug}) {
name
price
image
}
}
`;
any ideas or pointers would be much appreciated :)
Seems like I forgot a "data" in the render function. Working fine now. Just in case anybody has the same issue, here the code I got it working with:
import React from 'react';
class ProductTemplate extends React.Component {
render() {
const post = this.props.data.productsCsv
return (
<div>
<h1>{post.name}</h1>
<img src={post.image} />
</div>
);
}
};
export default ProductTemplate;
export const singleQuery = graphql`
query ProductBySlug($slug: String!) {
productsCsv( slug: { eq: $slug }) {
name
price
image
}
}
`;
Related
I am trying to figure out, why my HTML is not rendering for conditional routes, actually, I have three components, one is parent index.js inside slug directory and two are child components country.js, profile.js inside components directory.
I want to keep same URL pattern for country and profile component
domain.com/[countryCode] or domain.com/[profileUsername]
Please have a look at the code below.
index.js
import React from 'react'
import { withRouter } from 'next/router'
import Country from '../../components/Country'
import Details from '../../components/Details'
class Type extends React.Component {
constructor(props){
super(props)
this.state = {
isCountry: ''
}
}
checkCountry = (type) => {
let isCountry = ''
if(type){
const countries = JSON.parse(localStorage.getItem('countries'))
let countryCodes = []
isCountry = countries.map(item => item.country.toLowerCase()).includes(type)
this.setState({isCountry:isCountry})
}
}
componentDidUpdate(prevProps){
if(this.props.router.query.slug !== prevProps.router.query.slug){
this.checkCountry(this.props.router.query.slug)
}
}
componentDidMount(){
this.checkCountry(this.props.router.query.slug)
}
render() {
const {isCountry} = this.state
return (
<React.Fragment>
{isCountry ? (
<Country/>
) : (
<Details/>
)}
</React.Fragment>
)
}
}
the above code is working fine as expected, but the problem is, It is not returning HTML into the view source code. Please suggest how can I sort out the above issue
You should use getStaticProps to provide next.js information about your request to API so next.js could cache your API responses (or similar functions, find what you need in the documentation: https://nextjs.org/docs/advanced-features/automatic-static-optimization).
Could be useful: https://nextjs.org/docs/advanced-features/static-html-export.
I'm testing out the gatsby-source-wordpress plugin from Gatsby, and I'm trying to get my menu to fetch the links from my Wordpress site.
I've managed to get all of the Wordpress schemas to come up in GraphiQL, and copied the GQL code from there.
I've made a Layout component, where I've tried most of what I can to make it work. However, I keep getting TypeError: Cannot read property 'props' of undefined when I deploy my Gatsby site to localhost.
Anyone ever come across this before? I'm kinda new to GraphQL and all that, so I honestly have no clue what's wrong, or if it's even GraphQL that's causing this. Any help is greatly appreciated! (Sorry if this is a really simple fix, and I'm just being ignorant)
I've searched through Google, Stack Overflow, Youtube, and anywhere else I could think of.
Code
Layout
import React, { Component } from "react";
import PropTypes from "prop-types";
import { StaticQuery, graphql } from "gatsby";
// import Image from "gatsby"
import Header from "../Header";
import MainMenu from "../Nav";
// import Aside from "../Aside"
// import Footer from "../Footer";
const Layout = ({ children }) => {
const menu = this.props.data.allWordpressWpApiMenusMenusItems;
return (
<StaticQuery
query={graphql`
query menuQuery {
allWordpressWpApiMenusMenusItems {
edges {
node {
id
name
items {
title
object_slug
url
}
}
}
}
}
`}
render={menu => (
<>
<Header>
<MainMenu menu="{data.allWordpressWpApiMenusMenusItems}" />
</Header>
<div className="Layout">
<main>{children}</main>
</div>
<footer>Footer</footer>
</>
)}
/>
);
};
Layout.propTypes = {
children: PropTypes.node.isRequired
};
export default Layout;
Menu component
import React, { Component } from "react";
// import { Link } from "gatsby";
class MainMenu extends Component {
render() {
const data = this.props.data.allWordpressWpApiMenusMenusItems;
console.log(data);
return (
<nav className="main-menu">
<ul>
<p>Test</p>
</ul>
</nav>
);
}
}
export default MainMenu;
Expected result: The site renders on localhost:8000
Error messages I recieve:
src/components/Layout/index.js:11
8 | // import Aside from "../Aside"
9 | // import Footer from "../Footer";
10 |
> 11 | const Layout = ({ children }) => {
12 | const menu = this.props.data.allWordpressWpApiMenusMenusItems;
13 | return (
14 | <StaticQuery
You're accessing this.props in a function based component. Change your declaration to
const Layout = ({ children, data }) => {
const menu = data.allWordpressWpApiMenusMenusItems;
Or explicitly declare props and destructure the properties you want
const Layout = props => {
const { data, children, ...rest } = props
const menu = data.allWordpressWpApiMenusMenusItems;
I am trying to useStatic Query and GraphQL to get a simple title from
Contentful, pass it to state and then show in the render. I cant make it work. I am attaching an image showing my current setup and errors.
Possible problems: 1. the query returns an array, and I need to change it into a string, or access 0 element, the first one, because my content type is just one, as it is a onepage.
Placing of the query in the component, I am not sure if it can be in the constructor of an component
For comparison: in the screen from my file you can see a variable name showing Josh Perez, when I uncomment it and add it to this.state = { dataTest: name}, then in RENDER: this.state.dataTest returns the name Josh Perez well, so passing a variable to state works, but passing a string from graphql query is not possible for me...
I have a limitation which is that I need to create my page component with a class, because of the fact that in the Component did mount I am placing some JQuery, which works well for me.
THIS IS MY TEST CODE
1. In Constructor
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
const name = 'Josh Perez';
this.state = { dataTest: name };
}
In render
{this.state.dataTest}
This works, the variable name is passed to state and shown in render.
However, I want to show in this way a simple text string from Contentful. So I am trying code like this (error message is shown in the screens):
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
//const name = 'Josh Perez';
const data = useStaticQuery(graphql`
query {
allContentfulHomepage (limit: 1) {
edges {
node {
section1Title
}
}
}
}
`)
this.state = { dataTest: data };
It turns out, that the below suggested solution works. I am putting below
my attempt at callingfurther content. It does not work. It displays the following error "Cannot read property 'map' of undefined". I would be very grateful for a suggestion how to improve it, how to make it work.
export default class Test extends Component {
state = {
dataTest: this.props.data.test.edges.map(({ node: test }) =>
test.section1Title),
dataTest2: this.props.data.test.edges.map(({ node: test }) =>
test.section2Lead),
dataTest3: this.props.data.test.edges.map(({ node: test }) =>
test.section1Text.json)
}
render() {
return <div>
<h1>{this.state.dataTest}</h1>
<h1>{this.state.dataTest2}</h1>
{documentToReactComponents(this.state.dataTest3)}
</div>
}
}
export const query = graphql`
{
test:allContentfulHomepage(limit: 1) {
edges {
node {
section1Title
section2Lead
section1Text {
json
}
}
}
}
}
`
If you're writing a page component as a class, you don't need to use the UseStaticQuery, you can use the simple PageQuery for this purpose.
To loop through arrays, the map() method works as well.
UPDATE
import React, { Component } from 'react';
import { graphql } from 'gatsby';
export default class Test extends Component {
render() {
const { edges } = this.props.data.test;
return (
<div>
{edges.map(({ node: itemFromContentful }) => (
<h1>{itemFromContentful.section1Title}</h1>
<h1>{itemFromContentful.section2Lead}</h1>
{documentToReactComponents(section1Text.json)}
))}
</div>
);
}
}
export const query = graphql`
{
test:allContentfulHomePage(limit: 1) {
edges {
node {
section1Title
}
}
}
}
`
Whats happening:
The GraphQL query you're using is bringing the data you want from the Contentful;
The React Stateful Component (class Test) is receiving all the data available from the query as a prop;
We're accessing this data on the render() method using the destructing assignment;
we're accessing the data nodes through the map method (the one I suggested you to take a look;
The curly braces into the JSX allows you to use JS to manipulate what you want - In this case, to render the information.
I am trying to output some svgs and output them from a list, here is my render method:
render() {
const renderTag = () => {
const Tag = this.props.id
return(<Tag />)
}
return (
<div key={this.props.name} className="social-box">
<a className={this.props.id + "-link"}>
{renderTag()}
</a>
</div>
)
}
However, the DOM node is always lowercase i.e. <facebook> rather than <Facebook> this.props.id is correctly rendered to the console as Facebook. Can anyone tell me why react or the browser incorrectly renders as lowercase, and therefore not the component, and how to fix?
It's a technical implementation of React, all tags get lowercased on this line here, AFAIK it's not possible to render non-lowercased tags and that is by design.
Read more here.
i suggest that you would take a look at this article about dynamic components.
The most relevant example from the article:
import React, { Component } from 'react';
import FooComponent from './foo-component';
import BarComponent from './bar-component';
class MyComponent extends Component {
components = {
foo: FooComponent,
bar: BarComponent
};
render() {
const TagName = this.components[this.props.tag || 'foo'];
return <TagName />
}
}
export default MyComponent;
you most likely have a limited amount of components that could be rendered, so you might create a dictionary that contain a key (name of the component) to the component itself (as shown in the example) and just use it that way:
import Facebook from './FaceBook';
import Twitter from './Twitter';
const components = {
facebook: Facebook,
twitter: Twitter
};
render() {
return <div key={this.props.name} className="social-box">
<a className={this.props.id + "-link"}>
<components[this.props.id] />
</a>
</div>;
}
I find the answer eventually. #TomMendelson almost had the answer, but it needed fleshing out a bit more.
A function to create the component outside of the render method, suggested by #ShubhamKhatri actually did the job. Here's the final code:
import React from 'react';
import Facebook from './svg/Facebook';
import LinkedIn from './svg/LinkedIn';
import Twitter from './svg/Twitter';
import Pinterest from './svg/Pinterest';
class SocialMediaBox extends React.Component {
renderElement(item) {
const Components = {
'Facebook': Facebook,
'Twitter': Twitter,
'Pinterest': Pinterest,
'LinkedIn': LinkedIn
}
return React.createElement(Components[item], item);
}
render() {
const Element = this.renderElement(this.props.id)
return
(
<div>
{Element}
</div>
)
}
}
export default SocialMediaBox;
Thanks for the question and answers; alongside the answers given in Dynamic tag name in jsx and React they helped me to find a solution in my context (making a functional component in Gatsby with gatsby-plugin-react-svg installed):
import React from "react"
import FirstIcon from "../svgs/first-icon.inline.svg"
import SecondIcon from "../svgs/second-icon.inline.svg"
import ThirdIcon from "../svgs/third-icon.inline.svg"
const MyComponent = () => {
const sections = [
{ heading: "First Section", icon: () => <FirstIcon /> },
{ heading: "Second Section", icon: () => <SecondIcon /> },
{ heading: "Third Section", icon: () => <ThirdIcon /> },
]
return (
<>
{sections.map((item, index) => {
const Icon = item.icon
return (
<section key={index}>
<Icon />
<h2>{item.heading}</h2>
</section>
)
})}
</>
)
}
export default MyComponent
As mine is a Gatsby project I used the above mentioned plugin, but it itself process svgs with svg-react-loader so the basic principle should work in any React project using this package.
I'm working on converting a Flux app to Relay.js and I'm running into some issues. I can't seem to get component fragment composition to work properly. The correct data comes back from the server, but the composed data is never passed back into the props of the parent component for some reason.
here's my code so far:
LibraryLongDescription.js
import React, {Component, PropTypes} from 'react';
import Relay from 'react-relay';
import DocumentTitle from 'react-document-title';
import Address from '../components/Address';
import Orders from '../pages/Orders';
export default class LibraryLongDescription extends Component {
constructor(props)
{
super(props);
this.state = {
library: {}
};
console.log(props);
if(props.library){
this.state.library = props.library;
}
}
render()
{
return (
<DocumentTitle title="Libraries">
<div>
{this.state.library.name}
<div className="row">
<Address type={"Address"} address={this.state.library.address} />
</div>
<div className="top-space-60">
<h3>Orders</h3>
<Orders />
</div>
</div>
</DocumentTitle>
);
}
}
export default Relay.createContainer(LibraryLongDescription, {
fragments: {
library: () => Relay.QL`fragment on Library {
id,
name,
address{
id
sanNumber,
addressLine1,
addressLine2,
city,
state,
zip
},
${Orders.getFragment('orders')}
}`,
}
});
Orders.js
import React, {Component, PropTypes} from 'react';
import Relay from 'react-relay';
class Orders extends Component {
constructor(props)
{
super(props);
console.log(props);
}
render()
{
return (<h1>This is where the order goes</h1>);
}
}
export default Relay.createContainer(Orders, {
fragments: {
orders: () => Relay.QL`fragment on Library {
orders(first: 10){
edges{
node{
id,
message_number,
order_total
}
}
pageInfo{
hasPreviousPage,
hasNextPage
}
}
}`
}
});
This does not resolve correctly. When I console log props in LibraryLongDescription.js I get all the values from that query, but I don't get anything from the Orders fragment. When I look to see what came over the network I get data in this form:
{
"data":{
"library":{
"id":"valid",
"name":"valid",
"address":{
correct data
},
"_orders1mpmPX":{
"edges":[
{
"node":{
correct data
},
"cursor":"correct data"
},
],
"pageInfo":{
correct data
}
}
}
}
}
but when I console log props from library Long description I don't see anything for orders. I also get this property: __fragment__ which seems to not really have anything useful on it. Any help with this would be greatly appreciated. I've been searching the internet for solutions for hours. If there's any info I did not provide that would be of use let me know.
After spending a stupid amount of time trying to solve this issue I have discovered relay does not like you defining a type field in a fragment query. here's what I mean... the library query changed to this:
export default Relay.createContainer(LibraryLongDescription, {
fragments: {
library: () => Relay.QL`
fragment on Library {
id,
name,
address{
id
sanNumber,
addressLine1,
addressLine2,
city,
state,
zip
},
orders(first: 500){
${Orders.getFragment('orders')}
}
}
`,
}
});
and the orders query changed to this:
export default Relay.createContainer(Orders, {
fragments: {
orders: () => Relay.QL`fragment on OrderConnection {
edges{
node{
id
purchaseDate
}
}
pageInfo{
hasPreviousPage
hasNextPage
}
}`
},
});
if you don't have some sort of root field like orders defined on the parent, relay won't know how to resolve that field back to the parent to be passed back into your child component. by doing this: orders(first: 500) you are declaring that name as a dependency for relay to pass it into that component. Without that name relay does not see your component requiring that name and it won't pass it. I hope this helps someone else out someday. I spent more than a full day trying to figure this one out.