React: How ensure completion of some Life-cycle methods before others - reactjs

I'm using React Native 0.43. I've one component, named ApiComponent. In componentWillMount method of this component, I'm fetching some results from an API and I want this result in my render method. I'm using following code (abridged version) in my component:
export default class ApiComponent extends Component {
constructor(props) {
super(props);
this.state = {
statement: {},
};
}
componentWillMount() {
fetch('http://example.com/api_url/')
.then(response => response.json())
.then(data => this.setState({ statement: data }))
.catch(error => console.error(error));
}
render() {
return (
<Text>{'Rendering API: ' + console.log(this.state.statement)}</Text>
);
}
}
Now, when I run this code I get an empty result in my console Rendering API: {}. As per my understanding, the render method executes before the results are returned from the API and therefore the state is not being updated with the results.
My question is, how I can make sure that my render method only executes when the code in my componentWillMount completes its execution?

You can use a ternary operation to ensure the text only renders if this.state.statement is true
return (
{ this.state.statement ? <Text>{'Rendering API: ' + console.log(this.state.statement)}</Text> : null }
)

Related

re-render triggered in componentDidMount

In the life cycle of a component, if a re-render is triggered by some synchronous operation in componentDidMount(), would the user have a chance to see the first render content on browser?
e.g. If I toggle a start downloading boolean flag in componentDidMount() through redux, which then causes the re-render because the flag is mapped to redux for the component.
-------Update Info-----
The sync operation is just changing the start downloading flag to true, and the flag is mapped to the component, where the flag is checked to determine the JSX contents in render(). In redux, right after the flag is set to true, then the downloading operation begins. When downloading is completed, redux sets the flag to false.
Consider the following lifecycle sequence:
render() //JSX A
componentDidMount() // the flag is set
render() // re-render JSX B
Will JSX A be displayed in the browser, regardless of how quick it is?
the action creator called in componentDidMount():
export const downloadArticleList = () => {
return (dispatch, getState) => {
// set start flag to true synchronously, before axios.get
dispatch(listDownloadStart());
axios.get('/articles')
.then(response => {
//set the flag to false and update the data
dispatch(saveArticleList(response.data))
})
.catch(err => {
dispatch(serverFail(err))
console.log("[downloadArticleList]] axios", err);
})
}
}
It is a SPA, no SSR.
It depends on a few things:
How long sync operation takes
Are you doing SSR (thus there will be time dedicated for DOM rehydrating)
Generally, I'd consider this as an antipattern
As we discuss in the comment here is the example :
interface ExampleComponentProps {
}
interface ExampleComponentState {
loading: boolean;
}
export class ExampleComponent extends React.Component<ExampleComponentProps, ExampleComponentState>{
constructor(props, context) {
super(props, context);
this.state = { loading: true };
}
componentDidMount() {
//some method {}
//after get result
this.setState({
loading: false
})
}
render() {
return (
<div>
<Spin spinning={this.state.loading} >
//Your COmponent here
</Spin>
</div>
)
}
}
If your project is complicated, the easiest way is using
setTimeout(() => {
this.setState({
// your new flag here
})
}, 0);

ReactJs: Fetch, transform data with function, then setState

I have managed to fetch data from an API successfully. Data transformation of JSON format works too, but i'm having trouble integrating it to "componentDidMount" to set state with a transformed JSON format. I'm getting an undefined state when i console.log(this.state.races).
I'm also getting this error message:
Can't call setState (or forceUpdate) on an unmounted component.
class Races extends Component {
constructor(props) {
super(props);
this.state = {
races: []};
this.processResults = this.processResults.bind(this);
}
componentDidMount(){
fetch(RACE_SERVICE_URL)
.then(results => results.json())
.then(this.processResults)
}
processResults(data) {
const raceId_arr = data.map(d => d.raceId);
const season_arr = data.map(d => d.season);
const raceName_arr = data.map(d => d.raceName);
const url_arr = data.map(d => d.url);
const data_mapped = {'raceId': raceId_arr, 'season': season_arr, 'raceName': raceName_arr, 'url': url_arr};
this.setState({races:data_mapped});
console.log(data_mapped);
console.log(this.state.races);
}
render() {
const title = 'Race Tracks';
return (
<div>
<h2>{title}</h2>
<RacesViz data= {this.state.races.raceId} />
</div>
);
}
}
export default Races;
I have also tried:
.then(data => this.processResults(data))
What console.log(data_mapped) prints:
{raceId:[1, 2, 3]
raceName:["AGP", "BGP", "CGP"]
season: [2018, 2018, 2018]
url: ["http://en.wikipedia.org/wiki/AGP", "http://en.wikipedia.org/wiki/BGP", "http://en.wikipedia.org/wiki/CGP"]}
setState is async so you can't get immediate result with console.log like you did. Use a callback function instead:
this.setState({races:data_mapped}, () => console.log(this.state.races));
Or you can console.log your state in your render method.
Quote from official docs:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Important!
This makes reading this.state right after calling setState() a potential pitfall.
So you will not get state immediately after setState. You have 2 ways to solve it.
1) You should check in componentDidUpdate hook.
componentDidUpdate(prevProps, prevState) {
console.log(this.state.races);//your data updated here.
}
You can see here to use properly.
2) Or you use callback in setState like this setState(updater, callback):
this.setState({races:data_mapped}, () => {
console.log(this.state.races)//your data updated here.
})

