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}
Related
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
}
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/
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)
I'm trying to parse a json received from external api.
My reducer is:
import { RECEIVED_FORECAST } from '../actions/index';
export default function ForecastReducer (state = [], action) {
switch (action.type) {
case RECEIVED_FORECAST:
return Object.assign({}, state, {
item: action.forecast
})
default:
return state;
}
}
Then main reducer goes like:
import { combineReducers } from 'redux';
import ForecastReducer from './forecast_reducer';
const rootReducer = combineReducers({
forecast: ForecastReducer
});
export default rootReducer;
and container looks like
import React, { PropTypes, Component } from 'react';
import { connect } from 'react-redux';
class WeatherResult extends Component {
render() {
const forecast = this.props.forecast.item;
{console.log('almost: ', forecast)}
return (
<div>
<h1> </h1>
</div>
)
}
}
function mapStateToProps({ forecast }) {
return {
forecast
}
}
export default connect(mapStateToProps)(WeatherResult)
Output of the almost is exactly the same son as I supposed:
almost:
Object
currently: {time: 1476406181, summary: "Drizzle", icon: "rain", nearestStormDistance: 0, precipIntensity: 0.0048, …}
daily: {summary: "Light rain on Saturday and Thursday, with temperatures rising to 92°F on Wednesday.", icon: "rain", data: Array}
So, my question is, how can I show the value of, let's say forecast.currently.summary?
1) If I just try to insert it within {} I receive : 'TypeError: undefined is not an object (evaluating 'forecast.currently')'
2) I can't use mapping as the json might have other components added
Is there any method to get to this property directly, without mapping all the file?
Thanks
The problem you have is that you're requesting the data. That doesn't complete immediately. Think about what the app is doing while you're waiting for the weather data to arrive.
It's displaying something. In your case, the render method is failing because you're trying to show data that hasn't arrived yet.
The solution:
render() {
const forecast = this.props.forecast;
const text = forecast && forecast.item.currently.summary || 'loading...';
return (
<div>
<h1>{text}</h1>
</div>
)
}
}
This way you check if you already have the data and if not, you show something useful.
I am attempting to load some local json data with redux and display in react app. But i'm getting the pageId is undefined in the reducer.
Not sure what I am doing wrong here, I think it might be something wrong with how I'm passing the data but im very new to redux so i'm not sure.
Data
const page = [
{"title":"Mollis Condimentum Sem Ridiculus"},
{"title":"Pharetra Tellus Amet Commodo"}
]
export default page;
Action
const getPage = (pageId) => {
const page = { pageId: pageId }
return {
type: 'GET_PAGE_SUCCESS',
payload: page
}
}
export default getPage
Reducer
import getPage from '../actions/actionCreators'
import pageData from './../data/pageData';
const defaultState = pageData
const pageReducer = (state = defaultState, action) => {
if (action.type = 'GET_PAGE_SUCCESS') {
state.page[action.payload.pageId].title = action.payload
}
return state
}
export default PageReducer
Component
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import getpage from '../../actions/actionCreators'
const mapStateToProps = (state, props) => {
const page = state.page[props.pageId]
return { page }
}
class Page extends Component {
componentDidMount () {
this.props.getpage(this.props.pageId)
}
render() {
return (<div><PageContainer pageId={0} /></div>)
}
}
const PageContainer = connect(mapStateToProps, { getpage })(page)
export default Page
I've modified your code into a working JSFiddle for reference: https://jsfiddle.net/qodof048/11/
I tried to keep it as close to your example, but let me explain the changes I made to get it working (also note that JSFiddle does not use the ES6 import syntax).
1) Your PageContainer was not constructed correctly. The last parameter should have been a reference to the Page component (not 'page').
const PageContainer = connect(mapStateToProps, { getPageSimple, getPageAsync })(PageComponent)
2) You used PageContainer in the Page component, but PageContainer is the 'wrapper' around Page. You use PageContainer instead of Page in your render method, so it loads the data (maps state and actions).
ReactDOM.render(
<Provider store={store}>
<div>
<PageContainer pageId="0" async={false} />
<PageContainer pageId="1" async={true} />
</div>
</Provider>,
document.getElementById('root')
);
3) The store was mixed up a bit. If I understood your example correctly you want to load a page into the local store from the pageData array, which simulates a server call maybe. In that case you intialState can't be pageData, but rather is an empty object. Think of it like a local database you're going to fill. The call to your action getPage then gets the page (here from your array) and dispatches it into the store, which will save it there.
const getPageSimple = (pageId) => {
const page = pageDatabase[pageId]; // this call would be to the server
// then you dispatch the page you got into state
return {
type: 'GET_PAGE_SUCCESS',
payload: {
id: pageId,
page: page
}
}
}
4) I've added an async example to the JSFiddle to explain how you would actually fetch the page from the server (since the simple example would not be sufficient). This needs the thunk middleware for redux to work (since you need access to the dispatch method in order to async call it). The setTimeout simulates a long running call.
const getPageAsync = (pageId)=>{
return (dispatch, getState) => {
setTimeout(()=>{
const page = pageDatabase[pageId]; // this call would be to the server, simulating with a setTimeout
console.log("dispatching");
// then you dispatch the page you got into state
dispatch({
type: 'GET_PAGE_SUCCESS',
payload: {
id: pageId,
page: page
}
});
}, 2000);
}
}
The JSFiddle loads 2 containers, one with your simple getPage and one with the async version, which loads the title after 2 seconds.
Hope that helps you along on your react/redux journey.
Hey I see a small mistake in you component, I think. You are doing this.props.pageId, when you are setting page and not pageId on the component's props. So shouldn't it be this.props.getPage(this.props.page.pageId) instead? Could that be it?
Also a small side note, an important tip for using redux is to not mutate state. In you reducer where you are doing state.page[action.payload.pageId].title = action.payload you should probably not set state like that, but instead return a new object called newState which is identical to state, but with the title updated. It is important to treat objects as immutable in Redux. Cheers