dangerouslySetInnerHTML getting data from S3 bucket object in Serverless React - javascript

My goal is to read an HTML document from an S3 Bucket, and display it in the browser using Serverless React. The document contains HTML that should be rendered in the browser. Previous research seems to show that I need to use "dangerouslySetInnerHTML", so I'm trying that.
React code in App.tsx:
import "./App.css";
import * as React from "react";
import useConfig from "./components/useConfig";
import logo from "./logo.svg";
import logoPsych from "./psych_mind_logo.jpg"
/**
* Our Web Application
*/
export default function App() {
const config = useConfig();
function createPageBodyTest() {
let myHtml = 'First ยท Second';
return {__html: myHtml};
}
async function createPageBody() {
const AWS = require('aws-sdk');
const params = {
Bucket: 'mybucket',
Key: 'MyFile1.html'
}
let s3 = new AWS.S3();
/*
let pageBody = async function() {
// get s3 file and create stream
const contentsOfS3Item = s3.getObject(params).createReadStream();
return contentsOfS3Item;
}
*/
const response = await s3.getObject(params).promise() // await the promise
const fileContent = response.Body.toString('utf-8');
return {__html: fileContent};
}
return (
<div className="App">
<header className="App-header">
<img src={logoPsych} className="App-logo" alt="logo" />
<h1 className="App-title">CognitivePsychology.com</h1>
</header>
<p className="App-intro">
<div dangerouslySetInnerHTML={createPageBody()} />;
</p>
</div>
);
}
If I put createPageBodyTest instead of createPageBody I don't get any error in VSCode, but as the code is above, I see this (with the mouse-over error displayed). I'm guessing it is because if have an async function.
If I ignore that, and deploy it anyway, the server side refers to this url: https://reactjs.org/docs/error-decoder.html/?invariant=61