React component is rendering but not updating when state is updating

React component is showing data when state is null but, when its getting data then its not updating the content of the view.
constructor(props){
super(props);
this.state = {
post: null
}
this.getTotalDownloadSize = this.getTotalDownloadSize.bind(this);
}
componentWillMount(){
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState({
post: _.get(response, 'data')
});
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
inside my render i am getting null value for render(){
render(){
const {post} = this.state;
console.log{post};
const files = _.get(post, 'files', []);
)
initially its showing the null value but after it has value but its not updating the content of the view.
can anyone help me with this.
thanks in advance.
componentDidMount is place where you can place request logic.
componentDidMount() {
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState((state) => ({ post: _.get(response, 'data')}));
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
If your data came from an asynchronous request you should use componentDidMount
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs. At this point in the lifecycle,
you can access any refs to your children (e.g., to access the
underlying DOM representation). The componentDidMount() method of
child components is invoked before that of parent components.
If you want to integrate with other JavaScript frameworks, set timers
using setTimeout or setInterval, or send AJAX requests, perform those
operations in this method.

Why setState interrupt componentDidUpdate?

I have this component (simplified version):
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
data: {}
};
}
componentDidUpdate(prevProps, prevState) {
if(this.props.time && this.props.time !== prevProps.time){
this.setState({
isLoading: true
})
fetch(...).then(data => {
this.setState({
data: data
isLoading:false
}
}
}
render(){
{isLoading, data} = this.state;
return (isLoading ? /*show spinner*/ : /* show data*/);
}
}
This component works: it shows a spinner while fetching data, then it shows the data.
I'm trying to test it using jest and enzyme:
test('Mounted correctly', async() => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
myComponent.setProps({time: '02-01-18'}); //necessary to call componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}
From my knowledge, in order to call componentDidUpdate you have to call setPros (link). However, following the debugger, the call end when hitting:
this.setState({
isLoading: true
})
Which is kinda of expected, the problem is that the snapshot is:
Object {
"isLoading": true
"data": {}
}
Which is, of course, something that I don't want. How can I solve this?
UPDATE: I found a(n ugly) solution!
The problem is that what we want to test is this setState is completed:
this.setState({
data: data
isLoading:false
}
Now, this doesn't happen even by setting await myComponent.setProps({time: '02-01-18'}); (as suggested in one of the answers), because it doesn't wait for the new asynchronous call created by the setState described above.
The only solution that I found is to pass a callback function to props and call it after setState is completed. The callback function contains the expect that we want!
So this is the final result:
test('Mounted correctly', async() => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
const callBackAfterLastSetStateIsCompleted = () => {
expect(topAsins.state()).toMatchSnapshot();
}
myComponent.setProps({time: '02-01-18', testCallBack: callBackAfterLastSetStateIsCompleted}); //necessary to call componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}
And modify the component code as:
this.setState({
data: data
isLoading:false
},this.props.testCallBack);
However, as you can see, I'm modifying a component in production only for testing purpose, which is something very ugly.
Now, my question is: how can I solve this?
All you need to do here to test is make use of async/await like
test('Mounted correctly', async () => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
await myComponent.setProps({time: '02-01-18'}); //necessary to call componentDidUpdate, await used to wait for async action in componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}

Get answers from API request as array

I'm trying to make an API request and display them, I think that I have to render them as array but have problems with this. Here is my code
class RecipesList extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResult: ''
};
}
componentDidMount() {
fetch('https://api.edamam.com/search?q=chicken&app_id=ec279fce&app_key=51017bcd32e69bcb0cc8b5f99e8783ca')
.then(resp => resp.json())
.then(resp => this.setState({searchResult: resp})
);
}
render() {
return <div>{this.state.searchResult}</div> //???
}
}
class App extends React.Component {
render() {
return <RecipesList/>;
}
}
ReactDOM.render(
<App/>, document.querySelector('#app'));
If you just want to see your data, just do:
componentDidMount() {
fetch('https://api.edamam.com/search?q=chicken&app_id=ec279fce&app_key=51017bcd32e69bcb0cc8b5f99e8783ca')
.then(resp => resp.json())
.then(resp => this.setState({searchResult: JSON.stringify(resp)})
);
}
You can also log your response object like:
componentDidMount() {
fetch('https://api.edamam.com/search?q=chicken&app_id=ec279fce&app_key=51017bcd32e69bcb0cc8b5f99e8783ca')
.then(resp => resp.json())
.then((resp) => {
console.log(resp);
this.setState({searchResult: JSON.stringify(resp)})
});
}
The problem was: (I saw this error in the log)
Uncaught (in promise) Error: Objects are not valid as a React child
(found: object with keys {q, from, to, params, more, count, hits}). If
you meant to render a collection of children, use an array instead or
wrap the object using createFragment(object) from the React add-ons.
Check the render method of [MyComponent]...
So in fact, the resp in the 2nd callback is an object, which cannot be rendered, so you can either convert it to string to see if your API works, or loop through the object to render it properly based on your need!
Feel free to post here some new errors in your console, so we can find a good way to render your data, thanks

Resources