Gatsby GraphQL Variables In Component - reactjs

I'm an iOS developer and I've been struggling for what seems like the longest time making my portfolio site from scratch. I've tried a bunch of different technologies and have finally settled on using Gatsby to create it.
So far things have been fairly straightforward but I can not figure out for the life of me how to get a component that looks like the picture below. I've gotten most of the layout design working, but I can't seem to use graphql to query the images I need in the component.
Desired Layout
I've found plenty of Gatsby example templates such as this one and this one that are similar. However the main difference is that each of these only have one image and they seem to be using Gatsby 2.0 instead of 3.0.
I can get one image using "useStaticQuery", however I need access to different images for each component. From my understanding this is not possible to do within a component, only on a page. I also can not pass the image path as a variable to StaticImage either.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} />
</div>
);
Result
Can anyone please explain to me how I can get the desired layout in a component?
Edit
Here is some relevant code of what I am doing.
This is my index.js home page.
export default function IndexPage({ data }) {
const projects = data.apps.edges;
return (
<Layout>
<SEO title="Home" />
<HeroSection />
<DescriptionSection />
<div>
{projects.map(({ node: project }) => (
<AppSection app={project} />
))}
</div>
<FooterSection />
</Layout>
);
}
//export page query
export const query = graphql`
query Apps {
apps: allAppsJson(sort: { order: ASC, fields: order }) {
edges {
node {
appLink
title
tagline
moreLink
order
icon
}
}
}
}
`;
Here is the component.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} alt={app.title} />
</div>
);
}

You have a few options:
Query for all of your image data in your page query and prop-drill the data to the component that uses it to display the image.
Using Gatsby v3+, hardcode the image references for each component using the new StaticImage component.
If you have a single component used multiple times with different content/images, but a static parent component with your content, you can leverage option #2 above but pass the image component down as a prop or children.

Related

How to export a component to a PDF file, that is not visible on UI, but has to be in PDF document (html-to-image, jsPDF, React)

Like the title says, I want to export a component to a PDF file, that I want to be invisible in the app or should I say on UI, but I want it to be inside a PDF document.
To make this PDF exporting functionality I have used the combination of html-to-image library, jsPDF library and everything is made using React.
This is my code:
function App() {
const [exporting, setExporting] = useState(false);
async function createPdf({ doc, element }) {
const imgData = await toPng(element);
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, "PNG", 10, 0, pdfWidth, pdfHeight, "", "FAST");
}
async function handleDownloadPdf() {
const element = document.getElementsByClassName("container")[0];
const doc = new jsPDF(
"p",
"px",
[element.clientWidth, element.clientHeight],
true
);
setExporting(true);
await createPdf({ doc, element });
doc.save(`charts.pdf`);
}
return (
<pdfContext.Provider value={{ exporting, setExporting }}>
<div className="App">
<button onClick={handleDownloadPdf}>Test</button>
<div className="container">
<Hidden />
<Foo />
</div>
</div>
</pdfContext.Provider>
);
}
export default App;
The component that I want to be hidden is <Hidden />, this is a simple component but let me show the code anyways:
const Hidden = () => {
const { exporting, setExporting } = useContext(pdfContext);
return (
<div
className="elementOne"
style={{ visibility: exporting ? "visible" : "hidden" }}
>
</div>
);
};
export default Hidden;
As you can see I want to use the context called pdfContext that sets the visibility of a component to hidden when the component is not being exported, and to visible when it's being exported, but this way is not really a good solution, as the component gets visible for a split second before exporting and in my opinion it's not a good design.
So if anyone has any solution or a workaround on how to export a component to a PDF using these libraries, but without showing it on a UI, that would be great.
I know that the way these components are being exported to a PDF is by converting the container to an image, and probably the way I am asking to do this is maybe impossible but then again it does not hurt to ask.

Elasticsearch search-ui with React

