React Component properties - javascript

I'm trying to use a component property this.albumButtons to store an array of AlbumButtons.
When componentDidMount gets called, I fetch the album names and set it to the state. Then, I call makeButtons from the names.
In my makeButtons function, I set this.albumButtons to the array of AlbumButton components.
When I check this.albumButtons length, I get 0.
What am I doing wrong?
export default class ModalContent extends Component {
constructor(props){
super(props);
this.state = {
albumNames: [],
isLoading: false,
isEmptyOfAlbums: false,
}
this.albumButtons = []
}
componentDidMount(){
this.setState({isLoading: true})
const getAlbumsNamesPromise = new Promise ((resolve, reject) => {
MySwiftClass.getAlbumsNames((arr) => {
if(arr.length === 0) this.setState({isEmptyOfAlbums: true});
this.setState({albumNames: arr})
})
})
getAlbumsNamesPromise.then(this.makeButtons).then(this.setState({isLoading: false}))
}
makeButtons() {
//const component = this;
return new Promise((resolve, reject) => {
this.albumButtons = this.state.names.map((name) =>
<AlbumButton
key={name}
name={name}
/>
)
resolve()
})
}
render() {
if (this.state.isLoading){
return(
//loading screen
)
}
return(
<Text>{this.albumButtons.length}</Text>
)
}
}

setState is asynchronous, you need to resolve in the callback of setState so it waits until state is updated with the albumNames:
const getAlbumsNamesPromise = new Promise ((resolve, reject) => {
MySwiftClass.getAlbumsNames((arr) => {
if(arr.length === 0) this.setState({isEmptyOfAlbums: true});
this.setState({albumNames: arr}, resolve)
})
}) // also need to pass a function into .then, not invoke a function
getAlbumsNamesPromise.then(this.makeButtons).then(() => this.setState({isLoading: false}))
Your also mapping over this.state.names.map I think you meant this.state.albumNames.map

Related

React: Async function not being called

Why is my aync call fetchButtonTeams() below not being called. I am trying to print its results in console.log(this.state.data) below. Even if i call it in the render() I get infinite loops or errors. Can anyone suggest what to do?
I just want to print the results in console.log in render()
class TeamFilter extends Component {
constructor() {
super();
this.state = { data: [] };
}
async fetchButtonTeams() {
const response = await fetch(`/api/teams`);
const json = await response.json();
console.log(json)
this.setState({ data: json });
}
handleTeamSelection = e => {
this.props.setTeam(e.target.title);
this.props.fetchTeams(e.target.title)
};
render() {
let test = ['Chaos', 'High Elves', 'Orcs']
this.fetchButtonTeams()
console.log(this.state.data)
return (
<DropdownButton id="dropdown-team-button" title={this.props.team_name}>
{test.map(cls => (
<div key={cls}>
<Dropdown.Item onClick={this.handleTeamSelection} title={cls}>{cls}</Dropdown.Item>
</div>
))}
</DropdownButton>
)
}
}
const mapStateToProps = state => {
return {
team_name: state.team_name
}
};
const mapDispatchToProps = dispatch => {
return {
fetchCards: path => dispatch(fetchCards(path)),
fetchTeams: params => dispatch(fetchTeams(params)),
setTeam: team_name => dispatch({ type: "SET_TEAM", team_name })
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamFilter)
The reason you get infinite loops when you call the function on the render method is because each time the function is calling setState which in turn runs the function again and again, triggering an infinite loop.
I don't see where you are calling fetchButtonTeams() anywhere in your component, but a good idea for fetching data is putting the method inside a componentDidMount lifecycle method and console log inside the render method.You can learn more about lifecycle hooks here.
For your code:
class TeamFilter extends Component {
constructor() {
super();
this.state = { data: [] };
}
componentDidMount() {
this.fetchButtonTeams();
}
async fetchButtonTeams() {
const response = await fetch(`/api/teams`);
const json = await response.json();
console.log(json);
this.setState({ data: json });
}
handleTeamSelection = e => {
this.props.setTeam(e.target.title);
this.props.fetchTeams(e.target.title);
};
render() {
let test = ["Chaos", "High Elves", "Orcs"];
console.log(this.state.data);
return (
<DropdownButton id="dropdown-team-button" title={this.props.team_name}>
{test.map(cls => (
<div key={cls}>
<Dropdown.Item onClick={this.handleTeamSelection} title={cls}>
{cls}
</Dropdown.Item>
</div>
))}
</DropdownButton>
);
}
}
const mapStateToProps = state => {
return {
team_name: state.team_name
};
};
const mapDispatchToProps = dispatch => {
return {
fetchCards: path => dispatch(fetchCards(path)),
fetchTeams: params => dispatch(fetchTeams(params)),
setTeam: team_name => dispatch({ type: "SET_TEAM", team_name })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamFilter);

State array does not render properly in React.js

I am working on a hacker news clone I am trying to get the ids of the top stories from their api using axios in componentDidMount and then making another axios call to get the stories and push them in a state array but when I try to map over and render that array nothing shows up
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.state.posts.push(value)
})
.catch(err =>{
console.log(err)
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
<Header title="Hacker News" />
{this.state.posts.map( (element, index) => <Post key={element.data.id} serialNum={index} postTitle={element.data.title} postVotes={element.data.score} postAuthor={element.data.by} />) }
</div>
)
}
}
Try setting the state like this:
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({
posts: [value, ...this.state.posts]
})
})
.catch(err =>{
console.log(err)
})
})
This way you're using setState and appending every new value to the existing state.
As stated in the comments, don't use push for set state. In your code when you make the second request you must change the setState method to spread out the new value.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState(prevState => ({posts: [ value.data, ...prevState.posts]}))
})
.catch(err =>{
console.log("err");
console.log(err);
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
{this.state.posts && this.state.posts.map( (element, index) =>
<div key={element.id}>
{element.title}
</div>
)}
</div>
);
}
}
componentDidMount() is called after Render() only once. React doesn't know about the state changes unless you use setState().
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({posts: [value, ...this.state.posts]})
})
})
})
}
Use this.setState({posts : [value, ...this.state.posts]}) instead of this.state.posts.push(value). using ... (spread operator) appends the value to the original posts array.

