ReacJs and fetch json and update state prop - json

I'm a bit disappointed of my results about getting started with reactjs.
I'm now just trying to parse json data from a simple restful ws.
If I put on chrome the url of rest get query, it answers correctly a json array:
[{"label":"TestLabel!!","value":7,"rating":17.25},{"label":"TestLabel2 !!","value":8,"rating":18.25}]
this is my React component:
export default class ItemLister extends React.Component {
constructor() {
super();
this.state = { items: [{"label":"Test1!!","value":7,"rating":17.25}] };
}
componentDidMount() {
fetch('/rest/json/product/get')
.then(result=> {
this.state.items.push(result);
});
}
render() {
return(
<div>
<div>Items:</div>
{
this.state.items.map(function(item, i){
return <div key={i}>{item.label}</div>
}
)}
</div>
);
}
}
This, after lots of tweaking, shows no errors, but nothing happens to the list, which shows only the constructor element.
Pls point me to the right direction...
thanks
SOLVED:
the "setState()" as suggested was part of the problem.
The other part is the way i manage the json answer.
This way the project works:
componentDidMount() {
fetch('/rest/json/product/get')
.then(response => response.json())
.then(response => {
this.setState({items: response });
})
}
But I just tried another way to implement the call by chance since i was totally lost.
I feel a bit confused. What is the correct way to implement a fetch?
Anyways...this is ok.

Read the last line in the Docs:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
try this:
this.setState((state) => ({ items: state.items.concat(result) }))
or you can do this if result is array:
this.setState({ items: [...this.state.items , ...result] })

try this.setState('items',result)

Related

Type safe deserialization in TypeScript

I have this sample TypeScript code that is supposed to deserialize a simple JSON into an instance of class Person and then call foo method on it, but it doesn't work:
class Person {
name!: string;
age!: number;
foo() {
console.log("Hey!");
}
}
fetch("/api/data")
.then(response => {
return response.json() as Promise<Person>;
}).then((data) => {
console.log(data);
data.foo();
});
The output of console show that object is in a proper shape, but it is not recognized as Person:
Object { name: "Peter", age: 44 }
​
age: 44
​name: "Peter"
​
Thus when it tries to call foo method it fails:
Uncaught (in promise) TypeError: data.foo is not a function
http://127.0.0.1:8000/app.js:14
promise callback* http://127.0.0.1:8000/app.js:12
How can I fix it? Should I use Object.assign or there is another better/native solution?
let x = (<any>Object).assign(Object.create(Person.prototype), data);
x.foo();
Remember, TypeScript is just a way of annotating JavaScript code with type guards. It doesn't do anything extra. For example, saying that the object returned by response.json() should be treated as a Promise<Person> does not mean it will invoke the constructor of your Person class. Rather, you'll just be left with a plain old JavaScript object that has a name and an age.
It looks to me like you'll need to create a constructor for your Person class which can create a new instance of a Person based on an object that matches its interface. Something like this, perhaps?
interface PersonLike {
name: string;
age: string;
}
class Person implements PersonLike {
constructor(data: PersonLike) {
this.name = data.name;
this.age = data.age;
}
name: string;
age: string;
foo() {
console.log("Hey!");
}
}
fetch("/api/data")
.then(response => {
return response.json() as Promise<PersonLike>;
}).then((data) => {
const person = new Person(data);
person.foo();
});
I'd also recommend using a type guard instead of the as keyword, in case the API you're fetching data from changes. Something like this, perhaps:
function isPersonLike(data: any): data is PersonLike {
return typeof data?.name === 'string' && data?.age === 'string';
}
fetch("/api/data")
.then(response => {
return response.json();
}).then((data: unknown) => {
if (isPersonLike(data)) {
const person = new Person(data);
person.foo();
}
});
... is supposed to deserialize a simple JSON into an instance of class Person and then ...
Unfortunately, generic type in TypeScript only works as some kind of model design assistant. It will never be compiled into JavaScript file. Take your "fetch" code for example:
fetch("/api/data")
.then(response => {
return response.json() as Promise<Person>;
}).then((data) => {
console.log(data);
data.foo();
});
After compile the above TypeScript file into JavaScript, we can find the code as Promise<Person> is completely removed:
fetch("/api/data")
.then(function (response) {
return response.json();
}).then(function (data) {
console.log(data);
data.foo();
});
To implement "type safe deserialization", you need to save class/prototype information during serialization. Otherwise, these class/prototype information will be lost.
... or there is another better/native solution? ... BTW, what if a class field has a custom type, so it is an instance of another class?
No, there is no native solution, but you can implement "type safe" serialization/deserialization with some libraries.
I've made an npm module named esserializer to solve this problem automatically: save JavaScript class instance values during serialization, in plain JSON format, together with its class name information. Later on, during the deserialization stage (possibly in another process or on another machine), esserializer can recursively deserialize object instance, with all Class/Property/Method information retained, using the same class definition. For your "fetch" code case, it would look like:
// Node.js server side, serialization happens here.
const ESSerializer = require('esserializer');
router.get('/api/data', (req, res) => {
// ...
res.json(ESSerializer.serialize(anInstanceOfPerson));
});
// Client side, deserialization happens here.
const ESSerializer = require('esserializer');
fetch("/api/data")
.then(response => {
return response.text() as Promise<string>;
}).then((data) => {
const person = ESSerializer.deserialize(data, [Person, CustomType1, CustomType2]);
console.log(person);
person.foo();
});

