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: [].
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.
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.
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.
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.
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.
You are initializing this.state.book
with a promise. Try setting it to null
instead:
this.state = {
book: null
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With