How to cancel a request in componentWillUnMount

i want to cancel the load_items request on componentWillUnmount. I am not sure how to do it? could someone help me with this. Thanks.
What i have tried to do?
I have a parent component ViewItems and child component ItemsList. In the ViewItems component i retrieve the items list from the server using load_items method which in turn uses client.get_file method. and store those items list in state named "items". I call this load_items method in componenDidMount method.However doing so it sometimes displays items for previous item. This happens sometimes. I would like to have the proper item details in the state.
I think i have to cancel the load_items request before updating the state again.
Below is the code ,
class ViewItems extends React.PureComponent {
constructor(props) {
super(props);
this.default_root_item = {
name: 'Items',
index: 0,
children: [],
};
this.state = {
root_items: this.default_root_item,
items: [],
};
}
componentDidMount() {
this.load_items();
}
componentDidUpdate(PrevProps) {
if (PrevProps.item_id !== this.props.item_id) {
this.load_items();
}
}
componentWillUnmount() {
this.unlisten_path_change();
}
load_items = () => {
const file_name = 'file_name.json';
client.get_file(this.props.item_id, file_name, 'json')
.then((request) => {
const items = request.response;
this.setState({items: [this.default_root_item]});}
this.handle_path_change(this.props.location.pathname);})};
return (
<ChildComponent
on_close={this.handle_item_close}
root_item={this.state.root_item}/>)}
export default class ChildComponent extends React.PureComponent {
<Items
items={root_item}/>
function Items(props) {
return (
<ul className="Items_list">
<div className="items">
{props.items.map((item, index) => {
return (
<Item
key={index}
item={item}
/>
);
})}
</div>
</ul>
);
}
}
export function get_file(qrcode, file_name, response_type,on_progress, cancel) {
const local_override_defined = item_files[qrcode] && item_files[qrcode][file_name];
if (local_override_defined) {
const file = item_files[qrcode][file_name];
const reader = new FileReader();
return new Promise(resolve => {
if (response_type === 'blob') {
resolve({response: file});
} else {
reader.onload = () => {
if (response_type === 'json') {
resolve({response: JSON.parse(reader.result)});
} else {
resolve({response: reader.result});
}
};
reader.readAsText(file);
}
});
}
return new Promise((resolve, reject) => {
item_file_get_url(qrcode, file_name).then(({response}) => {
const request = new XMLHttpRequest();
request.addEventListener('progress', on_progress);
request.open('GET', response.url);
request.responseType = response_type;
send_request(request, undefined, cancel, response.url).then(resolve).catch(reject);
}).catch(reject);
});
}

How to keep the fetching queue in order

