Can not store json obj in state when i use useEffect to get an object from an API - json

Im trying to show an object's properties on a modal, but nothing seems to happen after i fetch it. I've tried without using the useEffect hook, and it does store the item but then i cant access the properties, i asked about it, and a user told me to use use Effect. But now, nothing seems to be stored...
This is my code:
import React, {useState, useEffect } from 'react';
const Modal = ({ handleClose, show, id }) => {
const showHideClassName = show ? "mod displayBlock" : "mod displayNone";
const [peliSeleccionada, setPeli] = useState([]);
useEffect(() => {
fetch(`http://localhost/APIpeliculas/api/pelicula/read_single.php/?ID=${id}`)
.then(res => res.json())
.then(
result => {
alert(result); //the alerts dont even pop up
setPeli(result);
alert(peliSeleccionada);
});
}, []);
return (
<div className={showHideClassName}>
<section className="mod-main">
<h5>EDITAR: </h5>
<label>
{ peliSeleccionada.Nombre }
</label>
<div className="btn-grupo">
<button type="button" className="btn btn-success btn-lg btn-block">Guardar cambios</button>
<button onClick={handleClose} type="button" className="btn btn-secondary btn-lg btn-block">Cerrar</button>
</div>
</section>
</div>
);
};
export default Modal;
The alerts i put inside my useEffect function dont even pop up, and i also get this error on the console as soon as i enter the page:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
Also I want to access my object's properties, which are: ID, Nombre, Categoria, and Director. Is this the correct way to do it? { peliSeleccionada.Nombre }

useEffect is run after the component renders, similarly to how componentDidMount works.
What this means, putting it very simply, is that the component will return and then fire the fetch.
There is an issue with your peliSeleccionada state, you declare it as an array but call peliSeleccionada.Nombre like if it was an object. This means that on first render it will print undefined for the peliSeleccionada.Nombre.
An approach to have this is to pair it with a loading state.
const Modal = () => {
const [loaded, setLoading] = useState(true);
const [peliSeleccionada, setPeli] = useState({});
useEffect( () => {
fetch()
.then(res => {
setPeli(res) // parse this accordingly
}) // or chain of thens if needed
.catch(err => {
setLoading(false);
console.log(err) // something failed in the fetch. You could have another state to mark that the fetching failed; close the modal, show an error, etc.
})
}, [])
if(!loaded) return 'Loading...'
return (<div>Data goes here</div>) // be careful if the fetch failed, it won't show data!
}
Last but not least, the error is happening in the fetch and the message states exactly that, that you are not catching it. If it is in the fetch call, the above code works. If it is in a parsing, it might require a try/catch around the useEffect

Related

Taken two pages back since render function is called twice

