Problem rendering fetched data with reactjs and firebase - javascript

I'm trying to fetch data that contains a JSON array that looks like this
[
{
"prof": "Jason Crank",
"views": "2",
//etc etc
}
]
and I'm trying to transform the JSON object into an JavaScript array I can map and render the appropriate amount of blocks, I'm using typescript and this is my code
const [notesArr, setNotesArr] = useState<notesInterface[]>([])
const fetchNotes = async (active) => {
try{
const response = await fetch("my server", {
method:"GET",
headers: {'Content-Type': 'application/json'}
})
.then(resp => JSON.parse(JSON.stringify(resp)))
.then(result => setNotesArr(result))
return(
<div>
{notesArr.map((notes) => <NoteThumb link={notes.link} title={notes.title} semester={notes.semester} prof={notes.prof} timestamp={notes.timestamp} likes={notes.likes} views={notes.views} pages={notes.pages} isBookmarked={notes.isBookmarked}/>)}
</div>
)
}catch(error){
console.log(error)
}
}
and the interface I'm using looks like this
interface notesInterface {
prof: string
views: number
}
When trying this I get an error that says Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
I'd appreciate any help and I can offer more snippets of my code if necessary

To summarise what you're being told in the comments above...
Your functional component needs to return the JSX node, not your fetchNotes function
Use useEffect to execute fetchNotes on component mount
const Notes: React.FC = () => {
const [notesArr, setNotesArr] = useState<notesInterface[]>([])
useEffect(async () => {
try {
const response = await fetch("my server")
if (!response.ok) {
throw new Error(`${response.status}: ${await response.text()}`)
}
setNotesArr(await response.json())
} catch (err) {
console.error(err)
}
})
return (
<div>
{notesArr.map(note => <NoteThumb {...note} />)}
</div>
)
}
I cleaned up your fetch() call as well; GET is the default method and GET requests do not have Content-type due to there being no request body.

Related

Cannot render and map POST request array promise

I have an API called getQuote and a component called QuoteCard. Inside QuoteCard I'm trying to render an array of users that liked a quote. The API works fine, I have tested it, and the code below for getting the users works fine too.
const Post = async (url, body) => {
let res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"accept": "*/*"
},
body: JSON.stringify(body)
}).then(r => r.json());
return res;
}
const getAllLikes = async () => {
let users = await Post('api/getQuote', {
id: "639e3aff914d4c4f65418a1b"
})
return users
}
console.log(getAllLikes())
The result is working as expected :
However, when trying to map this promise result array to render it onto the page is where I have problems. I try to render like this:
<div>
{getAllLikes().map((user) => (
<p>{user}</p>
))}
</div>
However, I get an error that states:
getAllLikes(...).map is not a function
I don't understand why this is happening. Why can't I map the array? Is it because it's a promise or something?
And if anyone needs to see the getQuote API, here it is:
//Look ma I wrote an API by myself! :D
import clientPromise from "../../lib/mongodb";
const ObjectId = require('mongodb').ObjectId;
import nc from "next-connect";
const app = nc()
app.post(async function getQuote(req, res) {
const client = await clientPromise;
const db = client.db("the-quotes-place");
try {
let quote = await db.collection('quotes').findOne({
_id: new ObjectId(req.body.id)
})
res.status(200).json(JSON.parse(JSON.stringify(quote.likes.by)));
} catch (e) {
res.status(500).json({
message: "Error getting quote",
success: false
})
console.error(e);
}
})
export default app
Thanks for any help!
It is due to the fact that getAllLikes is an async function and thus it returns promise which does not have a map function.
You can either save it in a state variable before using await Or chain it with .then.
Minimal reproducible example which works
const getAllLikes = async () => {
return ['a', 'b']
}
getAllLikes().then((r) => r.map((g) => { console.log(g) }))
Edit: The above code won't work if directly used with jsx since the return of getAllLikes will still be a promise. Solution would be to save it in a state variable and then using it.
I am from Angular and I believe we call pipe on Observables (or Promises). Map can then be called inside the pipe function
observable$ = getAllLikes().pipe(map( user => <p>{user}</p>))
If there is no pipe, I can only think of manually subscribing (which is not a good practice)
sub$ = getAllLikes().subscribe( user => <p>{user}</p>)
// unsub from sub$ appropriately
// We do this from ngOnDestroy in angular
ngOnDestroy() {
this.sub$?.unsubscribe()
}

How to get data from two different api routes in nextjs?

