Importing and displaying multiple images from GraphQL using gatsby-plugin-image - javascript

Hi I'm trying to add an instagram feed to my website but I can not find a way to make it work. I haven't really used these tools before so I don't really understand it, but made it to point where I think I have the feed in GraphQL. Now the problem is I don't know how to display the images. Can anyone help?
Here is some code:
import React from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { useStaticQuery, graphql } from "gatsby"
const CapabilityList = () => {
const data = useStaticQuery(graphql`
query InstagramQuery {
allInstagramContent {
edges {
node {
localImage {
childImageSharp {
fixed(width: 200, height: 200) {
...GatsbyImageSharpFixed
}
}
}
}
}
}
}
`)
let arrayOfInstaImages = getImage(data, 'allInstagramContent.edges')
return (
<div style={{ maxWidth: `900px`, marginBottom: `1.45rem`, display: 'flex' }}>
{arrayOfInstaImages.map((item, i) => {
return (
<div key={i} style={{ width: "200px", height: "200px" }}>
<GatsbyImage image={item.node.localImage.childImageSharp.fixed} />
</div>)
})}
</div>
)
}
export default CapabilityList;
This doesn't work and displays error: Property 'map' does not exist on type 'IGatsbyImageData'.
So I think I need some other way to display a few images from the array.
Thanks a lot for every help!