I want to go back to the previous page when Apollo Client error.graphQLErrors has an error with a specific message from the back-end server,
Below is the snippet of my code.
const Detail = () => { const { snackbar } = useSnackbar();
const history = useHistory();
return(
<Compo query={graphQLQuery}>
{({ data, error, }) => {
if(error?.graphQLErrors[0]?.extensions?.debugMessage.includes('Specific Error')){
history.goBack();
snackbar('Specific Error');
return <></>;
}
else{
//render another component
}
}
}
</Compo>);
Issue is since the render is called twice, when the error happens, history.goBack() is executed twice and I'm taken two pages back.
I'm able to avoid this by removing <React.StrictMode> encapsulating <App> component.
Is there a better way to do this?
I'm trying to avoid removing <React.StrictMode> since it's been there since a long time.
Issue
The issue here is that you are issuing an unintentional side-effect from the render method. In React function components the entire function body is considered to be the "render" method. Move all side-effects into a useEffect hook.
Solution
Since the code is using a children function prop you'll need to abstract what the "child" is rendering into a React component that can use React hooks.
Example:
const DetailChild = ({ data, error }) => {
const history = useHistory();
const { snackbar } = useSnackbar();
const isErrorCondition = error?.graphQLErrors[0]?.extensions?.debugMessage.includes('Specific Error'
useEffect(() => {
if (isErrorCondition)) {
history.goBack();
snackbar('Specific Error');
}
}, [error]);
return isErrorCondition
? null
: (
... render another component ...
);
};
...
const Detail = () => {
return (
<Compo query={graphQLQuery}>
{({ data, error }) => <DetailChild {...{ data, error }} />}
</Compo>
);
};

TypeScript - Navigate dropdown listitems using keyboard

I'm working on an open-source project and have encountered a bug. I'm not able to navigate the dropdown list items using the keyboard (arrow key/tab). I've written the keyboard-navigation logic, but not quite sure of how to implement it. Below is the code snippet.
.
.
.
const TopNavPopoverItem: FC<ComponentProps> = ({closePopover, description, iconSize, iconType, title, to}) => {
const history = useHistory();
const handleButtonClick = (): void => {
history.push(to);
closePopover();
};
const useKeyPress = function (targetKey: any) { // where/how am I supposed to use this function?
const [keyPressed, setKeyPressed] = useState(false);
function downHandler(key: any) {
if (key === targetKey) {
setKeyPressed(true);
}
}
const upHandler = (key: any) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
React.useEffect(() => {
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
});
return keyPressed;
};
return (
<button className="TopNavPopoverItem" onClick={handleButtonClick}>
<Icon className="TopNavPopoverItem__icon" icon={iconType} size={iconSize} />
<div className="TopNavPopoverItem__right">
<span className="TopNavPopoverItem__title">{title}</span>
<span className="TopNavPopoverItem__description">{description}</span>
</div>
</button>
);
};
Any workaround or fixes?
Thanks in advance.
A custom hook should always be defined at the top level of your file. It cannot be inside of a component. The component uses the hook, but doesn't own the hook.
You have a hook which takes a key name as an argument and returns a boolean indicating whether or not that key is currently being pressed. It's the right idea, but it has some mistakes.
When you start adding better TypeScript types you'll see that the argument of your event listeners needs to be the event -- not the key. You can access the key as a property of the event.
(Note: Since we are attaching directly to the window, the event is a DOM KeyboardEvent rather than a React.KeyboardEvent synthetic event.)
Your useEffect hook should have some dependencies so that it doesn't run on every render. It depends on the targetKey. I'm writing my code in CodeSandbox where I get warnings about "exhaustive dependencies", so I'm also adding setKeyPressed as a dependency and moving the two handlers inside the useEffect.
I see that you have one handler as function and one as a const. FYI it really doesn't matter which you use in this case.
Our revised hook looks like this:
import { useState, useEffect } from "react";
export const useKeyPress = (targetKey: string) => {
const [keyPressed, setKeyPressed] = useState(false);
useEffect(
() => {
const downHandler = (event: KeyboardEvent) => {
if (event.key === targetKey) {
setKeyPressed(true);
}
};
const upHandler = (event: KeyboardEvent) => {
if (event.key === targetKey) {
setKeyPressed(false);
}
};
// attach the listeners to the window.
window.addEventListener("keydown", downHandler);
window.addEventListener("keyup", upHandler);
// remove the listeners when the component is unmounted.
return () => {
window.removeEventListener("keydown", downHandler);
window.removeEventListener("keyup", upHandler);
};
},
// re-run the effect if the targetKey changes.
[targetKey, setKeyPressed]
);
return keyPressed;
};
I don't know you intend to use this hook, but here's a dummy example. We show a red box on the screen while the spacebar is pressed, and show a message otherwise.
Make sure that the key name that you use when you call the hook is the correct key name. For the spacebar it is " ".
import { useKeyPress } from "./useKeyPress";
export default function App() {
const isPressedSpace = useKeyPress(" ");
return (
<div>
{isPressedSpace ? (
<div style={{ background: "red", width: 200, height: 200 }} />
) : (
<div>Press the Spacebar to show the box.</div>
)}
</div>
);
}
CodeSandbox Link

Issue mapping an array from api fetch call

I reviewed previous posts and did not see anything that addresses this issue. I'm using a functional component/hook to fetch data from an open source api. The code works as long as I am only displaying 1 field from data.map in the return render. If I try to display more than one, I get the following error: Objects are not valid as a React child (found: object with keys {name, catchPhrase,bs}). If you meant to render a collection of children, use an array instead. DevTools says the error is in the list component. When all but one list element is commented out, I can see that the entire array is returned in DevTools, and the one list element will display. Adding an additional fields(list items) results in the error. Not sure what I'm doing wrong. Any help is appreciated.
import React, { useEffect, useState } from 'react';
function PeopleData() {
const [data, setData] = useState([]);
function GetData(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(response => setData(response))
}
console.log(data)
useEffect(() => {
GetData();
}, []);
return (
<div>
{data.map(item => <div key={item.id}>
<div>
<li>{item.name}</li>
{/* <li>{item.company}</li>
<li>{item.phone}</li>
<li>{item.email}</li> */}
</div>
</div>)}
</div>
)
}
export default PeopleData;
It's because you are trying to display company property from the response.
From the API:
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
This means that you have to display company.name, company.bs or company.catchPhrase.
Each object has the shape shown bellow, and the app is crashing because you are trying to render company which is an object, when it is expecting a string or node. So you should use item.company.name instead
{
id: 1,
name: "Leanne Graham",
...
company: {
name: "Romaguera-Crona",
catchPhrase: "Multi-layered client-server neural-net",
bs: "harness real-time e-markets",
},
};
You can also take advantage of the await/async syntax instead of chaining together then statements like bellow. The execution of the line is promised before the beginning of the next line by means of the await operator
async function GetData() {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const json = await response.json();
setData(json);
}
I have this working in a sandbox here https://codesandbox.io/s/throbbing-butterfly-dt2yy?file=/src/App.js
The company property is an object and an object can't be a child in React as it says in the error as well.
Objects are not valid as a React child (found: object with keys {name,
catchPhrase,bs})
The solution is to only show properties from the company object that you may be interested to show in the UI.
import React, { useEffect, useState } from 'react';
function PeopleData() {
const [data, setData] = useState([]);
function GetData(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(response => setData(response))
}
console.log(data)
useEffect(() => {
GetData();
}, []);
return (
<div>
{data.map(item => <div key={item.id}>
<div>
<li>{`Name: ${item.name}`}</li>
<li>{`Company: ${item.company.name}`}</li>
<li>{`Phone: ${item.phone}`}</li>
<li>{`Email: ${item.email}`}</li>
</div>
</div>)}
</div>
)
}
export default PeopleData;
Item.company is an Object, that's why React throws an Error. You have to access the attributes of company. If you want to display all attributes of Company you could built a "For In" loop and and display them all too. I can show you an example if you need clarification.
As a little side note and tip, look into the useFetch hook. You will love it, especially if you have multiple components that fetch data like this.

difficulty displaying part of a json response in react

so i created a hook to fetch the ISS API. it works fine. but i am having difficulty displaying a specific part of the json that is returned.
my react fetch hook, the useEffect part
my display code
the code works and displays the first two tags, but when i add the 3rd with location.iss_position.longitude i get an undefined error
the console.dir of the json data
i have tried many variations of location.iss_position.longitude but nothing seems to work and a few google searches were unproductive. maybe my own fault for being able to accurately describe my problem with the correct technical language.
EDIT: heres my full code for fetch and display logic. i followed a tutorial and understand about 80% of it now. still learning
export const useFetchPosition = () => {
// define states for the hook
const [location, setLocation] = useState({})
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
// init loading and error states
setLoading(true)
setError(null)
// fetch api url
fetch(issUrl)
// return response as promise with json content
.then(res => res.json())
// return json promise, setLoading state, console log
.then(json => {
setLoading(false)
if (json) {
setLocation(json)
console.dir(json)
} else {
// this else prevents infinite loop
setLocation([])
}
})
// errors update state here
.catch(err => {
setError(err)
setLoading(false)
})
},[])
// return updated states for export to display
return { location, loading, error }
}
const Display = () => {
//call hook and hook data
const { location, loading, error } = useFetchPosition()
// loading and error
if (loading) return <div>Loading...</div>
if (error) return <div>{error}</div>
return (
<>
<h2 className="bg-gray-900 text-gray-300 text-center text-6xl">
{location.message}
</h2>
<h2 className="bg-gray-900 text-gray-300 text-center text-3xl">
response timestamp: {location.timestamp}
</h2>
<h2 className="bg-gray-900 text-gray-300 text-center text-3xl">
current latitude: {JSON.stringify(location) !== '{}' && location.iss_position.latitude}
</h2>
<h2 className="bg-gray-900 text-gray-300 text-center text-3xl">
current longitude: {JSON.stringify(location) !== '{}' && location.iss_position.longitude}
</h2>
</>
)
}
export default Display
Answer:
React isn't having difficulty displaying the JSON response; it's having trouble displaying your component before the response comes in, because you are trying to reference members of undefined objects.
Try putting JSON.stringify(location) !== '{}' && location.iss_position.latitude instead (presuming your default state, when using setState is {})
Alternatively you can define a default state in the same shape as the API's response
Explanation
This is normal Javascript behaviour.
You've assigned {} to location when you first called:
// I'm presuming you did something like this
let [location, setLocation] = setState({});
At this point, location is set to {}. You can, in any JS context, try to refer to members of an object that don't exist and you'll get undefined.
But when you do location.iss_position.longitude, you are trying to reference longitude on a member iss_position which is undefined - this will throw an error. You cannot reference members of undefined, but you can reference undefined members on a defined object.
Try running the following in your console:
let foo = {}; // Can't redefine window.location
console.log(foo); // {}
console.log(foo.iss_position); // undefined
console.log(foo.iss_position.longitude); // TypeError: location.iss_position is undefined
In fact, your console will tell you exactly that. The error your component is throwing specifically says:
location.iss_position is undefined
This is telling you that the object you are trying to reference (location.iss_position) is undefined at some point (before the API responds, for example)