I'm using Next.js and I need to get data from two different API routes. I want to fetch the data in getServerSideProps.
The first data I need is from the http://localhost:3000/api/admin/classes/${className} route.
The second set of data will be from http://localhost:3000/api/admin/classes/${className}/subjects this route.
When I try to get data from just a single API, it works fine. I tried to fetch the data from both API using the code in getServerSideProps. But it doesn't work.
I want to have the data like this export default function classPage({ subjects, classDetail }) {}. The return props from gerServerSideProps should look like this: return { props: {classDetail: data, subjects: data2} }, if it's possible
export async function getServerSideProps({ query: { className } }) {
const res = await fetch(
`http://localhost:3000/api/admin/classes/${className}`
).then(() => {
const res2 = await fetch(`http://localhost:3000/api/classes/${className}/subjects`)
});
const { data } = await res.json();
const {data2} = await res2.json()
return { props: { classDetail: data } };
}
Api get request code:
try {
const subjectDetail = await Subject.find({}).populate('classDetail')
res.status(200).json({success: true, data: subjectDetail})
} catch (error) {
res.status(400).json({success: false})
console.log(error)
}
You can do it much simpler, I assume that you don't need to wait for the first request to end to starts the second so you can simply use Promise.all to wait for both requests to finish.
export async function getServerSideProps({ query: { className } }) {
// Create the promises for the data we need to fetch
const promises = [
fetch(`http://localhost:3000/api/admin/classes/${className}`).then(res => res.json()),
fetch(`http://localhost:3000/api/classes/${className}/subjects`).then(res => res.json()),
];
// Wait for all the promises to resolve and get the data
const [classDetail, subjects] = (await Promise.all(promises)).map(p => p.data);
return { props: { classDetail, subjects } };
}
But the problem that you seem to have with the second request is that when you write: const {data2} = await res2.json(), you are trying to get the attribute data2 from the response which is probably not what you want. You need to get data from both responses as I did here.

Using FetchMock to mock fetch not working

Im trying to mock a fetch request to confirm if the right actions were dispatched:
the redux function:
export function loginRequest(email, password) {
return (dispatch) => {
dispatch(login(email, password))
return fetch('http://localhost:8000/login-success.json')
.then((response) => response.json())
.then((response) => dispatch(loginSuccess()))
.catch((error)=>dispatch(loginFailure()))
}
}
the test:
test("API returns the right response, the store received two actions LOGIN and LOGGING_SUCCESS", () => {
const store = mockStore({})
fetchMock.get('*', {})
return store.dispatch(loginRequest('jack', 124)).then(()=>{
const actions = store.getActions()
console.log(actions)
expect(actions).toEqual([login('jack', 124), loginSuccess()])
})
})
the console.log output:
[
{ type: 'LOGIN', user: { email: 'jack', password: 124 } },
{ type: 'LOGIN_FAILURE' }
]
im expecting the second action to be LOGIN_SUCCESS action instead. it seems like the mock isnt working at all. Am i missing something here
SOLVED: in the file where i defined the loginRequest function, i was importing fetch (import fetch from 'node-fetch';) hence calling the function in the test resulted in an actual fetch call rather than fetch-mock

Trouble with fetch error handling in Reactjs

I'm learning how to use fetch and was trying the following syntax:
const [stuff, setStuff] = useState([]);
const request = "link-to-API";
const data = await fetch(request)
.then(response => response.json())
.catch(err => {
console.log(err);
return {} //(or [], or an empty return, or any return at all)
})
setStuff(data.hits)
Then, in the return, I have:
{stuff.map((element) => (
<Thing
title={element.label}
link={element.url}
/>
))}
Thinking I could just render an empty object whenever my fetch fails. Except, this works only when the fetch itself works. React gives me the error
"Objects are not valid as a React child (found: TypeError: Failed to
fetch)."
But I can't find any solution online. How could I handle the errors just by not rendering anything?
(that's not the only part I'm rendering, I just want to render an empty div, not conditionally render that part)
when you use await you can't use then and catch methods
It's important that you use await in async function
let data = null
try{
const response = await fetch(request)
data = response.json();
} catch(err) {
console.log(err);
}
you can try removing the await keyword, as you are using .then
also the datafetching part should be included inside useEffect
const [stuff, setStuff] = useState([]);
const request = "link-to-API";
useEffect( ()=> {
fetch(request)
.then(response => response.json())
.then(data => setStuff(data.hits))
.catch(err => {console.log(err)})
},[])

Access json nested with react

How are you? I created some more levels in Json that comes from Mongo, however, when accessing elements with more than two levels of nesting, when they can not read the undefined property.
Is there a limitation to how much data can be accessed in a Json? When access by console.log outside of render (), the same, but within the render method, goes two levels even.
Thanks for the help.
I use the redux-saga to search the api:
import { all, takeEvery, put, call } from 'redux-saga/effects';
import actions from './actions';
const api = 'http://localhost:3003/api/estado/infraestrutura/habitacao';
function* getData() {
try {
const data = yield call (
async () =>
await fetch(api)
.then(res => res.json())
.then(res => res)
.catch(error => error)
)
yield put({
type: actions.INPUT_DATA,
data
})
}
catch (error) {
console.log(error)
}
}
export default function* rootSaga() {
yield all([takeEvery(actions.GET_DATA, getData)]);
}

Categories