Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React setState doesn't re-render after fetching data

This code works if data already fetched.
But doesn't work if I refresh the page and doesn't rerender element.
I'm also using Next JS if it's worth mentioning.

class Books extends Component {
    constructor(props) {
        super(props);
        this.state = {
            book: []
        }
        this.renderBooks= this.renderBooks.bind(this);
    }

    renderBooks() {
        let item;
        let items = [];
        return new Promise((resolve, reject) => {
            this.props.ids.forEach(address => {
                firebase.database().ref(`/books/${address}`)
                    .on('value', snap => {
                        item = snap.val();
                    });
                items.push(item);
            })
            resolve(items);
        });
    }

    async componentDidMount() {
        try {
            let res = [];
            res = await this.renderBooks();
            console.log(res);
            this.setState({ book: res });
        } catch (error) {
            console.log(error);
            this.setState(prevState => {
                return { book: 'err' }
              });
        }
    }

    render() {
        return (
            <div>
                { <List grid={{ gutter: 16 }}
                        dataSource={ this.state.book }
                        renderItem={ item => (
                            <List.Item>
                                <Card title={ !!item && item.title }>
                                    ...Book content...
                                </Card>
                            </List.Item>
                        )} />
                }
            </div>
        );
    }
}

export default Books; 

Is there anything to know about setState and fetching data that I missed here?
PS. Edited constructor to book: [].

like image 813
Igniter Avatar asked Aug 13 '18 14:08

Igniter


People also ask

Why setState is not updating immediately?

The answer: They're just queues setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.

How do you're-render after setState?

If we want to re-render the component then we can easily do so by calling the setState() function which is obtained by destructuring the array returned as a result of calling the useState() hook. Whenever we update the state using the setState() method it re-renders the current component and its child components.

Does React re-render immediately after setState?

React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.

Does setState cause re-render?

As we already saw before, React re-renders a component when you call the setState function to change the state (or the provided function from the useState hook in function components). As a result, the child components only update when the parent component's state changes with one of those functions.


2 Answers

You cannot initialize book with a promise. Instead you can have a solution like below.

Add a conditional rendering to you render method so it will know when to render book. Also you don't need to return new Promise in this case.

class Books extends Component {
    constructor(props) {
        super(props);
        this.state = { books: null }
    }

    componentDidMount() {
        this.renderBooks()
    }

    renderBooks() {
        this.props.ids.forEach(address => {
            firebase.database().ref(`/books/${address}`)
             .on('value', snap => {
                this.setState({books: [...(this.state.books || []), snap.val()] });
            });
        });
    }

    render() {
        return (
           this.state.books ? 
            <div>
                { <List grid={{ gutter: 16 }}
                        dataSource={ this.state.books }
                        renderItem={ item => (
                            <List.Item>
                                <Card title={ !!item && item.title }>
                                    ...Book content...
                                </Card>
                            </List.Item>
                        )} />
                }
            </div> 
           : 'Initializing'
        );
    }
}

export default Books;

Promises are basically async functions that are resolved when it's time.

So when you do

 var item, items = []; // <---- Step 1
 this.props.ids.forEach(address => {
     firebase.database().ref(`/books/${address}`)
         .on('value', snap => {
             item = snap.val(); // <--- Step 3
         });
     });
     items.push(item); // <----- Step 2
 });

The steps are like this. So you were doing items.push(item) before item was assigned a new value which is snap.val(). And that makes item undefined.

I guess the second result you have is thanks to caching. If the internet connection is SOOOO FAST Step 3 might be earlier than Step 2, but that's a bad assumption. That's why the second time you get the result correct.

In this answer's case, instead of having an items array, the snap.val() is added to this.state.books. But this makes it a bit heavyweight. Because every time a query on('value') is called, the setState method will be triggered and the component will be rendered again. If there were 1000 ids the state would change 1000 times.

That's why instead of getting the data one by one I would suggest you to get all the data at once. Try to google something like 'retrieve multiple data from firebase javascript'. Unfortunately I don't know much about firebase so cannot help there.

like image 157
sertsedat Avatar answered Oct 07 '22 14:10

sertsedat


You are initializing this.state.book with a promise. Try setting it to null instead:

this.state = {
    book: null
}
like image 33
TLP Avatar answered Oct 07 '22 14:10

TLP