How to efficiently fetch data from URL and read it with reactjs?

I have some URL with json and need to read data.
For the sake of this example json looks like this:
{
"results": [
...
],
"info": {
...
}
}
I want to return fetched data as a property of a component.
What is the best way to do it?
I tried to do that with axios. I managed to fetch data, but after setState in render() method I received an empty object. This is the code:
export default class MainPage extends React.Component {
constructor(props: any) {
super(props);
this.state = {
list: {},
};
}
public componentWillMount() {
axios.get(someURL)
.then( (response) => {
this.setState({list: response.data});
})
.catch( (error) => {
console.log("FAILED", error);
});
}
public render(): JSX.Element {
const {list}: any = this.state;
const data: IScheduler = list;
console.log(data); // empty state object
return (
<div className="main-page-container">
<MyTable data={data}/> // cannot return data
</div>
);
}
}
I don't have a clue why in render() method the data has gone. If I put
console.log(response.data);
in .then section, I get the data with status 200.
So I ask now if there is the other way to do that.
I would be grateful for any help.
----Updated----
In MyTable component I got an error after this:
const flightIndex: number
= data.results.findIndex((f) => f.name === result);
Error is:
Uncaught TypeError: Cannot read property 'findIndex' of undefined
What's wrong here? How to tell react this is not a property?
Before the request is returned, React will try to render your component. Then once the request is completed and the data is returned, react will re-render your component following the setState call.
The problem is that your code does not account for an empty/undefined data object. Just add a check, i.e.
if (data && data.results) {
data.results.findIndex(...);
} else {
// display some loading message
}
In React, after you have stored your ajax result in the state of the component (which you do appear to be doing), you can retrieve that result by calling this.state.list
So to make sure this is working properly, try <MyTable data={this.state.list}>
https://daveceddia.com/ajax-requests-in-react/