How to update the html page view after a timeout in Angular

I am trying to display a routerlink name based on a condition. I want to display the div section routerLink name if condition is true.If i check {{isNameAvailable}}, first it displays false and after this.names got the values it shows true.Since in the component getDetails() method is asynchronous this.names getting the values after html template render.Therefore this routerLink does n't display.Therefore I want to display div section after some time. (That 's the solution i have) Don't know whether is there any other solution.
This is my html file code.
<main class="l-page-layout ps-l-page-layput custom-scroll bg-white">
{{isNameAvailable}}
<div class="ps-page-title-head" >
<a *ngIf ="isNameAvailable === true" [routerLink]="['/overview']">{{Name}}
</a>
{{Name}}
</div>
</main>
This is my component.ts file
names= [];
isNameAvailable = false;
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.subscribe(params => {
this.names.push(params.Names);
console.log(this.names);
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
});
});
}
resolveAfterSeconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 900);
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
async getValues() {
await this.resolveAfterSeconds(900);
}
And console.log(this.isLevelAvailable); also true. What I can do for this?
1.You do not have anything to show in the HTML only the isNameAvailable, because you do not have any assignment in the Name variable.
2.It is better to use the angular build-in async pipe,
when you want to show the returned value from observables.
3.When you are using the *ngIf directive you can skip *ngIf ="isNameAvailable === true" check because the variable is boolean type, you gust write *ngIf ="isNameAvailable", it will check also for null but NOT for undefined
It is working because the *ngIf directive is responsible for checking and rendering the UI, you can see how many times the directive is checking by calling an function and print and answer in the console.
By any chance do you have changeDetection: ChangeDetectionStrategy.OnPush docs set in component annotation? That might explain this behaviour. With it Angular run change detection only on component #Input()'s changes and since in your case there were non it did not run change detection which is why template was not updated. You could comment that line to check if that was cause of the issue. You are always able to run change detection manually via ChangeDetectorRef.detectChange() docs which should solve you problem
constructor(private cd: ChangeDetectorRef) {}
...
getDetails() {
this.route.params.subscribe(params => {
...
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
this.cd.detectChanges(); // solution
console.log(this.isNameAvailable);
});
});
}
This stackblitz show this bug and solution. You can read more about change detection here
You could use RxJS timer function with switchMap operator instead of a Promise to trigger something after a specific time.
Try the following
import { Subject, timer } from 'rxjs';
import { takeUntil, switchMap } from 'rxjs/operators';
names= [];
isNameAvailable = false;
closed$ = new Subject();
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.pipe(
switchMap((params: any) => {
this.names.push(params.Names);
return timer(900); // <-- emit once after 900ms and complete
}),
takeUntil(this.closed$) // <-- close subscription when `closed$` emits
).subscribe({
next: _ => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
}
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
ngOnDestroy() {
this.closed$.next(); // <-- close open subscriptions when component is closed
}

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.

Cannot get deeper to arrays and objects

I'm looking for some explanation why in the componentDidMount() function, api returns all values ​​from API, but not in the render() function. In the render function I can only get to the first level as in the case of {this.state.home.title} but {this.state.home.acf.hero_text [1] .text} returns an undefined error.
import React, {Component} from "react";
import '../styles/App.scss';
class Home extends Component {
constructor() {
super();
this.state = {
home: []
}
}
async componentDidMount() {
let homeUrl = await fetch("http://localhost:8080/wp-json/better-rest-endpoints/v1/page/strona-glowna");
fetch(homeUrl)
let json = await homeUrl.json();
this.setState({
home: json
})
console.log(this.state.home);
console.log(this.state.home.acf.hero_tekst[1].tekst); // works fine !
}
render() {
console.log(this.state.home.acf); // works fine !
return (
<div className="Home">
Home
<br/>
{this.state.home.title} // works fine !
{this.state.home.acf.hero_tekst[1].tekst} // trows an error: Cannot read property 'hero_tekst' of undefined
</div>
);
}
}
I've tried to use instead useState, useEffect, but the problem is the same. My guess is that the render() function is called before and that's why there is a problem, but how to make the API data go to the render() function.
render function calls before the componentDidMount so the value of
{this.state.home.acf.hero_tekst[1].tekst} is undefined you have to write the check like
{this.state.home.acf.hero_tekst && this.state.home.acf.hero_tekst[1].tekst }
componentDidMount executes after initial render. Initially, you state is empty.
So first time your component renders, it won't get any data and throws error. Then your componentDidMount executes which set's state value, here you can see updated state.
You should check if data is present,
{this.state.home && this.state.home.title}
{this.state.home.acf.hero_tekst && this.state.home.acf.hero_tekst[1].tekst}

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/