Undefined error when trying to map child objects in React - arrays

I'm trying to get image url for nested JSON object.
Tried {post.image.url} but I get an error saying url undefined
I appreciate any help or guidance that could be offered. New to Javascript / React but after an hour of Googling and searching couldn't come up with a solution for this. Must be missing something simple :-P
Here's my code...
export const getAllPosts = async () => {
return await fetch(
`https://notion-api.splitbee.io/v1/table/${NOTION_BLOG_ID}`
).then((res) => res.json());
}
export async function getStaticProps() {
const posts = await getAllPosts()
return {
props: {
posts
},
};
}
function Blog({ posts }) {
return (
<div>
{posts.map((post) => (
<Link href="/blog/[slug]" as={`/blog/${post.slug}`}>
<div>
<div className='text-6xl'>{post.title}</div>
<img className='w-24' src={post.imgUrl}></img>
{/*{post.image.rawUrl}*/}
</div>
</Link>
))}
</div>
);
}
export default Blog
Here's the JSON...
[
{
"id": "90ee0723-aeb5-4d64-a970-332aa8f819f6",
"slug": "first-post",
"date": "2020-04-21",
"Related to Notion API Worker (Column)": [
"0976bfa6-392a-40b0-8415-94a006dba8d9"
],
"imgUrl": "https://www.notion.so/image/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F689883de-2434-4be3-8179-a8ba62a7bc1e%2Fsnowmountain.jpg?table=block&id=90ee0723-aeb5-4d64-a970-332aa8f819f6&cache=v2",
"image": [
{
"name": "snowmountain.jpg",
"url": "https://www.notion.so/image/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F689883de-2434-4be3-8179-a8ba62a7bc1e%2Fsnowmountain.jpg?table=block&id=90ee0723-aeb5-4d64-a970-332aa8f819f6&cache=v2",
"rawUrl": "https://s3-us-west-2.amazonaws.com/secure.notion-static.com/689883de-2434-4be3-8179-a8ba62a7bc1e/snowmountain.jpg"
}
],
"title": "My first blogpost bruce"
}
]

The image property of a post is an array as denoted by the bracket on the end of line 1:
1. "image": [
2. {
3. "name": "snowmountain.jpg",
4. "url": "https://www.notion.so/image/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F689883de-2434-4be3-8179-a8ba62a7bc1e%2Fsnowmountain.jpg?table=block&id=90ee0723-aeb5-4d64-a970-332aa8f819f6&cache=v2",
5. "rawUrl": "https://s3-us-west-2.amazonaws.com/secure.notion-static.com/689883de-2434-4be3-8179-a8ba62a7bc1e/snowmountain.jpg"
6. }
7. ],
If you know that there will always be exactly one image you could access it directly by referencing the first array item.
function Blog({ posts }) {
return (
<div>
{posts.map((post) => (
<Link href="/blog/[slug]" as={`/blog/${post.slug}`}>
<div>
<div className='text-6xl'>{post.title}</div>
<img className='w-24' src={post.imgUrl}></img>
{post.image && post.image[0].rawUrl}
</div>
</Link>
))}
</div>
);
}
Note that this could err if the array is undefined / null or if it's empty, so you should probably safe-guard it by only displaying something if it exists.

There is no need to call then() method, if you use async\await.
As mdn says:
await can be put in front of any async promise-based function to pause
your code on that line until the promise fulfills, then return the
resulting value.
export const getAllPosts = async () => {
return await fetch(
`https://notion-api.splitbee.io/v1/table/${NOTION_BLOG_ID}`
);
}
export async function getStaticProps() {
const posts = await getAllPosts()
return {
props: {
posts
},
};
}

Related

dynamic URL with API paramethers