I have two more fetching requests in one page, how to arrange them one by one?
Just a code for instance, expecting the fetching queue is executed in num order.
class FetchingQueue extends Component {
...
componentDidUpdate(preProps) {
if ( this.props.prop1 != preProps.props1) {
this.props.fetching1
this.props.fetching2
this.timeoutHanlde = setTimeout(() => {
this.props.fetching3
}, 1000)
}
}
render() {
return (
...
)
}
}
export default connect(
state => ({
prop1: state.reducer.props1
}),
{ fetching1, fetching2, fetching3 }
)(FetchingQueue)
Just return Promises from the fetching functions and then wait for them:
class FetchingQueue extends Component {
...
async componentDidMount() {
const fetching1Result = await this.props.fetching1();
const fetching2Result = await this.props.fetching2();
const fetching3Result = await this.props.fetching3();
}
render() {
return (
...
)
}
}
export default connect(
state => ({ prop1: state.reducer.props1 }),
{ fetching1, fetching2, fetching3 }
)(FetchingQueue)
Fetching function can look like this:
const fetching1 = new Promise((resolve, reject) => {
// call resolve when ready
resolve('result');
});

React State Not Updating on First click and Component Not Rendering

retrieveInTransit() is the click handler. An API call retrieves data and returns an array of objects into the new state object in the setState() function. The first click logs an empty array for the pumps property. If I click it again, I get the populated array of pumps. Why do I have to click twice? The Equipment component does not render even though the log in the render() function returns the correct array of pumps which is passed in as a prop for the Equipment component. I am at a loss on this and any help would be appreciated.
import React, { Component } from 'react';
import {ListGroup} from 'reactstrap';
import Equipment from './Equipment';
class Transit extends Component {
constructor(props) {
super(props);
this.state = {
pending: false,
pumps: []
};
}
handleCancelClick = (index) => {
this.setState((prevState) =>
prevState.pumps[index].isCancelled = !prevState.pumps[index].isCancelled
);
this.setState((prevState) => {
prevState.pumps.every((equipment) => equipment.isCancelled === false)
? prevState.pending = false: prevState.pending = true
}
)};
populate_transit = () => {
let pumpArray = [];
fetch('http://192.168.86.26:8000/api/v1/transit_list', {mode: 'cors'})
.then(response => response.json())
.then(MyJson => MyJson.map((entry) =>{
if (document.getElementsByClassName('title')[0].innerText.toLowerCase().includes(entry.transferfrom)) {
pumpArray.push(
{
unitnumber: entry.unitnumber,
id: entry.id,
message: entry.unitnumber + ' was moved to ' + entry.transferto,
isCancelled: false
});
}}));
return pumpArray
};
cancelTransit = () => {
let cancelled = this.state.pumps.filter((movement) => movement.isCancelled);
let cancelledId = cancelled.map((object) => object.id);
fetch('http://192.168.86.26:8000/api/v1/transit_list/', {
method:'POST',
mode: 'cors',
body: JSON.stringify(cancelledId),
headers:{
'Content-Type': 'application/json'
}
}
);
this.populate_transit()
};
retrieveInTransit = () => {
if (this.state.pending) {
this.setState({
pending: false,
pumps: this.cancelTransit()
}, console.log(this.state))} else {
this.setState({
pending: false,
pumps: this.populate_transit()
}, console.log(this.state))
}
};
render() {
console.log(this.state.pumps);
return (
<ListGroup>
<Equipment transitequipment={this.state.pumps} cancelClick={this.handleCancelClick}/>
<button className='btn btn-dark' onClick={this.retrieveInTransit}>
{this.state.pending ? 'Submit Cancellations': 'Refresh'}
</button>
</ListGroup>
);
}
}
export default Transit;
populate_transit = () => {
let pumpArray = [];
fetch('http://192.168.86.26:8000/api/v1/transit_list', {mode: 'cors'})
.then(response => response.json())
.then(MyJson => MyJson.map((entry) =>{
if (document.getElementsByClassName('title')[0].innerText.toLowerCase().includes(entry.transferfrom)) {
pumpArray.push(
{
unitnumber: entry.unitnumber,
id: entry.id,
message: entry.unitnumber + ' was moved to ' + entry.transferto,
isCancelled: false
});
}}));
return pumpArray};
So, In the code above, you are returning pumpArray outside of the fetch call. Since fetch takes time to call the api and resolve the promise, the current value of your pumpArray = [] that's why you get the empty array at first. On the next click promise is already resolved so you get your pumpedArray.
In order to fix this, move the pumpArray inside then.

Categories