I have a React site with aside and main content. I want to use search-ui for searching on the site.
The search bar should be on the aside, and when the user searches for something, results should be displayed on the main content. Aside and main content are two separated react components.
In my aside, I'm configuring search-ui SearchBox like this
<SearchBox
autocompleteResults={{
titleField: "title",
urlField: "url"
}}
autocompleteSuggestions={true}
onSubmit={searchTerm => {
navigate("/elastic-search?q=" + searchTerm);
}}
onSelectAutocomplete={(selection, {}, defaultOnSelectAutocomplete) => {
if (selection.suggestion) {
navigate("/elastic-search?q=" + selection.suggestion);
} else {
defaultOnSelectAutocomplete(selection);
}
}}
/>
So when the user searches something the app will redirect to a separate page named elastic-search and I'm passing the searchTerm in the URL through navigate method.
On MainContent I have results like this:
<Results titleField='title' urlField='url'/>
Now the question is how can I fetch searchTerm and display the results on main content. The structure of the app is like this:
<App>
<SearchProvider config={config}>
<Aside /> ---- Here I have <SearchBox>
<MainContent /> ---- Here I have <Results>
</SearchProvider>
</App>
When I search the app redirects to /elastic-search with searchTerm in URL, but the results are not displaying. If I refresh the page they are displayed. How can I notify Results or re-render the page, so I can show the searched results.
Your Results seems to be missing some parameters and should look something like this:
<>
<Results
titleField="title"
urlField=""
view={SearchView}
resultView={SearchResultView}
/>
</>
And your SearchView (Used to override the default view for this Component.) and SearchResultView (Used to override individual Result views.) components, should look something like this:
const SearchView = ({ children }) => {
return <div>{children}</div>
};
const SearchResultView = ({ result: searchResult }) => {
return <div>{searchResult.content}</div>
}
Additional suggestion
This is a working example in the Next.js app with import { useRouter } from "next/router"; that needs to be replaced with your routing solution. In the SearchBox component:
export const SearchBoxComponent = () => {
const router = useRouter();
return (
<>
<SearchBox
searchAsYouType={true}
autocompleteResults={{
titleField: "title",
urlField: "",
shouldTrackClickThrough: true,
clickThroughTags: ["test"],
}}
autocompleteSuggestions={true}
onSubmit={(searchTerm) => {
const urlEncodedQuery = encodeURI(searchTerm).replace(/%20/g, "+");
router.push(`/search?q=${urlEncodedQuery}`);
}}
...
</>
)
}

gatsby-source-medium thumbnail image not showing

