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)]);
}
Related
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()
}
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.
Code Snippet:
let data ={};
zlFetch('http://localhost:3000/new.json')
.then(response => handleget(response))
.catch(error => console.log(error));
function handleget(response)
{
data = response.body;
}
export default data
Now i would like to export this data so that i can import the same in other code.
Now I know that since resolving the promise is asynchronous the data will be {}.
But if i try to export from inside the handleget function then i get an error saying import export should be in top level of the document.
So how can i extract the data from a fetch response and store it in a variable outside the then() scope and then export it else where
You can't.
It is asynchronous.
The export of the value of data happens before the response has been received.
Assign the promise returned from catch to data instead.
Then deal with the promise after you import it.
const data = zlFetch('http://localhost:3000/new.json')
.then(response => resonse.body)
.catch(error => console.log(error));
export default data;
Later
import newthing from './myModule';
newthing.then(data => ...);
Using the answer provided by Quentin, here is how my final code looks like along with error handling from fetch request
//Logic.js
const data = zlFetch('http://localhost:3000/new.json')
.then(response => response)
.catch(error => console.log(error));
export default data
//App.js
import data from "./Logic.js";
async function fetch_this_data()
{
const res = await data.then(result => result);
if (res.status == 200)
{
console.log("res"+JSON.stringify(res.body));
return res.body;
}
else
{
console.log("resu"+JSON.stringify(res));
return "Error!! Status "+res.status;
}
}
fetch_this_data();
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)})
},[])
I encountered this problem recently when I tried to use two different versions of an API. The logic is if v2 of the API gives me a 404 error, then I would try v1, if no errors, I would use the results from v2
Here is my attempt: I created two separate async thunk actions for each version and then create a async thunk where I dispatch both actions.
export const getV2LoggingOptions = createAsyncThunk(
"settings/getV2LoggingOptions",
async () => {
return sdkClient.getV2LoggingOptions().promise();
}
);
export const getV1LoggingOptions = createAsyncThunk(
"settings/getV1LoggingOptions",
async () => {
return sdkClient.getV1LoggingOptions().promise();
}
);
export const getLoggingOptions = createAsyncThunk(
"settings/getLoggingOptions",
async (arg, thunkApi) => {
let response = await thunkApi.dispatch(getV2LoggingOptions());
if (response.error) {
if (
response.error.statusCode === "404"
) {
response = await thunkApi.dispatch(getV1LoggingOptions());
}
throw response.error;
}
return response.payload;
}
);
I think this approach works. but not sure if this is the best way of doing it. Right now there are a couple issues with this approach:
I don't know how I can properly type this response as in let response = await thunkApi.dispatch(getV2LoggingOptions());.
also, the error property inside of response(if v2 call failed) doesn't contain a statusCode property. so I cannot read it. This is really confusing to me as to why it doesn't contain the statusCode
Then another approach would be just create one async thunk and call two versions inside directly
export const getLoggingOptions = createAsyncThunk(
"settings/getLoggingOptions",
async () => {
let response;
try {
response = await sdkClient.getV2LoggingOptions().promise();
} catch (error) {
if (
error.statusCode === "404"
) {
response = await sdkClient.getV1LoggingOptions().promise();
}
throw error;
}
return response;
}
);
It seems to be working too. but the issues are, still I am not sure how to type the response here.
The API does offer typing for its response. GetV1LoggingOptionsResponse and GetV2LoggingOptionsResponse. but I am not sure should I type the response as
let response: GetV1LoggingOptionsResponse | GetV2LoggingOptionsResponse
since then I can only read the overlapped part of these two types from the response.
also in the second approach statusCode is missing in the error that got caught in the catch clause.
Since both apis have different return values, they should probably be different asyncThunks and you should handle their actions separately.
Also, you should do the error handling (including throwing) inside the respective asyncThunks, as that will lead to a rejected action that you can handle in the reducer.
Once you have those two asyncThunks, there is no reason for a third asyncThunk that will introduce it's own lifecycle actions that you really don't need as this is essentially just orchestration of the other two.
Write a normal thunk:
const getLogginOptions = () => async (dispatch) => {
let result = await dispatch(getV2LoggingOptions());
if (getV2LoggingOptions.rejected.match(result)) {
result = await dispatch(getV1LoggingOptions());
}
return result;
}