Unable to Display Image from S3 Using Signed URL : React/Amplify - javascript

I am attempting to retrieve and display images from S3 using a signed URL. I can log the Signed URL to the console, click it, and it will download the image locally. The downloaded image is not corrupted in any way.
: Component Hierarchy
App
- Maps
-- Map
The App component manages filtering of maps, the filtered maps state is then passed to the Maps component which dynamically renders Map components.
export default function Maps({ maps }) {
return (
<Carousel
autoPlay={false}
PrevIcon={<ArrowCircleLeftIcon fontSize='large' />}
NextIcon={<ArrowCircleRightIcon fontSize='large' />}
>
{maps.map((map, i) => (
<Map key={i} map={map} />
))}
</Carousel>
)
}
My Map component holds state containing the Maps Image URL, and a useEffect hook which calls a getImg function.
export default function Map({ map }) {
const [mapImg, setMapImg] = useState(null)
useEffect(() => {
if (map.uri) {
getImg(map.uri).then((res) => {
console.log(res)
setMapImg(res)
})
}
}, [map.uri])
return (
<CardMedia
component='img'
alt='random nature'
height='550'
image={mapImg}
/>
)
Finally, the getImg function.
export const getImg = (uri) => {
const uriToS3Path = (uri) => {
let path = uri.substring(0, 6)
const file = uri.substring(6) + '.TIF'
path = path.split('').join('/') + '/'
return path + file
}
const filePath = uriToS3Path(uri)
return Storage.get(filePath, { contentType: 'image/tiff' })
}
Don't be confused by map.uri, basically this property determines the key for the S3 object, hence the uriToS3Path function as above. I don't receive any debug errors, I can retrieve the link but the image does not display. Is it due to some asynchronous activity or should the entire approach to fetching be altered?
Many Thanks.
Update:
Inspecting the Application tab within the developer console. You can see that the S3 Image is listed (92.TIF), but the preview is broken.

Related

Display dynamic data in a PDF using #react-pdf

I am building an Electron App that allows user to auto populate a PDF based on fetched data from an internal server. I'm trying to use #react-pdf/renderer to display and create the PDF.
I've been using these two reference pages, official documentation, regarding this topic:
On the fly rendering
Dynamic content
Here's my code:
Home.tsx (abridged)
import PDFDoc from "/components/PDFDoc";
export default function Home(){
// I'm not sure how to use this as the official docs are limited.
const [pdfInstance, setPdfInstance] = ReactPDF.usePDF({ document: PDFDoc });
// I'd display code, here, to display data but it isn't relevant to this case.
return(
<PDFViewer
width="100%"
height="100%"
children={PDFDoc}
/>
)
}
PDFDoc.tsx
const PDFDoc = () => (
<Document>
<Page>
<Text>Test text</Text>
<Text
render={({pageNumber, totalPages}) => `${pageNumber} / ${totalPages}`}
fixed
/>
</Page>
</Document>
)
export default PDFDoc;
If I create the PDF in a normal component style similar to the home page, it displays properly within the PDFViewer.
The issue:
Without passing props to the component, how can I send data to the document so as to display dynamic data fetched via REST API.
I'm hoping that someone more experienced with the library can assist me with understanding how to dynamically display data within the PDF.
Yes, the react-pdf documentation about dynamic content.
So having faced the same challenge, here's what I discovered. It should help you ...
If you look at the codebase, under #react-pdf/renderer/index.d.ts (v2.1.1), you'll find two major optional props - render and children - that <Text/> can accept (at least it vital for you to understand them for this use case of passing dynamic content/data)
declare namespace ReactPDF {
// ...
interface TextProps extends NodeProps {
render?: (props: {
pageNumber: number;
totalPages: number;
subPageNumber: number;
subPageTotalPages: number;
}) => React.ReactNode;
children?: React.ReactNode;
// ...
}
/**
* A React component for displaying text...
*/
class Text extends React.Component<TextProps | SVGTextProps> {}
// ...
}
declare const Text: typeof ReactPDF.Text;
// ...
export { Text, // ... }
Note this:
render prop expects specific props to be passed through it.
So it cannot accept any kind of data fetched via the API calls unless you're obviously passing it stuff related to no. of pages and total pages etc.
On the hand, the children prop is more open versatile in this case.
The type of "children" expected are based off the React.ReactNode.
type React.ReactNode = boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined
So your answer lies in utilizing the children prop. How?
Here's a step-by-step process:
In the Parent component, do this...
const Parent = props => {
React.useEffect(() => {
// make API call
apiCall(...).then(res => {
if (res) {
// store your data in a session or local storage
// Why don't we use "state" and pass it down as props?
sessionStorage.setItem('data', JSON.stringify(res));
};
}).catch(error => ... )
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (...);
};
If you try to pass your fetched data (after the API call) by updating the Parent component state with an aim to pass it to the Child component as props, it does not work!
Why? The behavior is that the passed props are often undefined (I'm yet to understand why!).
Therefore storing the fetched data using those storage methods helps (or if you're using redux you might want to keep it in the store)
In the Child component, do this...
import { Page, Text, View, Document, StyleSheet } from '#react-pdf/renderer';
// Create styles
const styles = StyleSheet.create({
page: { ... },
section: { ... }
});
// Create Document Component
export const Child = () => {
const fetchedData = JSON.parse(sessionStorage.getItem("data"));
// let's destructure our data
const { email, gender, username, lastname, firstname, ... } = fetchedData;
// we define block-scope React Components to be passed into <Text>
// for example ...
const LastName = () => `${lastname}`;
return (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>Last name:</Text>
<Text children={<LastName/>} />
</View>
<View style={styles.section}>
<Text>Section #2</Text>
</View>
</Page>
</Document>
);
};
If you change this line:
<Text children={<LastName/>} />
and replace it with this:
<Text>{lastname}</Text>
It will not work (to your dismay) ... see the "Why?" under Parent component.
Otherwise, here's a screenshot where this approach worked - look towards the bottom-left to see what the Child component renders (that's dynamic).

Is it possible to fetch/parse JSON data from one component to a different rendered/dynamic route? (React JS)

I am new to reactJS and just started learning it for a coding assignment, but I am stuck on this one problem. I created an api in a component that FETCHes photo and photo album data from the online JSON prototyping website, and I want to use that data to display on a user page (only photos and albums related to that specific user/userID). The user page is a dynamic child component of the parent component (the home page). here is what it looks like so far
This is for the JSON data. I call it apiData
import React, { Component } from "react";
class apiData extends Component {
state = {
photos: [],
albums: [],
isLoading: false
};
componentDidMount() {
this.loadData();
}
loadData = async () => {
const photos = await fetch(
"https://jsonplaceholder.typicode.com/photos"
);
const albums = await fetch(
"https://jsonplaceholder.typicode.com/albums"
);
const photoData = await photos.json();
const albumData = await albums.json();
this.setState({ isLoading: true, photos: photoData, albums: albumData});
};
render() {
return (
<div className="App">
<p> dataObject={this.state.results}</p>
</div>
);
}
}
This is the rendered/dynamic route. currently, I am using
const userPhoto = apiData.photos.find((userPhoto) =>{
to try to access the API, but I get an error that says
TypeError: Cannot read property 'find' of undefined"
(keep in mind, these 2 components are coded on the same .js file)
const userPhotoPage = ({match}) => {
const userPhoto = apiData.photos.find((userPhoto) =>{
return parseInt(match.params.id) == userPhoto.id
})
return <>
{match.isExact && <>
<h1>{userPhoto.title}</h1>
<p>image = {userPhoto.url}</p>
</>}
</>
}
I want my userPhotoPage function to successfully access the data from apiData, so I can manipulate it and add data to the photo page, but I'm not sure if this is possible? If I need to provide more information I'll gladly give it!
It looks like your trying to access the find method on an undefined object. First off, the .find() method is an iterator on arrays. I tried fetching the data from your JSON photos API but for some reason, it's hanging on this end. I'm not sure if that API actually returns an array. I would look into that first. Next, even if it returns an array and your able to chain the find method on it you're still left with the issue of function scope. The photo objects is scoped to/private your loadData async funtion. You will not be able to access it outside of the function.

Implement RSS feed in React native

So, I've been learning react native lately and I am trying to create an RSS reader.I have managed to Download the RSS data but I am stuck in implementing them into JSX ( Screen). I've been trying using setState method but it didn't work.
I can get the RSS data logged into Console but I can't display them in user through JSX
PS: Comments are just tests that I made
class HomeScreen extends React.Component {
state = {
feed: {},
items: {}
};
RSS() {
return fetch("http://di.ionio.gr/feed/")
.then(response => response.text())
.then(responseData => rssParser.parse(responseData))
.then(rss => {
this.setState(() => ({
//...prevState,
feed: rss
//titles: rss.items,
}));
});
}
render() {
{this.RSS();}
return (
<View style={styles.top}>
<HeaderScreen {...this.props} />
<Text>{this.state.feed.title}</Text>
</View>
);
}
}
I've been using react-native-rss-parser.
I also tried without setState but that also didn't work.
Thanks in advance
With your current setup, you're creating an endless loop. This is because you have a side effect (i.e. network request) in your render function.
When your request returns and you call setState your component is rendered again, which in turn calls the network again with this.RSS(), etc. etc. etc.
To fix this, move the this.RSS() to either your constructor function or better yet to the componentDidMount function.
i.e.
componentDidMount() {
this.RSS();
}
render() {
return (
<View style={styles.top}>
<Text>{this.state.feed.title}</Text>
</View>
);
}

Access query inside return method

I have a backend Drupal site and react-native app as my frontend. I am doing a graphQL query from the app and was able to display the content/s in console.log. However, my goal is to use a call that query inside render return method and display it in the app but no luck. Notice, I have another REST API call testName and is displaying in the app already. My main concern is how to display the graphQL query in the app.
Below is my actual implementation but removed some lines.
...
import gql from 'graphql-tag';
import ApolloClient from 'apollo-boost';
const client = new ApolloClient({
uri: 'http://192.168.254.105:8080/graphql'
});
client.query({
query: gql`
query {
paragraphQuery {
count
entities {
entityId
...on ParagraphTradingPlatform {
fieldName
fieldAddress
}
}
}
}
`,
})
.then(data => {
console.log('dataQuery', data.data.paragraphQuery.entities) // Successfully display query contents in web console log
})
.catch(error => console.error(error));
const testRow = ({
testName = '', dataQuery // dataQuery im trying to display in the app
}) => (
<View>
<View>
<Text>{testName}</Text> // This is another REST api call.
</View>
<View>
<Text>{dataQuery}</Text>
</View>
</View>
)
testRow.propTypes = {
testName: PropTypes.string
}
class _TestSubscription extends Component {
...
render () {
return (
<View>
<FlatList
data={this.props.testList}
...
renderItem={
({ item }) => (
<testRow
testName={item.field_testNameX[0].value}
dataQuery={this.props.data.data.paragraphQuery.entities.map((dataQuery) => <key={dataQuery.entityId}>{dataQuery})} // Here I want to call the query contents but not sure how to do it
/>
)}
/>
</View>
)
}
}
const mapStateToProps = (state, ownProps) => {
return ({
testList: state.test && state.test.items,
PreferredTest: state.test && state.test.PreferredTest
})
}
...
There are few different things that are wrong there.
Syntax error is because your <key> tag is not properly closed here:
(dataQuery) => <key={dataQuery.entityId}>{dataQuery})
And... there is no <key> element for React Native. You can check at docs Components section what components are supported. Btw there is no such an element for React also.
Requesting data is async. So when you send request in render() this method finishes execution much earlier before data is returned. You just cannot do that way. What can you do instead? You should request data(in this element or its parent or Redux reducer - it does not matter) and after getting results you need to set state with .setState(if it happens inside the component) or .dispatch(if you are using Redux). This will call render() and component will be updated with data retrieved. There is additional question about displaying spinner or using other approach to let user know data is still loading. But it's orthogonal question. Just to let you know.
Even if requesting data was sync somehow(for example reading data from LocalStorage) you must not ever do this in render().This method is called much more frequently that you can expect so making anything heavy here will lead to significant performance degradation.
So having #3 and #4 in mind you should run data loading/fetching in componentDidMount(), componentDidUpdate() or as a part of handling say button click.

How can I call a React-Native navigator from an outside MeteorListView file?

This may be more a javascript question than a react-native/meteor question: I am adding Meteor connectivity to an existing React Native app, and have run into a snag with navigation. I previously had a ListView that provided an onPress function each row that would call the navigation. In keeping with Meteor's createContainer protocol, I've used (in my case) a "PuzzlesContainer" in place of the ListView that, in a separate file, refers to
const PuzzlesContainer = ({ puzzlesReady }) => {
return (
<Puzzles
puzzlesReady={puzzlesReady}
/>
);
};
export default createContainer(() => {
const handle = Meteor.subscribe('puzzles-list');
return {
puzzlesReady: handle.ready(),
};
}, PuzzlesContainer);
This file includes the "Puzzles" file, which is also a const function that contains the MeteorListView:
const Puzzles = ({ puzzlesReady }) => {
if (!puzzlesReady) {
return null;//<Loading />;
}else{
return (
<View style={launcherStyle.container}>
<MeteorListView
collection="puzzles"
renderRow={
(puzzle) =>
<View >
<TouchableHighlight style={launcherStyle.launcher} onPress={()=>onSelect(puzzle.text)}>
<Text style={launcherStyle.text}>{puzzle.text}</Text>
</TouchableHighlight>
</View>
. . .
My problem is that there is now no context for the original routing scheme, so when I call
this.props.navigator.push
it gives "undefined is not an object (evaluating 'this.props.navigator')". How can I handle this?
One way is to look at the new NavigationExperimental, which handles nagivator in a redux fashion.
Another method is, even though I do not know if this is recommended or not, to globalize the navigator component by assigning it to a module. It can be something like this
// nav.js
let nav = null
export function setNav = _nav => nav = _nav
export function getNav = () => {
if (nav) {
return nav
} else {
throw "Nav not initialized error"
}
}
Then when you first get hold of your navigator, do this
// component.js
import { Navigator } from 'react-native'
import { setNav } from './nav'
// ...
renderScene={ (route, navigator) => {
setNav(navigator)
// render scene below
// ...
}}
As much as I liked the suggestion of globalizing my navigation, a) I never managed to do it and b) it seemed like maybe not the best practice. For anyone else who might encounter this issue, I finally succeeded by passing the navigation props in each of the JSX tags--so:
<PuzzlesContainer
navigator={this.props.navigator}
id={'puzzle contents'}
/>
in the parent (react component) file, then
<Puzzles
puzzlesReady={puzzlesReady}
navigator={navigator}
id={'puzzle contents'}
/>
in the second 'const' (Meteor container) file, and using it
<TouchableHighlight onPress={()=>navigator.replace({id: 'puzzle launcher', ... })}>
in the third 'const' (MeteorListView) file. Hope it helps someone!

Categories

Resources