I believe this because you are returning a promise. I think it can be solved by inputting a placeholder and then exchanging that with what you actually want there once the promise has been resolved. You could do this with states:
export default function App() {
const [html, setHtml] = React.useState({ __html: '<div>Placeholder text</div>'})
async function createPageBody() {
// AWS stuff
// Resolve the promise
setHtml({ __html: resolvedPromise })
}
return (
<div className='App'>
<header className='App-header'>
<img src={logoPsych} className='App-logo' alt='logo' />
<h1 className='App-title'>CognitivePsychology.com</h1>
</header>
<p className='App-intro'>
<div dangerouslySetInnerHTML={html} />;
</p>
</div>
);
If you are using TypeScript replace the useState for:
const [html, setHtml] = React.useState<{ __html: string}>({ __html: '<div>Placeholder text</div>'})
You could also do the same with a separate component for handling AWS stuff:
App.?sx
export default function App() {
return (
<div className='App'>
<header className='App-header'>
<img src={logoPsych} className='App-logo' alt='logo' />
<h1 className='App-title'>CognitivePsychology.com</h1>
</header>
<p className='App-intro'>
<getBlog />;
</p>
</div>
);
getBlog.?sx
export default function getBlog() {
const [html, setHtml] = React.useState({ __html: '<div>Placeholder text</div>'})
async function createPageBody() {
// AWS stuff
// Resolve the promise
setHtml({ __html: resolvedPromise })
}
return <div dangerouslySetInnerHTML={html} />;
}
I have no experience using AWS but I believe you should be able to handle your promise doing something like:
async function createPageBody() {
const AWS = require('aws-sdk');
const params = {
Bucket: 'mybucket',
Key: 'MyFile1.html'
}
let s3 = new AWS.S3();
const response = await s3.getObject(params).promise() // await the promise
response.then((data) => {
setHtml({ __html: data.data.you.want.to.react })
}, (error) => {
// Handle error
}
}

Related

Problem getting API images to display in NextJS App

This is my first time using NextJS and I'm trying to load 3 random dog breed images onto the app's webpage using the Dog.ceo API. I am able to see the three random dogs in the console from the console.log(data) line, but the images aren't being displayed. In this API there are only two properties - message (containing the image URL) and status (displaying 'success'). Any help in how to get these images to display? Also to note, I'm not using Typescript for this.
const defaultEndpoint = "https://dog.ceo/api/breeds/image/random/3";
export async function getServerSideProps() {
const res = await fetch(defaultEndpoint);
const data = await res.json();
return {
props: { data },
};
}
export default function Home({ data }) {
console.log("data", data);
const { results = [] } = data;
return (
<div className={styles.container}>
<Head>
<title>Dog Breed App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<div className="grid">
{results.map((result) => {
const { message } = result;
return (
<div key={message}>
<img src={message} alt=""></img>
</div>
);
})}
</div>
</main>
</div>
);
}
I tried using "message" from the "data" variable to get the url for the image. But that isn't working.
It's just a destructuring error. You have const { results = [] } = data;.
That line says: Find the property in my data object called results and if it doesn't exist, set it to an empty array. Your data object doesn't have a property called results. It has a property called message.
You could change this line to const { message = [] } = data and then just loop over the message array or you could just store the message array in the props.data property like this:
export async function getServerSideProps() {
const res = await fetch('https://dog.ceo/api/breeds/image/random/3');
// Destructure the response object here and
// rename the 'message' property as 'data'
const { message: data } = await res.json();
return {
props: { data },
};
}
// Destructure the props object to have access to the
// property named data:
export default function Home({ data }) {
return (
<main>
<div className="grid">
{data.map((img) => (
<div key={img}>
<img src={img} alt="dog"></img>
</div>
))}
</div>
</main>
);
}
you can use useEffect hook to load data and update to a state dogs. This will update render once on component creation.
const defaultEndpoint = "https://dog.ceo/api/breeds/image/random/3";
import React, { useState, useEffect } from 'react'
export default function Home({ data }) {
const [dogs, setDogs] = useState([]);
export async function getServerSideProps() {
const res = await fetch(defaultEndpoint);
const data = await res.json();
console.log("data", data);
setDogs(data)
}
useEffect(() => {
getServerSideProps()
}, [])
return (
<div className={styles.container}>
<Head>
<title>Dog Breed App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<div className="grid">
{dogs.map((result) => {
const { message } = result;
return (
<div key={message}>
<img src={message} alt=""></img>
</div>
);
})}
</div>
</main>
</div>
);
}
I'll suggest using [dependencies] in useEffect to control when it re-renders it like below
useEffect(() => {
//
return () => {
//
}
}, [dependencies])

How to use slug url in nextjs

I am working in nextjs, i am trying to make "dynamic routes",
i want after click my url should be like "myurl.com/article/55"
for this i use following "link tag"
<Link href={{pathname: "article/[id]",query: { id: post.id },}}>
<a className="rdmre-btn"> Read More</a>
</Link>
And here is my code in ("pages/article/[slug].js) in file,Where i am wrong ? i want whenever i click on any blog then blog details page should open.
import Axios from "axios";
import { useRouter } from "next/router";
import Link from "next/link";
import LatestBlogs from "../../components/LatestBlogs/LatestBlogs";
const Post = ({ post }) => {
const router = useRouter();
const htmlString = post.description_front;
if (router.isFallback) {
return <div>Loading...</div>;
}
return (
<>
<header className="bgbanner blog_header">
<div className="container cont">
<div className="header">
</div>
</div>
<div className="container "></div>
</header>
<section>
<div className="container Blog_page_sidebar">
<div className="blog_details">
<div className="blog_image">
<img src={post.image} />
</div>
<div className="blog_heading">
<h2>{post.title}</h2>
</div>
<div className="blog_detail">
<div
className="product-des"
dangerouslySetInnerHTML={{ __html: htmlString }}
/>
</div>
</div>
</div>
</section>
</>
);
};
export default Post;
export const getStaticProps = async ({ params }) => {
const { data } = await Axios.get(
`https://myurl.com/api/blogbyids/${params.id}`
);
const post = data;
return {
props: {
post,
},
};
};
export const getStaticPaths = async () => {
const { data } = await Axios.get(
"myurl.com/admin-panel/api/blogs"
);
const posts = data.slice(0, 10);
const paths = posts.map((post) => ({ params: { id: post.id.toString() } }));
return {
paths,
fallback: true,
};
};
[slug] is used to have nested routes. But correct is [...slug].js (info)
Example: myurl.com/article/[id]/[otherid]
In the example above we can see that in [id] can be nested children. You can name this param as you want.
If you want to have your structure as myurl.com/article/55, you need to have structure as follow:
In your pages folder:
You create a folder article (pages/article)
You create 2 files: index.js (or .tsx) and [id].js (you can name as [slug].js or [specialId].js - no matter the name
After, you are getting info with param name created.
Here is example of the code (URL: myurl.com/article/55; file: pages/article/[pid].js)
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
//same name as name of your file, can be [slug].js; [specialId].js - any name you want
const { pid } = router.query
//result will be '55' (string)
return <p>Post: {pid}</p>
}
export default Post

getStaticPaths is required for dynamic SSG

I am new to Next.js and
I've been trying to to use getStaticProps in my dynamic pages in my Next.js app
and I get this error:
Error: getStaticPaths is required for dynamic SSG pages and is missing
for '/query/[itmid]'
[itmid].jsx
function Query({ posts }) {
return (
{posts.map((itm, k) => {
return (
<>
<Head>
<title> {itm.Name} - wixten </title>
</Head>
<div key={itm._id} className="Question-one">
<h1> {itm.Name}</h1>
<h2>{itm.Summary}</h2>
</div>
<div className="username">
<span className="username2">--{itm.username}</span>
</div>
</>
);
})}
</>
<div className="multi-container">
<Answershooks id={gotid} />
<RealtedPost />
</div>
</>
);
}
export async function getStaticProps() {
const res = await fetch("https://ask-over.herokuapp.com/questone/" + gotid);
console.log("check");
console.log("dada");
const posts = await res.json();
return {
props: {
posts,
},
};
}
export default Query;
Why am I getting this error?
What getStaticProps does is to generate the static page, but you need to tell next js, what are the paths to generate?
export async function getStaticPaths() {
return {
paths: [
{ params: { query: 'slug-1' }},
{ params: { query: 'slug-2' }}
],
fallback: true // false or 'blocking'
};
}
Then in your getStaticProp
export async function getStaticProps({ params }) {
//params.query will return slug-1 and slug-2
const res = await fetch("https://ask-over.herokuapp.com/questone/" + params.query);
console.log("check");
console.log("dada");
const posts = await res.json();
return {
props: {
posts,
},
};
}
You need to use params.query if you name your file [query].js.
The above codes will generate static paths /slug-1 and /slug-1.
If you are not trying to generate static pages (which seems like it), then you should probably use getServerSideProps which generates page on the go.

Next-JS Beginner using two dynamic APIs, one works, the other doesn't

My friend helped me to rewrite my crappy JS web app to a next-app, but upon trying to continue, I'm running into roadblocks and much confusion.
I have built two APIs that return objects:
import fetch from "isomorphic-fetch";
import cheerio from "cheerio";
export const getData = async (player) => {
const req = await fetch('blahblahblah');
...
return teams;
};
module.exports = {
getData,
};
and another that returns teamStats
I have 2 endpoints:
[id].js - and [tid].js
import { getData } from "../../../utils/api";
export default async (req, res) => {
const details = await getData(req.query.id);
res.status(200).json(details);
};
import { getStats } from "../../../utils/squadapi";
export default async (req, res) => {
const details = await getStats(req.query.tid);
res.status(200).json(details);
};
two components (this one works fine):
import React from "react"; // react dependencies
const Teams = ({ teams }) => {
return teams.map((team, index) => (
<React.Fragment key={index}>
<br />{" "}
<div class="container">
{team.map((pokemon) => (
<React.Fragment key={pokemon.pokemon}>
<br /> <div class="bout">{pokemon.bout}</div>
<div class="child">
<img src={pokemon.sprite} />
<p>{pokemon.pokemon}</p>
</div>
</React.Fragment>
))}
</div>
<br />
</React.Fragment>
));
};
export default Teams;
this one doesn't work (teamStats is undefined)
import React from "react";
const Squads = ({ teamStats }) => {
return (
<React.Fragment>
<img src={teamStats.logo} />
</React.Fragment>
);
};
export default Squads;
why is my second component returning undefined? i did my best to replicate the steps my friend took to create the first component which works fine... second one errors 'teamStats' is undefined.
edit: directory structure is:
Pages > api > player > [id].js Pages > api > squad > [tid].js
edit: index.js:
import Head from "next/head";
import React, { useState } from "react";
import Teams from "../components/Teams";
import styles from "../../styles/Home.module.css";
import Squads from "../components/Squads";
export default function Home() {
const [teams, setTeams] = useState([]);
const [player, setPlayer] = useState("Player Name");
const [loading, setLoading] = useState(false);
const [squad, setSquad] = useState("9a7059e278");
const loadSquad = async () => {
setLoading(true);
const req = await fetch(`/api/squad/${squad}`);
const json = await req.json();
setSquad(json);
setLoading(false);
};
const loadPeople = async () => {
setLoading(true);
const req = await fetch(`/api/player/${player}`);
const json = await req.json();
setTeams(json);
setLoading(false);
};
return (
<div className={styles.main}>
<Head>
<title>Liam</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1>Silph Team Finder</h1>
<br />
<div>
<select
value={squad}
onChange={(e) => setSquad(e.currentTarget.value)}
>
<option value="9a7059e278">Stoked</option>
</select>
<button onClick={() => loadSquad()}>Load</button>
{loading && <div className={styles.load}>LOADING</div>}
<Squads />
<input value={player} onChange={(e) => setPlayer(e.target.value)} />
<button onClick={() => loadPeople()}>Load</button>
{loading && <div className={styles.load}>LOADING</div>}
</div>
<div className={styles.teams}>
<Teams teams={teams} />
</div>
</main>
</div>
);
}
Credit goes to #juliomalves & #calvin
my jsx/component was missing props:
<Squads teamStats={squads} />

My function is returning a promise while trying to render an image

I am trying to render images using the TMDB api. In my database i have the IMDB id's. TMDB api has a method that return a poster_path(image) for a given IMDB id.
Here is my code:
const IMAGE_API="https://image.tmdb.org/t/p/original/";
const popular =(props)=> {
async function imdbIdFind(imdb_id) {
var result = await ApiService.findAll(imdb_id);
console.log(result);
return result.data.movie_results[0].poster_path;
}
return (
props.popularMovies.map((movie, index) => {
return (
<Card style={{width: '18rem'}}>
<Card.Img variant="top" src={IMAGE_API + imdbIdFind(movie.movieId)}/>
<Card.Body>
<Card.Title>{movie.originalTitle}</Card.Title>
</Card.Body>
</Card>
)
})
)
}
export default popular;
Here is the ApiService findAll method
const ApiService ={
findAll:(id)=>
axios
.get(https://api.themoviedb.org/3/find/${id},{
params:{
api_key:API_KEY,
external_source:"imdb_id"
}
})
The parent of the popular component is the home component:
render() {
return(
<div className={"row "}>
<div className={"col-md-12"}>
<h3 className={"upcoming-movies"}>Popular movies</h3>
</div>
<div className={"col-md-12"}>
<PopularMovies popularMovies={this.state.popularMovies}/>
</div>
</div>
)}
The error i am getting is this :
GET https://image.tmdb.org/t/p/original/[object%20Promise] 404
Thanks in advance
Problem
The components are rendered before the api call is resolved and the function returns a Promise, to get the value you need to use then block or async/await. It is not recommended to call async function from render.
Solution 1: Get all the paths
You can store the paths in the component state:
const [paths, setPath] = useState([]);
useEffect(() => {
const getPaths = async ()=> {
const result = await Promise.all(props.popularMovies.map(movie => imdbIdFind(movie.movieId)));
setPath(...result);
}
getPaths();
}, []);
async function imdbIdFind(imdb_id) {
var result = await ApiService.findAll(imdb_id);
return result.data.movie_results[0].poster_path;
}
Then:
<Card.Img variant="top" src={IMAGE_API + paths[index]}/>
Solution 2: Create a component to render the image
You can store the path in the component state:
const CardImage = ({movieId, ...props}) => {
const [path, setPath] = useState([]);
useEffect(() => {
imdbIdFind();
}, []);
async function imdbIdFind() {
var result = await ApiService.findAll(movieId);
setPath(result.data.movie_results[0].poster_path);
}
return (<Card.Img src={IMAGE_API + path} {...props}/>)
}
Then render the CardImage component:
<Card style={{width: '18rem'}}>
<CardImage variant="top" movie={movie}/>
...
</Card>

Categories