62

I'm new to reactjs, I want to fetch data in server, so that it will send page with data to client.

It is OK when the function getDefaultProps return dummy data like this {data: {books: [{..}, {..}]}}.

However not work with code below. The code execute in this sequence with error message "Cannot read property 'books' of undefined"

  1. getDefaultProps
  2. return
  3. fetch
  4. {data: {books: [{..}, {..}]}}

However, I expect the code should run in this sequence

  1. getDefaultProps
  2. fetch
  3. {data: {books: [{..}, {..}]}}
  4. return

Any Idea?

statics: {
    fetchData: function(callback) {
      var me = this;

      superagent.get('http://localhost:3100/api/books')
        .accept('json')
        .end(function(err, res){
          if (err) throw err;

          var data = {data: {books: res.body} }

          console.log('fetch');                  
          callback(data);  
        });
    }


getDefaultProps: function() {
    console.log('getDefaultProps');
    var me = this;
    me.data = '';

    this.fetchData(function(data){
        console.log('callback');
        console.log(data);
        me.data = data;      
      });

    console.log('return');
    return me.data;            
  },


  render: function() {
    console.log('render book-list');
    return (
      <div>
        <ul>
        {
          this.props.data.books.map(function(book) {
            return <li key={book.name}>{book.name}</li>
          })
        }
        </ul>
      </div>
    );
  }

10 Answers 10

29

What you're looking for is componentWillMount.

From the documentation:

Invoked once, both on the client and server, immediately before the initial rendering occurs. If you call setState within this method, render() will see the updated state and will be executed only once despite the state change.

So you would do something like this:

componentWillMount : function () {
    var data = this.getData();
    this.setState({data : data});
},

This way, render() will only be called once, and you'll have the data you're looking for in the initial render.

Sign up to request clarification or add additional context in comments.

5 Comments

...but if getData fires a async request?
@nav For async requests, you're going to have to set some initial state that indicates to the user that data is being fetched (perhaps a loading icon). You can still perform the fetch in componentWillMount, and when the data is retrieved, you can set that state again to indicate that the data is finished loading, and display it to the user.
Yes, but then the data is fetched on the client and not the server. Isn't the OP asking to save this roundtrip if what is being requested is being rendered on the server and the async request is hitting that same server surely its better to process this all BEFORE sending the response down to the client?
@nav you can fire this request synchronously by using await this.getData()
@MichaelParker componentWillMount is deprecated. Do you have any other alternatives?
21

A very simple example of this

import React, { Component } from 'react';
import { View, Text } from 'react-native';

export default class App extends React.Component  {

    constructor(props) {
      super(props);

      this.state = {
        data : null
      };
    }

    componentWillMount() {
        this.renderMyData();
    }

    renderMyData(){
        fetch('https://your url')
            .then((response) => response.json())
            .then((responseJson) => {
              this.setState({ data : responseJson })
            })
            .catch((error) => {
              console.error(error);
            });
    }

    render(){
        return(
            <View>
                {this.state.data ? <MyComponent data={this.state.data} /> : <MyLoadingComponnents /> }
            </View>
        );
    }
}

Comments

9

The best answer I use to receive data from server and display it

 constructor(props){
            super(props);
            this.state = {
                items2 : [{}],
                isLoading: true
            }

        }

componentWillMount (){
 axios({
            method: 'get',
            responseType: 'json',
            url: '....',

        })
            .then(response => {
                self.setState({
                    items2: response ,
                    isLoading: false
                });
                console.log("Asmaa Almadhoun *** : " + self.state.items2);
            })
            .catch(error => {
                console.log("Error *** : " + error);
            });
    })}



    render() {
       return(
       { this.state.isLoading &&
                    <i className="fa fa-spinner fa-spin"></i>

                }
                { !this.state.isLoading &&
            //external component passing Server data to its classes
                     <TestDynamic  items={this.state.items2}/> 
                }
         ) }

Comments

6

In React, props are used for component parameters not for handling data. There is a separate construct for that called state. Whenever you update state the component basically re-renders itself according to the new values.

