GatsbyImage working when pulling data locally but not with Strapi and Gatsby - undefined

I'm on the verge of quitting (again!!) but keeping at it..
Would really appreciate some help on this or my laptop may be thrown out the window soon!
I set up a project locally and am now linking it to content on Strapi. I'm able to add the text data from Strapi fine but what I'm really struggling with is the GatsbyImage data.
I'm getting this error:
Warning: Failed prop type: The prop image is marked as required in GatsbyImage, but its value is undefined.
and here's my code:
import React from "react";
import { SubTitle } from "../components/styles/Styles";
import { GatsbyImage, getImage } from "gatsby-plugin-image";
import { graphql, useStaticQuery } from "gatsby";
import { ImageLayout } from "../components/styles/GridLayout";
// featured products on home page
const query = graphql`
{
allStrapiNewArrivals {
nodes {
images {
localFile {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
layout: CONSTRAINED
height: 400
width: 400
)
}
}
}
}
}
}
`;
const FeatureProducts = () => {
const data = useStaticQuery(query);
const nodes = data.allStrapiNewArrivals.nodes;
console.log(nodes);
return (
<div>
<SubTitle>New Arrivals</SubTitle>
<ImageLayout>
<div>
<div className="collection-cards">
{nodes.map((image, index) => {
const pathToImage = getImage(image);
return (
<GatsbyImage
image={pathToImage}
alt=""
key={index}
className="collection"
/>
);
})}
</div>
</div>
</ImageLayout>
</div>
);
};
export default FeatureProducts;
When I console.log(nodes) it returns:
[{…}]
0:
images: Array(3)
0: {localFile: {…}}
1: {localFile: {…}}
2: {localFile: {…}}
length: 3
__proto__: Array(0)
__proto__: Object
length: 1
__proto__: Array(0)
My thoughts - in the allStrapiNewArrivals data, could the 'images{ localFile ' bit be the cause? because these aren't listed when pulling the data locally. eg. should it read: 'file{childImageSharp'
I've tried using 'const nodes = data.allStrapiNewArrivals.nodes.images.localFile' but this is also throwing an error of:
Cannot read property 'map' of undefined
or could it be the getImage() in the .map function? - const pathToImage = getImage(image);
If anyone can help I'd be so grateful, I've been stuck on this for ages!

images is an array of images so you would have to map over it too. Also try gatsby clean
nodes.map((node, index) => {
return node.images.map(image => {
const pathToImage = getImage(image.localFile);
return ( <
GatsbyImage image = {
pathToImage
}
alt = ""
key = {
index
}
className = "collection" /
>
);
})
})

Related

Generate Image from HTML String in React

I'm trying to generate some content for an HTML source dynamically, and then convert it to an Image (JPG or PNG) for later conversion into Base64.
For prefilling the HTML I'm using VelocityJS. And then attempting to use html-to-image library to convert the formatted HTML String/Element to an Image.
But I keep getting the following error.
oops, something went wrong! TypeError: Cannot read properties of undefined (reading 'defaultView')
at px (util.ts:69:1)
at getNodeWidth (util.ts:75:1)
at getImageSize (util.ts:87:1)
at toCanvas (index.ts:32:1)
at Module.toJpeg (index.ts:83:1)
at usePaymentReceiptBuilder.js:26:1
I first tried passing the string itself to the library. Which didn't work either. Then attempted adding the string into a div, and then wrapping it with JQuery to create an element. None of these worked.
The code is as follows.
import velocityjs from 'velocityjs';
import * as htmlToImage from 'html-to-image';
import paymentReceiptHtml from '../resources/email-receipts/payment-receipt.txt';
import useBusinessInfo from "./useBusinessInfo";
import $ from 'jquery';
const usePaymentReceiptBuilder = () => {
const {businessId, businessInfo, currencySymbol} = useBusinessInfo();
const PAPER_SIZE_TWO_INCH = 384;
const printReceiptOnCloverDevice = (purchaseEntry) => {
fetch(paymentReceiptHtml)
.then((response) => response.text())
.then((paymentReceiptHtmlTextContent) => {
let receiptContent = buildReceiptContent(purchaseEntry);
let velocityContext = {
"receiptContent": receiptContent
};
let renderedString = velocityjs.render(paymentReceiptHtmlTextContent, velocityContext);
// let htmlContent = <div dangerouslySetInnerHTML={{__html: renderedString}}/>;
let htmlContent = $(renderedString)
htmlToImage.toJpeg(htmlContent)
.then(function (dataUrl) {
console.log("generateReceiptBase64String 4");
console.log(dataUrl);
})
.catch(function (error) {
console.error('oops, something went wrong!', error);
});
});
}
const buildReceiptContent = (purchaseEntry) => {
return {
businessName: businessInfo.businessName,
businessAddress: businessInfo.address,
businessWebsite: "",
businessContactNumber: businessInfo.contactNumbers.length > 1 ? businessInfo.contactNumbers[0] : "",
businessLogoUrl: "",
receiptOrderContent: purchaseEntry,
instanceMarketingUrl: "",
orderQrCodeBase64: "",
paperWidth: PAPER_SIZE_TWO_INCH
};
}
return {printReceiptOnCloverDevice}
}
export default usePaymentReceiptBuilder
What am I doing wrong? Please assist.

Mapping JSON data with React Hooks

I'm working on a small project and I am trying to map data from a JSON file into my project.
In components with nested data, I keep getting an let data = props.data["runways"];.
data.json:
{
"runways":[
{
"slot":"Area 1",
"planes":[
{
"name":"PanAm",
"number":"12345",
"start":{
"time":1585129140
},
"end":{
"time":1585130100
}
},
{
"name":"PanAm 222 ",
"number":"12345",
"start":{
"time":1585129140
},
"end":{
"time":1585130100
}
}
]
}
]
}
App.js,
I pass the JSON data as props:
import planeData from './plane_info.json'
const Container = () => {
const [planeDataState, setPlaneDataState] = useState({})
const planeData = () => setPlaneDataState(planeData[0].runways)
return (
<>
<MyPlane planeInfo={planeDataState}/>
<button onClick={planeData} type="button">Get Data</button>
</>
)
}
and finally, I want to bring my data into my component:
MyPlane.jsx
const MyPlane = (props) => {
let data = props.data["runways"];
if(data)
console.log(data, 'aaa')
return (
<>
{
data ? (
<div>
<span>{props.planeInfo.name}</span>
<span>RAIL TYPE: {props.planeInfo.type}</span>
</div>
) : <h6>Empty</h6>
}
</>
);
}
According to the error message, the problem occurs at this line of code: let data = props.data["runways"]; However, I believe that I am passing the data for runways from the JSON file.
I've never worked with React Hooks to pass data, so I'm confused about why this error is occurring.
In order to map effectively over the JSON data it's necessary to understand how that data structure is composed.
If you're unsure, using JSON.stringify() is a great way to get the "bigger picture" and then decide what exactly is it that you want to display or pass down as props to other components.
It appears you wish to get the plane data (which is currently an array of 2 planes). If so, you could first get that array, set the state, then map over it to display relevant info. Perhaps like this:
const data = {
"runways":[
{
"slot":"Area 1",
"planes":[
{
"name":"PanAm",
"number":"12345",
"start":{
"time":1585129140
},
"end":{
"time":1585130100
}
},
{
"name":"PanAm 222 ",
"number":"12345",
"start":{
"time":1585129140
},
"end":{
"time":1585130100
}
}
]
}
]
}
function App() {
const [ planeData, setPlaneData ] = React.useState(null)
React.useEffect(() => {
setPlaneData(data.runways[0].planes)
}, [])
return (
<div className="App">
{/* {JSON.stringify(planeData)} */}
{planeData && planeData.map(p => (
<p key={p.name}>
{p.name} | {p.number} | {p.start.time} | {p.end.time}
</p>
))}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Here const planeData = () => setPlaneDataState(planeData[0].runways)
In this line, planeData[0].runways will be undefined according to the json file which you have shared.
Instead try setting and passing entire json object, ie,
const planeData = () => setPlaneDataState(planeData)
Try this, And then inside MyPlane.jsx component, let data = props.data["runways"]; this won't be undefined. So , the error won't come.
At the beginning there is no data in props.data['runways'] (also you can use props.data.runways, I guess you come from another language like Python as of this syntax that you are using), because you sent the request at first, it takes time for request to be satisfied, so you need to check in your <MyPlane /> component to see if there is a runways key in data and then proceed to render the component, something like below:
const MyPlane = (props) => {
const data = props.data
return (
<>
{
data.runways
? <>
...your render able items that you wrote before
</>
: <p>There is no data yet!</p>
}
</>
)
}
Also please note that you might return something from component. At your case your render is inside the if(data){...} statement! what if the condition was not satisfied? which is your current error case !
NOTE: please check that you are passing your planeDataState as planeInfo prop to the child component, so you might have something like:
const data = props.planInfo
to be able to use the data variable that you've defined before the render part.

How to access ag-Grid API in React function component (useState hook)?

What is the best way of accessing ag-Grid API inside of React function component?
I have to use some of the methods from API (getSelectedNodes, setColumnDefs etc.) so I save a reference to the API (using useState hook) in onGridReady event handler:
onGridReady={params => {
setGridApi(params.api);
}}
and then I can call the API like this: gridApi.getSelectedNodes()
I haven't noticed any problems with this approach, but I'm wondering if there's more idiomatic way?
Stack:
ag-grid-community & ag-grid-react 22.1.1
react 16.12.0
We find the most idiomatic way to use a ref. As the api is not a state of our component. It is actually possible to simply do:
<AgGridReact ref={grid}/>
and then use it with
grid.current.api
Here an example:
import React, { useRef } from 'react'
import { AgGridReact } from 'ag-grid-react'
import { AgGridReact as AgGridReactType } from 'ag-grid-react/lib/agGridReact'
const ShopList = () => {
const grid = useRef<AgGridReactType>(null)
...
return (
<AgGridReact ref={grid} columnDefs={columnDefs} rowData={shops} />
)
}
The good thing here is, that you will have access to the gridApi but als to to the columnApi. Simply like this:
// rendering menu to show/hide columns:
{columnDefs.map(columnDef =>
<>
<input
type='checkbox'
checked={
grid.current
? grid.current.columnApi.getColumn(columnDef.field).isVisible()
: !(columnDef as { hide: boolean }).hide
}
onChange={() => {
if (grid.current?.api) {
const col = grid.current.columnApi.getColumn(columnDef.field)
grid.current.columnApi.setColumnVisible(columnDef.field, !col.isVisible())
grid.current.api.sizeColumnsToFit()
setForceUpdate(x => ++x)
}
}}
/>
<span>{columnDef.headerName}</span>
</>
)}
Well I am doing it in my project. You can use useRef hook to store gridApi.
const gridApi = useRef();
const onGridReady = params => {
gridApi.current = params.api; // <== this is how you save it
const datasource = getServerDataSource(
gridApi.current,
{
size: AppConstants.PAGE_SIZE,
url: baseUrl,
defaultFilter: props.defaultFilter
}
);
gridApi.current.setServerSideDatasource(datasource); // <== this is how you use it
};
I'm running into the same issue but here is a workaround that at least can get you the selected rows. Essentially what I'm doing is sending the api from the agGrid callbacks to another function. Specifically I use OnSelectionChanged callback to grab the current row node. Example below:
const onSelectionChanged = params => {
setDetails(params.api.getSelectedRows());
};
return (<AgGridReact
columnDefs={agData.columnDefs}
rowSelection={'single'}
enableCellTextSelection={true}
defaultColDef={{
resizable: true,
}}
rowHeight={50}
rowData={agData.rowData}
onCellFocused={function(params) {
if (params.rowIndex != null) {
let nNode = params.api.getDisplayedRowAtIndex(params.rowIndex);
nNode.setSelected(true, true);
}
}}
onSelectionChanged={function(params) {
onSelectionChanged(params);
params.api.sizeColumnsToFit();
}}
onGridReady={function(params) {
let gridApi = params.api;
gridApi.sizeColumnsToFit();
}}
deltaRowDataMode={true}
getRowNodeId={function(data) {
return data.id;
}}
/>);

Cannot read property 'rotationOffset' of undefined

I am using react-vr and trying to use the json object to render image, but I'm getting an error that the browser cannot read property 'rotationOffset' of undefined. Below is my code for reference.
static defaultProps = {
portal: 'webTour.json',
} //assigning the json to a variable.
componentDidMount() {
fetch(asset(this.props.portal).uri)
.then(response => response.json())
.then(responseData => {
this.init(responseData);
})
.done
();
}
init(tourConfig) {
// Initialize the tour based on data file.
this.setState({
data: tourConfig,
locationId: null,
nextLocationId: tourConfig.firstPhotoId,
rotation: tourConfig.firstPhotoRotation + (tourConfig.photos[tourConfig.firstPhotoId].rotationOffset || 0),
});
}
render() {
//some code
const locationId = this.state.locationId;
const photoData = (locationId && this.state.date.photos[locationId]) || null;
const rotation = this.state.data.firstPhotoRotation + ((photoData && photoData.rotationOffset) || 0);
const isLoading = this.state.nextLocationId !== this.state.locationId;
return (
<Pano source = {asset(this.state.data.photos[this.state.nextLocationID].uri)} />
}
And below is my json file for reference.
{
"nave_icon": "gateway.png",
"firstPhotoID" : "112001",
"firstPhotoRotation" : 90,
"photos":{
"112001":{
"rotationOffset": 0,
"uri": "CustomPano_2.jpg",
}
}
}
What I'm trying to do is that change the background image with the objects inside the Pano. Am I missing any essential syntax? I've spent hours to figure out the problem is not working. Any help is much appreciated.

best way to pass an array of object with activeroute - Angular

I'm trying to pass an array of objects through activeroute. When I pass it to the next page I get [object Object]. I saw a question on Stackoverflow where they use JSON.stringify but that didn't work for me. Or is it better to use application providers instead of queryparams.
TS of page sending the data
criteriaList: ShipmentLookupCriteria[] = [];
navigateTo() {
const navigationExtras: NavigationExtras = {
queryParams: {
criteriaList: this.criteriaList
}
};
this.router.navigate(['/lookup/results'], navigationExtras);
}
TS of page receiving the data
this.sub = this.route.queryParams.subscribe(params => {
console.log(params.criteriaList);
});
ShipmentLookUpCriteria model
import { EquipReferenceTuple } from './equip-reference-tuple.model';
export class ShipmentLookupCriteria {
terminal: string;
equipReferenceList: EquipReferenceTuple[];
constructor(terminal: string, equipReferenceList: EquipReferenceTuple[]) {
this.terminal = terminal;
this.equipReferenceList = equipReferenceList;
}
}
UPDATE
I decided to start with something simple. So I create an array of objects with dummy data.
navigateTo() {
const navigationExtras: NavigationExtras = {
queryParams: {
criteriaList: [{ name: 1, age: 1 }, { name: 2, age: 2 }]
}
};
this.router.navigate(['lookup/results'], navigationExtras);
}
PAGE RECEIVING THE PARAMS
this.route.queryParams.subscribe(params => {
console.log(params.criteriaList[0]);
});
RETURNS = [object Object] If I do again JSON.stringify it shows it as string "[object Object]". if I do params.criteriaList[0].name returns undefined
You can simply pass,
this.router.navigate(['/lookup/results'], {queryParams: {criteriaList: this.criteriaList }});
and access it using
this.sub = this.route.snapshot.queryParamMap.get('criteriaList');