getImage is a helper function (you don't really need it) that returns an image object given a path. Not an array:
Safely get a gatsbyImageData object. It accepts several different
sorts of objects, and is null-safe, returning undefined if the object
passed, or any intermediate children are undefined.
Simply do:
return (
<div style={{ maxWidth: `900px`, marginBottom: `1.45rem`, display: 'flex' }}>
{data.allInstagramContent.edges.node.map((item, i) => {
return (
<div key={i} style={{ width: "200px", height: "200px" }}>
<Img image={item.node.localImage.childImageSharp.fixed} />
</div>)
})}
</div>
)
Note: Img for gatsby-image
Your array of iterable data is the node so you should look and loop there (you can console.log it to help you understand the data structure).
The main problem in your code, besides the wrong loop, is that you are using a GraphQL query structure for Gatsby Image (from version 2, Img from gatsby-image) while you are using GatsbyImage component (from v3 onwards, GatsbyImage from gatsby-image-plugin). You can check for the migration details at: https://www.gatsbyjs.com/docs/reference/release-notes/image-migration-guide/
If you want to use a GatsbyImage, your code should look like:
import React from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { useStaticQuery, graphql } from "gatsby"
const CapabilityList = () => {
const data = useStaticQuery(graphql`
query InstagramQuery {
allInstagramContent {
edges {
node {
localImage {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
}
}
}
`)
return (
<div style={{ maxWidth: `900px`, marginBottom: `1.45rem`, display: 'flex' }}>
{data.allInstagramContent.edges.node.map((item, i) => {
return (
<div key={i} style={{ width: "200px", height: "200px" }}>
<GatsbyImage image={item.node.localImage.childImageSharp.gatsbyImageData} />
</div>)
})}
</div>
)
}
export default CapabilityList;
Note: the use of Img or GatsbyImage will strictly rely on your installed packages and your gatsby-config.js structure. Check it out because you are mixing concepts.
Both components are similar but they accept different image props. While GatsbyImage accepts an image prop containing gatsbyImageData, Img accepts a fluid/fixed prop of childImageSharp (fluid or fixed) data, so the query will be slightly different depending on the component used so as it should be the configuration.
Of course, this is an approximation. Your GraphQL structure may be slightly different depending on your data structure. Check the query at the GraphiQL playground (localhost:8000/___graphql)

Related

React component taking more than 40 seconds to render

I'm trying to create a library to generate skeleton-loading components on the fly. I like those skeletons components with a shimmer effect, but I don't want to create them manually. So my idea is something like this:
I create a component which receives the component that I want to load as a children
while loading, I render a copy of this component, but invisible
I iterate through the component elements, and render a skeleton component based on that
I've already made some progress so far, I created a nextjs boilerplate just for testing purposes, I'm new at opensource, and I will create a library with typescript and rollup when I'm done testing.
My code by now is in this repository: https://github.com/FilipePfluck/skeleton-lib-test
here is the core component:
const Shimmer = ({ children, isLoading, component: Component, exampleProps }) => {
const fakeComponentRef = useRef(null)
useEffect(()=>{
console.log(fakeComponentRef.current.children)
},[fakeComponentRef])
const texts = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong']
const contents = ['img', 'video', 'button']
const styleProps = ['borderRadius', 'padding', 'margin', 'marginRight', 'marginLeft', 'marginTop', 'marginBottom', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'display', 'alignItems', 'justifyContent', 'flexDirection']
const renderElement = (element) => {
console.log('renderElement')
const object = {}
styleProps.forEach(s => Object.assign(object, {[s]: element.style[s]}))
if(texts.includes(element.localName)){
const fontSize = +document.defaultView.getComputedStyle(element, null)["fontSize"].replace('px','')
const lineHeight = +document.defaultView.getComputedStyle(element, null)["lineHeight"].replace('px','') | fontSize * 1.2
const numberOfLines = Math.round(element.offsetHeight / lineHeight)
const lineMarginBottom = lineHeight - fontSize
const lines = []
for(let i=0; i<numberOfLines; i++){
lines.push(i)
}
return(
<div style={{display: 'flex', flexDirection: 'column'}}>
{lines.map(line => (
<div
style={{
width: element.offsetWidth,
...object,
height: fontSize,
marginBottom: lineMarginBottom
}}
className="shimmer"
key={"line"+line}
/>))}
</div>
)
}
if(contents.includes(element.localName)){
return (
<div
style={{
width: element.offsetWidth,
height: element.offsetHeight,
...object
}}
className={'shimmer'}
/>
)
}
return (
<div
style={{
width: element.offsetWidth,
height: element.offsetHeight,
display: element.style.display,
alignItems: element.style.alignItems,
justifyContent: element.style.justifyContent,
flexDirection: element.style.flexDirection,
padding: element.style.padding,
margin: element.style.margin
}}
>
{!!element.children
? [...element.children]
.map(child => renderElement(child))
: null
}
</div>
)
}
return isLoading ? (
<>
<div style={{visibility: 'hidden', position: 'absolute'}} ref={fakeComponentRef}>
<Component {...exampleProps}/>
</div>
{fakeComponentRef?.current && renderElement(fakeComponentRef.current)}
</>
) : children
}
Basically I'm doing a recursive function to render the skeleton component. But I got a problem: the loading component is taking too long to render. Sometimes it takes over 40 seconds. And this is a massive problem, a loading component should not take longer to render then the loading itself. But I have no clue why is it taking so long. Also, I know that there is a lot of other problems, but my first goal was to render the component on the screen. I'd be pleased if any of you wanted to contribute to my project
UPDATE:
I kinda solved it. I commented the piece of code where the function calls itself, and it took the same time to render. So, I concluded something was interrupting the code during the first execution or before. I guess the problem was the fakeComponentRef?.current && before the function call. without this validation it wouldn't work because the ref starts as null, but I guess the UI doesn't change when a ref changes, as it would because of a state? Then I created a useEffect with a setTimeout to wait 1ms (just to make sure it is not null) to render the component and it worked.

What Type is Storybook Story From Example

I have used some code from the example on storybook website, specifically:
export const Primary = Primary.decorators = [(Story) => <div style={{ margin: '3em' }}><Story/></div>]
However, even though this is the typescript example, it does not specify a type for Story and my linter will not pass unless it has a type. What type should I use for Story?
Story: any
also will not pass.
Reference: https://storybook.js.org/docs/react/writing-stories/decorators
You can tell what sort of properties we require from Story based on how we use it. We are expecting that it is something that can be called via JSX with no arguments and return an element. So you could use (Story: React.FC) and that would work.
As shown in this example, you can import the type Meta from #storybook/react and use that as the type for Primary.
import { Meta } from "#storybook/react";
const Primary: Meta = {
title: "MyStory",
decorators: [
(Story) => (
<div style={{ margin: "3em" }}>
<Story />
</div>
)
]
};
They type for Story here is inferred as () => StoryFnReactReturnType.
You could also import the type for Story:
import { Story } from "#storybook/react";
export default {
title: "MyStory",
decorators: [
(Story: Story) => (
<div style={{ margin: "3em" }}>
<Story />
</div>
)
]
};

How can I include my existing table into my export function?

I am relatively new to React-JS and was wondering how I could pass my variables to my export function. I am using the jsPDF library.
At the time the Summary page is showing up, every thing is already in the database.
The Summary page creates in every round an IdeaTable component, writes it into an array and renders it bit by bit if the users click on the Next button (showNextTable()).
This component can use a JoinCode & playerID to assemble the table that was initiated by this player.
import React, { Component } from "react";
import { connect } from "react-redux";
import { Box, Button } from "grommet";
import IdeaTable from "../playerView/subPages/ideaComponents/IdeaTable";
import QuestionBox from "./QuestionBox";
import { FormUpload } from 'grommet-icons';
import jsPDF from 'jspdf';
export class Summary extends Component {
state = {
shownTable: 0
};
showSummary = () => {};
showNextTable = () => {
const { players } = this.props;
const { shownTable } = this.state;
this.setState({
shownTable: (shownTable + 1) % players.length
});
};
exportPDF = () => {
var doc = new jsPDF('p', 'pt');
doc.text(20,20, " Test string ");
doc.setFont('courier');
doc.setFontType('bold');
doc.save("generated.pdf");
};
render() {
const { topic, players } = this.props;
const { shownTable } = this.state;
const tables = [];
for (let i = 0; i < players.length; i++) {
const player = players[i];
const table = (
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={player.id} />
</Box>
);
tables.push(table);
}
return (
<Box
style={{ wordWrap: "break-word" }}
direction="column"
gap="medium"
pad="small"
overflow={{ horizontal: "auto" }}
>
<QuestionBox question={topic} />
{tables[shownTable]}
<Button
primary
hoverIndicator="true"
style={{ width: "100%" }}
onClick={this.showNextTable}
label="Next"
/>
< br />
<Button
icon={ <FormUpload color="white"/> }
primary={true}
hoverIndicator="true"
style={{
width: "30%",
background: "red",
alignSelf: "center"
}}
onClick={this.exportPDF}
label="Export PDF"
/>
</Box>
);
}
}
const mapStateToProps = state => ({
topic: state.topicReducer.topic,
players: state.topicReducer.players
});
const mapDispatchToProps = null;
export default connect(mapStateToProps, mapDispatchToProps)(Summary);
So basically how could I include the IdeaTable to work with my pdf export?
If you want to use html module of jsPDF you'll need a reference to generated DOM node.
See Refs and the DOM on how to get those.
Alternatively, if you want to construct PDF yourself, you would use data (e.g. from state or props), not the component references.
Related side note:
On each render of the parent component you are creating new instances for all possible IdeaTable in a for loop, and all are the same, and most not used. Idiomatically, this would be better:
state = {
shownPlayer: 0
};
Instead of {tables[shownTable]} you would have:
<Box pad={{ vertical: "large", horizontal: "medium" }}>
<IdeaTable authorID={shownPlayer} ref={ideaTableRef}/>
</Box>
And you get rid of the for loop.
This way, in case you use html dom, you only have one reference to DOM to store.
In case you decide to use data to generate pdf on your own, you just use this.props.players[this.state.shownPlayer]
In case you want to generate pdf for all IdeaTables, even the ones not shown, than you can't use API that needs DOM. You can still use your players props to generate your own PDF, or you can consider something like React-Pdf

Passing Functional Components to React Spring's useTransition

I'm trying to write a React component which contains a panel of buttons, and each of the buttons needs to be able to be independently switched in and out depending on the state of the app. To do this I've created a useTransition, wrapped in a custom hook, which is supposed to trigger the transition when it recieves a new SVG functional component.
The issue I'm having is that while the buttons are currently transitioning in correctly, I can't seem to get the useTransition to swap them out when it receives a new component. It will remove one when the component is replaced with an empty array, which is intended, and a useEffect inside the hook triggers when a new component is passed in, but for some reason that change is not picked up by the useTransition.
I'm guessing it has something to do with object equality between the components but I don't know how to work around it without forcing the change with window.requestAnimationFrame to set the state twice. Here is a codesandbox with a minimal demo.
Parent Component:
import React, { useState } from "react";
import { a } from "react-spring";
import "./styles.css";
import PlaySVG from "./svgs/PlaySVG";
import SaveSVG from "./svgs/SaveSVG";
import SearchSVG from "./svgs/SearchSVG";
import TrashSVG from "./svgs/TrashSVG";
import { useVertTransition } from "./Hooks";
export default function ActionButtons() {
const svgs = {
play: <PlaySVG style={{ width: "100%", height: "auto" }} id="play" />,
search: <SearchSVG style={{ width: "80%", height: "auto" }} id="search" />,
save: <SaveSVG style={{ width: "80%", height: "auto" }} id="save" />,
trash: <TrashSVG style={{ width: "80%", height: "auto" }} id="trash" />
};
const [actions, setActions] = useState({
one: svgs.play,
two: svgs.search,
three: svgs.trash
});
const [slotOne] = useVertTransition(actions.one);
const [slotTwo] = useVertTransition(actions.two);
const [slotThree] = useVertTransition(actions.three);
function buttonHandler() {
setActions({
...actions,
one: [],
two: svgs.save
});
}
return (
<div className="container">
<div className="panel">
<div className="button-container">
{slotOne.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item}
</a.button>
))}
</div>
<div className="button-container">
{slotTwo.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item}
</a.button>
))}
</div>
<div className="button-container">
{slotThree.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item}
</a.button>
))}
</div>
</div>
</div>
);
}
useVertTransition hook:
import React, { useEffect } from "react";
import { useTransition } from "react-spring";
export function useVertTransition(item) {
useEffect(() => {
console.log(item);
}, [item]);
const vertTransition = useTransition(item, null, {
from: { transform: "translateY(-20px) rotateX(-90deg)", opacity: 0 },
enter: { transform: "translateY(0px) rotateX(0deg)", opacity: 1 },
leave: { transform: "translateY(20px) rotateX(90deg)", opacity: 0 },
trail: 400,
order: ["leave", "enter"]
});
return [vertTransition];
}
SVG component example:
import React from "react";
function PlaySVG({ style }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" style={style} viewBox="0 0 24 24">
<path d="M7 6L7 18 17 12z" />
</svg>
);
}
export default PlaySVG;
You are right, that react-spring is not able to detect the difference between buttons. But it does not examine the items, it examine only the keys. So you have to add something for unique keys. So you may want to changed the input from svg to an object containing a name beside the svg.
const getSvg = name => ({ name, svg: svgs[name] });
const [actions, setActions] = useState({
one: getSvg("play"),
two: getSvg("search"),
three: getSvg("trash")
});
You can now add the key function:
const vertTransition = useTransition(item, svg => svg.name, {
And you should change the render:
{slotOne.map(({ item, props, key }) => (
<a.button key={key} style={props} onClick={buttonHandler}>
{item.svg}
</a.button>
))}
And now it works as intended. I think.
https://codesandbox.io/s/usetransition-demo-152zb?file=/src/ActionButtons.js:1253-1437
And I also learned a new trick, I never used the order property.

Task orphaned for request in react-native – what does it mean?

I am trying to build a grid system for tiles with buttons and other actions. I forked trying with the react native playground grid images source, that you can find here. It produces the following "stacktrace" and error when adding zIndex to individual pics. Images are never portrayed.
In case you are interested this is the exact component I am using:
export default class GridLayout extends Component {
constructor () {
super()
const { width, height } = Dimensions.get('window')
this.state = {
currentScreenWidth: width,
currentScreenHeight: height
}
}
handleRotation (event) {
var layout = event.nativeEvent.layout
this.setState({ currentScreenWidth: layout.width, currentScreenHeight: layout.height })
}
calculatedSize () {
var size = this.state.currentScreenWidth / IMAGES_PER_ROW
return { width: size, height: size }
}
renderRow (images) {
return images.map((uri, i) => {
return (
<Image key={i} style={[styles.image, this.calculatedSize()]} source={{uri: uri}} />
)
})
}
renderImagesInGroupsOf (count) {
return _.chunk(IMAGE_URLS, IMAGES_PER_ROW).map((imagesForRow) => {
console.log('row being painted')
return (
<View key={uuid.v4()} style={styles.row}>
{this.renderRow(imagesForRow)}
</View>
)
})
}
render () {
return (
<ScrollView style={styles.grid} onLayout={(ev) => this.handleRotation(ev)} contentContainerStyle={styles.scrollView}>
{this.renderImagesInGroupsOf(IMAGES_PER_ROW)}
</ScrollView>
)
}
}
var styles = StyleSheet.create({
grid: {
flex: 1,
backgroundColor: 'blue'
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
backgroundColor: 'magenta'
},
image: {
zIndex: 2000
}
})
It may have something to do with the way you are rendering. Your request seems to get stuck. It looks like this is the code where the error is thrown in RN (from RCTImageLoader.m):
// Remove completed tasks
for (RCTNetworkTask *task in _pendingTasks.reverseObjectEnumerator) {
switch (task.status) {
case RCTNetworkTaskFinished:
[_pendingTasks removeObject:task];
_activeTasks--;
break;
case RCTNetworkTaskPending:
break;
case RCTNetworkTaskInProgress:
// Check task isn't "stuck"
if (task.requestToken == nil) {
RCTLogWarn(#"Task orphaned for request %#", task.request);
[_pendingTasks removeObject:task];
_activeTasks--;
[task cancel];
}
break;
}
}
I'm not exactly sure how to solve this, but a couple ideas to help you debug:
The <Image> component has some functions and callbacks that you could utilize to try to further track down the issue (onLoad, onLoadEnd, onLoadStart, onError, onProgress). The last two are iOS only but you could see if any of those are called to see where in the process things get hung up.
Alternatively, the way I would do this would be to use a ListView and place the image urls in the datasource prop for the ListView (utilizing the _.chunk method to group them as you already do). This would be a little cleaner way of rendering them IMO
I think this issue is possibly related to you trying to load http images in ios. I get this same error in my project when using faker images in my seed data on ios (android doesn't care) which only come back with http. Here's a better explanation that I found on this topic React-native loading image over https works while http does not work
according to react native, it better to have the item component a pure component and all the data to be rendered in the component should be in the item object itself for better performance. In case of image I also dont know how they are handling it. loading image from url might be something which is not in the item object itself. However, i am also getting the issue and I am calling a function to load an image icon which are packed with application.

Categories