var BookList = React.createClass({
  // Fetches the book list from the server
  getBookList: function() {
    superagent.get('http://localhost:3100/api/books')
      .accept('json')
      .end(function(err, res) {
        if (err) throw err;

        this.setBookListState(res);
      });
  },
  // Custom function we'll use to update the component state
  setBookListState: function(books) {
    this.setState({
      books: books.data
    });
  },
  // React exposes this function to allow you to set the default state
  // of your component
  getInitialState: function() {
    return {
      books: []
    };
  },
  // React exposes this function, which you can think of as the
  // constructor of your component. Call for your data here.
  componentDidMount: function() {
    this.getBookList();
  },
  render: function() {
    var books = this.state.books.map(function(book) {
      return (
        <li key={book.key}>{book.name}</li>
      );
    });

    return (
      <div>
        <ul>
          {books}
        </ul>
      </div>
    );
  }
});

2 Comments

em... but as I know that data can be fetch and ready before render. your example will render twice. first render (with empty props) > fetch data and setState > render again with state. Correct me if I'm wrong
You are right, the component will render twice. Making ajax calls is asynchronous, which was the issue with the original code. The only way you're getting the data before render is called is if you have a parent component that is in charge of data fetching and mounting BookList. Even in then, you will have to update the state of that component which will call render twice.
1

As a supplement of the answer of Michael Parker, you can make getData accept a callback function to active the setState update the data:

componentWillMount : function () {
    var data = this.getData(()=>this.setState({data : data}));
},

Comments

1

I've just stumbled upon this problem too, learning React, and solved it by showing spinner until the data is ready.

    render() {
    if (this.state.data === null) {
        return (
            <div className="MyView">
                <Spinner/>
            </div>
        );
    }
    else {
        return(
            <div className="MyView">
                <ReactJson src={this.state.data}/>
            </div>
        );
    }
}

1 Comment

You can check if the state is falsy instead of null, so it could be cleaner to use if (!this.state.data) {
1

If you are using class based react components, then fetch the data inside the componentWillMount() method, if you are using functional components use the useeffect hook, everything inside it will load before mount and everything inside the return statement will mount afterwards.

using useEffect hook:

import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      const response = await fetch('your-api-endpoint');
      const result = await response.json();
      setData(result);
    } catch (error) {
      console.error('Error fetching data:', error);
    }
  };

  // Render the component using the fetched data
  return <div>{/* Render your component with the fetched data */}</div>;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>

using componentDidMount():

import React from 'react';

class MyComponent extends React.Component {
  state = {
    data: [],
  };

  async componentDidMount() {
    try {
      const response = await fetch('your-api-endpoint');
      const result = await response.json();
      this.setState({ data: result });
    } catch (error) {
      console.error('Error fetching data:', error);
    }
  }

  // Render the component using the fetched data
  render() {
    return <div>{/* Render your component with the fetched data */}</div>;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.2/umd/react-dom.production.min.js"></script>

1 Comment

This is incorrect, useEffect works like this: useEffect(() => { // this runs after render return () => { // this runs after unmount } }) There is no way to load data before render with useEffect. The only way to do that with hooks is to do it in the actual render body, and store a value with useRef to know if you've already mounted or not. If you put this at the beginning of your arrow function component it will start loading at the beginning of the render instead of after. I'm sorry it's formatted so horribly, Stackoverflow won't let me add paragraphs or code blocks.
0

Responded to a similar question with a potentially simple solution to this if anyone is still after an answer, the catch is it involves the use of redux-sagas:

https://stackoverflow.com/a/38701184/978306

Or just skip straight to the article I wrote on the topic:

https://medium.com/@navgarcha7891/react-server-side-rendering-with-simple-redux-store-hydration-9f77ab66900a

Comments

0

You can use redial package for prefetching data on the server before attempting to render

Comments

0

Try using componentDidMount:

componentDidMount : function () {
    // Your code goes here
},

More on this here

If you are using hooks, use the useEffect hook:

useEffect(() => { 
    // Your code goes here
});

Documentation on useEffect

1 Comment

The question was about making a request before a first render. But both componentDidMount and useEffect will be called after initial render

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.