I'm using gatsby in my react project, to show my medium, articles inside the project.
below is my graphql query for that.
const BlogPost = () => {
const blogMediumQueryData = useStaticQuery(graphql`
query Medium {
allMediumPost(sort: { fields: [createdAt], order: DESC }) {
edges {
node {
id
title
uniqueSlug
createdAt(formatString: "MMM YYYY")
virtuals {
previewImage {
imageId
}
}
author {
name
}
}
}
}
}
`)
const blogs = blogMediumQueryData.allMediumPost.edges
return (
<Blog
image={blog.node.virtuals.previewImage.imageId}
title={blog.node.title}
date={blog.node.createdAt}
author={blog.node.author.name}
path={blog.node.uniqueSlug}
/>
)
this gives me the preview image ID. And I'm passing it to the child component as a prop. But when I try to show the image with the Img component from gatsby, the Image is not showing.
Here is my code for the child component
import React from "react"
import { Link } from "gatsby"
import { slugify } from "../utils/utilityFunctions"
import Image from "../elements/image"
const Blog = ({ image }) => {
return (
<div className="content-block">
<div className="post-thubnail">
{image && (
<Link to={postUrl} target='blank'>
<Image src={image} alt={title} />
</Link>
)}
</div>
)
}
export default Blog
Here is the code for the Image component
import React from "react";
import Img from "gatsby-image";
const NonStretchedImage = props => {
let normalizedProps = props
normalizedProps = {...normalizedProps.fluid, aspectRatio: 1}
let alignment;
if(props.align === 'right'){
alignment = '0 0 0 auto'
} else if(props.align === 'left'){
alignment = '0 auto 0 0'
}else{
alignment = '0 auto'
}
if (props.fluid && props.fluid.presentationWidth) {
normalizedProps = {
...props,
style: {
...(props.style || {}),
maxWidth: props.fluid.presentationWidth,
margin: alignment,
},
}
}
return <Img {...normalizedProps} />
}
export default NonStretchedImage;
This is my first project with gatsby and graphql. Is there are anything that I have missed or is there anything that I'm doing wrong?
Thanks in advance
A few caveats that I guess will put you on the track to fix the issue.
node, in the GraphQL query is an array, in the same way, I guess that virtuals it is. Check and test the response in the localhost:8000/___graphql playground.
So assuming that your query works as expected, your code should look like:
const BlogPost = () => {
const blogMediumQueryData = useStaticQuery(graphql`
query Medium {
allMediumPost(sort: { fields: [createdAt], order: DESC }) {
edges {
node {
id
title
uniqueSlug
createdAt(formatString: "MMM YYYY")
virtuals {
previewImage {
imageId
}
}
author {
name
}
}
}
}
}
`)
const blogs = blogMediumQueryData.allMediumPost.edges
return (
<Blog
image={blog.node[0].virtuals.previewImage.imageId}
title={blog.node[0].title}
date={blog.node[0].createdAt}
author={blog.node[0].author.name}
path={blog.node[0].uniqueSlug}
/>
)
Alternatively, you can loop through the array of nodes and use your previous Blog component since it will get each iterable variable.
I don't think your Image component be able to render a gatsby-image only using the imageId. Gatsby needs a bunch of data (given by its transformers and sharps) to render the image, not using an identifier but series of fields (that's why it usually renders query fragments, noted by ...). Your image component, in the end, should render something like:
<img src={`https://medium.com/${blog.node[0].virtuals.previewImage.imageId}`}
Based on: https://blog.devgenius.io/how-to-scrap-your-medium-articles-with-gatsby-js-f35535ebc09d
So summarizing, gatsby-source-medium by itself doesn't provide enough data to use gatsby-image or gatsby-image-plugin plugins so I'm afraid you won't be able to use the Img component. You have to use the standard img tag.

Markdown in Antd

I can confirm that this has neither been asked nor been addressed anywhere. I am currently working on a website using Gatsby, Strapi and Antd for the design. I am using the the rich text editor for one of the content types where I have put all my markdown content. However, when I try to display the actual content on the webpage, the styling is completly nuked. I figured this was because the content uses normal HTML elements like <h1> and <p> instead of the antd components like <Title> or <Text>.
So I did some researches and found that Antd has a markdown.less in their source, which I figure is used to style the markdown in their documentation. I haven't found the same after scouring the source code inside the node modules folder. Does this mean that Antd does not support styling for markdown or am I missing something here?
Btw I am using the react-markdown library to display the all the markdown. I have also posted all the relevant code below.
template.tsx
const ProductTemplate: React.FC<Props> = ({ data }: Props) => {
const {
...
} = data
const {
product: { strapiId: selectedKeyProp },
} = data
return (
<Layout>
<AntLayout>
<ProductSidebar
selectedKeyProp={selectedKeyProp}
productsInfo={productsInfo}
>
<ProductInfo product={product} />
</ProductSidebar>
</AntLayout>
</Layout>
)
}
export const query = graphql`
...
`
export default ProductTemplate
page-component.tsx
const ComponentName = ({ data }) => {
const {
...
} = data
console.log(data)
return (
<Layout>
<AntLayout>
<ProductSidebar productsInfo={productsInfo}>
<div style={{ display: "unset", padding: "15px 35px" }}>
<ReactMarkdown className="markdown" children={content} />
</div>
</ProductSidebar>
</AntLayout>
</Layout>
)
}
export const query = graphql`
...
`
export default ComponentName
There problem can be pointed out here I guess. The <ReactMarkdown> receives the markdown content in the children props. But once the content is displayed to the page, the styling, as I mentioned above, is nuked out.
I raised this issue on github here. According to the devs, there is currently no markdown support for antd. As I thought, the markdown.less file linked above was just for the markdown in their documentation.
One way to get around this solution is to include the tags exactly as specified by antd component elements. For example, instead of a # or <h1> for a header, we can use <h1 class="antd typography">, though this definitely is painful and prone to error.
This other solution would be to use MDX, which support jsx inside markdown.
About a month late, but I just ran into this: I needed a way to change the styling of html generated by the react-markdown React component. I am using react-markdown in a NextJS app, and using Antd as my component library. I am additionally using react-syntax-highlighter and react-syntax-highlighter-prismjs for handling lighting codeblocks
While there is no support in Antd for markdown, there is support for custom components in react-markdown! react-markdown allows you to override the rendering engine and replace the individual components with your own, so I went through and replaced a bunch of them with Antd components:
Fair warning: This was my first pass to make sure this worked, it doesn't for instance handle checkboxes inside list item inputs.
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import {coy} from "react-syntax-highlighter/dist/cjs/styles/prism/prism";
import gfm from 'remark-gfm';
import { Typography } from 'antd';
const { Title, Text } = Typography;
import { List } from 'antd';
const mymarkdowndata = '
# A heading
Some text
## A second heading
* List!
* Has
* Many
* Items!
'
const renderers = {
heading: function Heading(props) {
return <Title level={props.level}>{props.children}</Title>;
},
list: function MakeList(props) {
return <List bordered>{props.children}</List>
},
listItem: function MakeListItem(props) {
return <List.Item>{props.children}</List.Item>
},
inlineCode: function makeInlineCode(props) {
return <Text code>{props.children}</Text>
},
code: function makeCodeBlock(props) {
return <SyntaxHighlighter language={props.language} style={coy}>{props.value}</SyntaxHighlighter>
},
blockquote: function makeBlockQuote(props) {
return <Text type="secondary">{props.children}</Text>
}
};
Then inside your component rendering function:
render() {
return <ReactMarkdown renderers={renderers} plugins={[gfm]} children={mymarkdowndata} />
}

Refetch container refetches, but does not update the view

My application looks something like what's included in the snippets below.
(I've left out a lot of the implementation details in the hopes that the code below is sufficient to get my question across).
SampleApp.js
const SampleAppQuery = graphql`
list SampleAppQuery {
...ItemComponent_item
}
`
function SampleApp() {
return (
<QueryRenderer
environment={environment}
query={SampleAppQuery}
render={(error, props) => {
if (props) {
return <AppRefetchContainer list={props} />
}
}}
/>
)
}
AppRefetchContainer.js
class AppRefetchContainer extends Component {
render() {
<div>
<HeaderComponent refetchList={this._refetchList} />
{list.map(item => <ItemComponent item={item} />)}
</div>
}
_refetchList(params) {
this.props.relay.refetch(params)
}
}
export default createRefetchContainer(
AppRefetchContainer,
graphql`
fragment AppRefetchContainer_list on Item {
...ItemComponent_item
}
`,
graphql`
query AppRefetchContainerQuery($sortBy: String!)
list SampleAppQuery(sortBy: $sortBy) {
...ItemComponent_item
}
`
)
The initial application load is just fine. Clicking on one of the headers should trigger a refetch so that the list data can be sorted on the passed inparams. When I trigger a refetch, the server responds with the correct data, however, the component does not rerender.
I suspect that my issue is with the way I've defined the fragments here. The console error I get on initial page load is:
RelayModernSelector: Expected object to contain data for fragment
`AppRefetchContainer_list`, got `{"list":[{"__fragments":
{"ItemComponent_item":{}},"__id":"[some_valid_id]","__fragmentOwner":null},{...
Question(s):
I'm not entirely sure how to interpret that error. Is it the reason why I'm unable to rerender the UI?
I know this approach may not be ideal, but how do I refetch from a component that isn't necessarily a fragment of the query?
Happy to provide any clarification or missing context :)

Resources