I'm new in nextjs and I need to do a dynamic URL using one API parameter
I have some like this data for the API
[
{
"categories" : {
"department": "HR"
"location": "xxxx"
},
"id": "4d2f341-k4kj6h7g-juh5v3",
},
{
"categories" : {
"department": "Operarions"
"location": "xxxx"
},
"id": "4qd3452-fsd34-jfd3453",
},
{
"categories" : {
"department": "Marketing"
"location": "xxxx"
},
"id": "4d2f341-k4kwer37g-juh5v3",
},
]
And I need to do something like that in my index.js
return(
<h1>More details</h1>
<a href="https://page.com/${id}"> link <a>
)
If someone can help me pls
Yes, will try to help you
Imagine you have a folder with the name records, so your url will be /records
In data you provided, you have several categories with ids which ones you like to have as links.(generated records dynamically)
According to nextjs docs, you can create getStaticProps page and generate static pages (it means it's very good for SEO, so use it on visible by SEO pages)
Imagine you have the next structure:
/records
[pid].tsx (or js)
index.tsx
This structure means that if you go to /records URL your index file will be triggered. And for /records/4d2f341-k4kj6h7g-juh5v3 your [pid] file will be triggered.
in index.tsx
import Link from 'next/link'
const Records= ({ records }) => {
return(
<div className="container">
<h1>Records</h1>
{records.map(record=> (
<div>
<Link href={'/records/' + record.id} key={record.id}>
<a>
{record.categories.department}
</a>
</Link>
</div>
))}
</div>
);
}
export const getServerSideProps = async () => {
//your API path here
const res = await fetch('replace_by_your_api_route');
const data = await res.json()
return {
props: {records: data}
}
}
export default Records
in your [pid].tsx
import GeneralUtils from "lib/generalUtils";
const RecordDetails= ({record}) => {
return(
<div>
{record?
<div >
{record.categories.department} {record.categories.location}
</div>
: ''}
</div>
)
}
export async function getStaticProps(context) {
const pid = context.params.pid;
//your api path to get each record where pid it is your parameter you are passssing to your api.
const res = await fetch(process.env.APIpath + '/api/public/getRecord?pid=' + (pid));
const data = await res.json();
return {
props: {record: data}
}
}
export async function getStaticPaths() {
const res = await fetch('your_api_path_same_as_in_index_tsx')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
//your id of each record, for example 4d2f341-k4kj6h7g-juh5v3
params: { pid: post.id.toString() },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
export default ProductDetails;
Output: - user types
/records - he will have list of recods with links
/records/4d2f341-k4kj6h7g-juh5v3 - he will have page with info of d2f341-k4kj6h7g-juh5v3
Check this info very carefully and this video

Create a HTML ordered list on basis of JSON response in React

I want to create a ordered html list with the JSON response values that I recieve by my backend. I have want to map on the result and per result generate a list item. But when I run this it's only showing 1. with nothing instead of:
Started flow version: 1
Started flow version: 2
I have the following react code:
const Deployment = (deployment_proces) => {
Object.values(deployment_proces).map(p => console.log(p))
const deploymentProcesItems = Object.values(deployment_proces).map(proces => (
<li key={proces.id}>
{proces.message}
</li>
));
return (
<div className='deployment-progress-container'>
<h6 className='deployment-progress-title'>Deployment progress:</h6>
<ol className='deployment-progress-list'>
{ deploymentProcesItems }
</ol>
</div>
)
}
My JSON response is as followes:
[
{
"code": 200,
"id": 2251799813688456,
"message": "Started flow version: 1",
"name": ""
},
{
"code": 200,
"id": 2251799813688479,
"message": "Started flow version: 1",
"name": ""
}
]
The deployment_proces variable is of the type object with as value the above JSON response. So I found a way in this article to convert a object to an array, because a object doesn't have a map function. And when I run the Object.values(deployment_proces).map(p => console.log(p)) it returns the same JSON as above in the console with length: 2 and proto: array(). So it is a array now (right?) and next I want to map on that array using the map function that iterates and creates a <li key={id}>{message}</li> per entry.
Any ideas how to fix this /or advice how I can do it better? Because I feel this is way to complex for a simpel iteration over a list.
EDIT
My deployment_proces variable value is equal to the return value of the DeployComponent function shown below.
const DeployComponent = async (data, basicAuth) => {
const response = await ApiCall(constants.ENDPOINTS.DEPLOY_COMPONENT, basicAuth, data);
if(!response.ok) {
throw new Error('Could not create component.');
}
return await HandleCreate(response);
}
const HandleCreate = async response => {
const result = await response.json();
if (result) {
return result;
} else {
throw Error('Message invalid response body.');
}
}
And the code where I use the <Deployment /> component:
const ButtonGroup = ({ data, deployment, basicAuth, doSetDeployment }) => {
const handleDeployment = async () => {
if (!data) {
return
}
var newDeployment = await DeployComponent(data, basicAuth);
doSetDeployment(newDeployment);
}
const isDisabled = () => {
if (Array.isArray(data) && data.length > 0) {
//now the data array has blocks check if the diagram has no errors
return DiagramHasError(data)
}
if (!data) {
return true;
}
if (Array.isArray(data) && data.length === 0) {
return true;
}
return false;
}
return (
<div className='button-group' >
{deployment ? <Deployment deployment_proces={deployment} /> : null}
<Button className={`btn ${isDisabled() ? 'disabled' : ''}`} color='green' text='Deploy diagram' onClick={() => handleDeployment()} disabled={isDisabled()} />
<Button className={`btn ${isDisabled() ? 'disabled' : ''}`} text='Save' disabled={isDisabled()} />
</div>
)
}
const mapStateToProps = state => ({
data: state.drawer.data,
deployment: state.drawer.deployment,
basicAuth: state.auth.basicAuth
})
const mapDispatchToProps = {
doSetDeployment: setDeployment
}
export default connect(mapStateToProps, mapDispatchToProps)(ButtonGroup)

star wars api, how do I get values from api?

I'm using the https://swapi.dev/api/people/ and I'm having an interesting issue with an api I haven't encountered before. Here is an example response of the first value form the api using https://swapi.dev/api/people/
[
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "http://swapi.dev/api/planets/1/",
"films": [
"http://swapi.dev/api/films/1/",
"http://swapi.dev/api/films/2/",
"http://swapi.dev/api/films/3/",
"http://swapi.dev/api/films/6/"
],
"species": [],
"vehicles": [
"http://swapi.dev/api/vehicles/14/",
"http://swapi.dev/api/vehicles/30/"
],
"starships": [
"http://swapi.dev/api/starships/12/",
"http://swapi.dev/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": "http://swapi.dev/api/people/1/"
},
]
The value I want to get is the first value of the films array, which I can achieve however its just a url. I'd need to somehow grab that url, make an api call with it and then get the first film. An example response after calling the api for the first value of films looks like this:
{
"title": "The Empire Strikes Back",
"episode_id": 5,
"opening_crawl": "It is a dark time for the\r\nRebellion. Although the Death\r\nStar has been destroyed,\r\nImperial troops have driven the\r\nRebel forces from their hidden\r\nbase and pursued them across\r\nthe galaxy.\r\n\r\nEvading the dreaded Imperial\r\nStarfleet, a group of freedom\r\nfighters led by Luke Skywalker\r\nhas established a new secret\r\nbase on the remote ice world\r\nof Hoth.\r\n\r\nThe evil lord Darth Vader,\r\nobsessed with finding young\r\nSkywalker, has dispatched\r\nthousands of remote probes into\r\nthe far reaches of space....",
"director": "Irvin Kershner",
"producer": "Gary Kurtz, Rick McCallum",
"release_date": "1980-05-17",
}
I've never used an api that has urls as values, any ideas on how this might work?
Here is my current code:
Page.jsx
import React, {useState, useEffect} from 'react';
import '../css/Page.css';
import axios from 'axios';
const Page = () => {
const [data, setData] = useState([]);
const fetchData = async () => {
try {
const res = await axios.get(
"https://swapi.dev/api/people/"
);
setData(res?.data.results);
} catch (error) {
console.log(error);
}
}
useEffect(() => {
fetchData();
}, [])
const mapData = () => {
if(data && data.length > 0) {
return (
<>
{data.map(item => (
<div className="itemContainer" key={item.name}>
<div className="image"><img src="" alt=""/></div>
<div className="content">
<span className="title">Name: </span><span className="name">{item.name}</span>
<br />
<span className="title">Birthyear: </span><span className="name">{item.birth_year}</span>
<br />
<span className="title">Homeworld: </span><span className="name">{item.homeworld}</span>
</div>
</div>
))}
</>
)
}
}
return (
<div className="container">
{mapData()}
</div>
);
}
export default Page;

Mapping API response to JSX

I have a JSON response in the form:
[
{
"author": 2,
"title": "how to draw",
"slug": "how-to-draw",
"content": "second attempt",
"status": 0,
"date_created": "2020-11-28T20:25:41.650172Z",
"date_posted": "2020-11-28T20:25:41.650172Z",
"tags": "none"
},
{
"author": 1,
"title": "Admin test post",
"slug": "admin-test-post",
"content": "This is just a test of the content field!\r\n\r\nParagraph 2",
"status": 4,
"date_created": "2020-11-16T21:02:02.521705Z",
"date_posted": "2020-11-16T21:37:40.477318Z",
"tags": "a b c"
}
]
And I am struggling to map the response correctly to a series of divs in ReactJS. The idea would be to have a div for each post, each with the author, title, etc. of the post. I can use post[0].title to take the title of the first element of the array. Am I missing something obvious?
import React, {useState} from 'react';
import axios from 'axios';
const Django = () => {
const [posts, setPosts] = useState([]);
// proxy set to http://127.0.0.1:8000/
const apiLink = "/api/post/";
const fetchData = async () => {
const res = await axios.get(`${apiLink}`, { headers: { Accept: "application/json"} });
console.log(res.data);
setPosts([res.data]);
}
return (
<div>
<h1>Posts:</h1>
<button onClick={fetchData}>Load jokes</button>
{posts.map((post, key) => {
//console.log(joke);
return (
<div className="data" key={key}>
{post.title}
{key}
</div>
)})}
</div>
);
}
export default Django;
It is not exactly clear what your problem is, but I suppose you're confused about why you aren't getting any data by directly calling {post.title} in the map body. That's because you're putting the result from your fetch, which is already an array into another array: setPosts([res.data]);. Just change the state setter to setPosts(res.data); and everything in the map should work fine.

how do I Mock API and following the same approach as my static array?

I have a React hooks component, which uses an HTML div-alike table to render data, and the data is being fetched from the server. I need to test the component to see if the table has data by mocking the API call. Below is the current code. I want to remove the use of arr completely and make avoid getting {users} rendered as text/plain as you can you in the below image
const arr = [
{
"demo": [
{
"_id": "T0810",
"title": "Historian",
"tags": [
"demo"
],
"queries": [],
},
{
"_id": "T0817",
"title": "book",
"tags": [
"demo"
],
"queries": [],
},
],
"demo_2": [
{
"_id": "T0875",
"title": "Program",
"tags": [
"demo_2",
"Control"
],
"queries": [],
},
{
"_id": "T0807",
"title": "Interface",
"tags": [
"demo_2"
],
"queries": [],
}
]
}];
const keys = Object.keys(arr[0]);
export default function Demo () {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const handleOnClick = async () => {
try {
const { data } = await axios.get('https://run.mocky.io/v3/0d7aa6e3-fc01-4a47-893d-7e1cc3013d4e');
setUsers(data);
// Now that the data has been fetched, open the modal
setModalIsOpen(true);
} catch (err) {
console.error("failed", err);
}
};
return (
<div className="container">
<>
{keys.map((key) => (
<div className="col" key={key}>
<div className="row">{key}</div>
{arr[0][key].map((item) => (
<div className="row" key={item.technique_id} onClick={() => handleOnClick(item)}>{item.technique}</div>
))}
</div>
))}
</>
{isModalOpen && <Modal onRequestClose={() => setModalIsOpen(false)} data={users}/>}
</div>
);
}
Second attempt...Assuming I understand the ask then a package like Nock can do the trick (https://github.com/nock/nock).
The approach is to allow Nock to mock the API and return the result locally without calling the server. In your case it will be something like:
const nock = require('nock');
const arr = [...]; // your original definition of arr
const scope = nock('https://run.mocky.io')
.get('/v3/0d7aa6e3-fc01-4a47-893d-7e1cc3013d4e')
.reply(200, arr);
// The rest of your code "as is"
...
This setup of Nock will intercept every HTTP call to https://run.mocky.io and will specifically return the content of arr in response to a GET /v3/0d7aa6e3-fc01-4a47-893d-7e1cc3013d4e.
Typically, I would not put this code in the main code.
Instead you can use it as part of a testing script that runs without dependency on an active server. E.g. testing during build.

Resources