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');
});
Related
I am new to react.js so troubles caught me. I have small todo-list app connected with mockAPI. Application gets todo list data from API. As required, I call API inside componentDidMount() instead of constructor. However, API is called twice (only after page reloaded, not data manipulation as put\delete data to API). Any errors or warnings in console.
class App extends Component {
todoServ = new TodoServer();
constructor(props) {
super(props);
this.state = { data: [], maxId: 0 };
}
/*
code to add\delete\done todo item;
*/
findCurrentMaxId = (data) => {
const idList = [];
data.forEach(todo => {
idList.push(todo.id);
});
return Math.max(...idList);
}
updateTodoData = (data) => {
const maxId = this.findCurrentMaxId(data);
this.setState({ data, maxId });
}
getTodoData = () => {
this.todoServ
.getTodoList()
.then(this.updateTodoData)
.catch(this.errorTodoData);
}
componentDidMount() {
this.getTodoData();
}
render() {
return (
<div className="app">
<div className="content">
<AddTodoListItem onAddNewTodoItemData={this.onAddNewTodoItemData}/>
<TodoList
data={this.state.data}
onDoneTodoItemData={this.onDoneTodoItemData}
onDeleteTodoItemData={this.onDeleteTodoItemData} />
</div>
</div>
)
}
}
export default App;
Console:
This is the service fetches data.
class TodoService {
#url = `https://*secret*/todoslist/todo`;
async getResource(url) {
let res = await fetch(url);
if (!res.ok) {
throw new Error(`Could not fetch ${url}, status: ${res.status}`);
}
return await res.json();
}
async getTodoList() {
const res = await this.getResource(this.#url);
console.log('GET', res);
return res;
}
}
export default TodoService;
Thanks for the advices.
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);
I have the next React/Redux/Thunk code:
This is my call to API:
//api.js
export const categoriesFetchData = (page, parentOf) => {
return axios.get(
url +
'categories/' +
'?parent=' +
parentOf +
'&limit=10' +
'&offset=' +
(page - 1) * 10,
);
};
This my action (I pretend to return an axios promise from this action):
//actions.js
export const subCategoriesFetchData = (page = 1, parentOf) => {
return dispatch => {
dispatch(oneCategoryLoading(true));
return api.categoriesFetchData(page, parentOf)
.then(response => {
dispatch(subCategoriesFetchDataSuccess(response.data.results));
dispatch(oneCategoryLoading(false));
})
.catch(error => {
console.log(error);
});
};
};
And in my container:
const mapDispatchToProps = dispatch => {
return {
fetchOneCategory: slug => {
dispatch(fetchOneCategory(slug)).then(() => {
console.log('working');
});
},
};
};
But I get this error:
Uncaught TypeError: Cannot read property 'then' of undefined
What is wrong and how to return a promise in the container?
Thanks for your help.
Here's how I am approaching this.
First, there are a couple of changes you need to do to your subCategoriesFetchData function:
export const subCategoriesFetchData = (page = 1, parentOf) => {
return dispatch => {
dispatch(oneCategoryLoading(true));
// That's the key thing. Return a new promise, then ma
return new Promise((resolve, reject) => {
api.categoriesFetchData(page, parentOf)
.then(response => {
dispatch(subCategoriesFetchDataSuccess(response.data.results));
dispatch(oneCategoryLoading(false));
resolve(response); // Resolve it with whatever you need
})
.catch(error => {
console.log(error);
reject(error); // And it's good practice to reject it on error (optional)
});
});
};
};
Then, here's how you can do the trick with mapDispatchToProps and then chaining .then()s:
import React from 'react';
import { connect } from 'react-redux';
import { subCategoriesFetchData } from './wherever-it-is';
class MyComponent extends React.Component {
componentDidMount() {
this.props.subCategoriesFetchData()
.then( () => { console.log('it works'); })
.catch( () => { console.log('on error'); });
}
render() {
return ( <p>Whatever</p> );
}
}
const mapDispatchToProps = { subCategoriesFetchData };
connect(null, mapDispatchToProps)(MyComponent);
Sorry, i will answer my own question:
i change this:
const mapDispatchToProps = dispatch => {
return {
fetchOneCategory: slug => {
dispatch(fetchOneCategory(slug)).then(() => {
console.log('working');
});
},
};
};
to
const mapDispatchToProps = dispatch => {
return {
fetchOneCategory: slug => {
return dispatch(fetchOneCategory(slug))
},
};
};
and now i can make this:
import React from 'react';
import { connect } from 'react-redux';
class MyComponent extends React.Component {
componentDidMount() {
this.props.fetchOneCategory()
.then( () => { console.log('it works'); })
.catch( () => { console.log('on error'); });
}
render() {
return ( <p>Whatever</p> );
}
}
thanks for your help!
in your container, you don't need
.then(() => {
console.log('working');
});
dispatch(fetchOneCategory(slug)) doesn't return a promise, there is no then to be read
I've been following a great course on how to build a server-side rendered app with React and Redux, but I'm now in a situation that the course doesn't cover and I can't figure out by myself.
Please consider the following component (it's pretty basic, except for the export part at the bottom):
class HomePage extends React.Component {
componentDidMount() {
this.props.fetchHomePageData();
}
handleLoadMoreClick() {
this.props.fetchNextHomePagePosts();
}
render() {
const posts = this.props.posts.homepagePosts;
const featuredProject = this.props.posts.featuredProject;
const featuredNews = this.props.posts.featuredNews;
const banner = this.props.posts.banner;
const data = ( posts && featuredProject && featuredNews && banner );
if( data == undefined ) {
return <Loading />;
}
return(
<div>
<FeaturedProject featuredProject={ featuredProject } />
<FeaturedNews featuredNews={ featuredNews } />
<Banner banner={ banner } />
<PostsList posts={ posts } heading="Recently on FotoRoom" hasSelect={ true } />
<LoadMoreBtn onClick={ this.handleLoadMoreClick.bind( this ) } />
</div>
);
}
}
function mapStateToProps( { posts } ) {
return { posts }
}
export default {
component: connect( mapStateToProps, { fetchHomePageData, fetchNextHomePagePosts } )( HomePage ),
loadData: ( { dispatch } ) => dispatch( fetchHomePageData() )
};
The above works fine: the loadData function makes an API request to fetch some data, which is fed into the component through the mapStateToProps function. But what if I wanted to fire multiple action creators in that same loadData function? The only thing that kind of works is if I write the function like this:
function loadData( store ) {
store.dispatch( fetchFeaturedNews() );
return store.dispatch( fetchHomePageData() );
}
export default {
component: connect( mapStateToProps, { fetchHomePageData, fetchNextHomePagePosts } )( HomePage ),
loadData: loadData
};
but this is not great because I need all data to be returned... Keep in mind that the exported Component ends up in the following route configuration:
const Routes = [
{
...App,
routes: [
{
...HomePage, // Here it is!
path: '/',
exact: true
},
{
...LoginPage,
path: '/login'
},
{
...SinglePostPage,
path: '/:slug'
},
{
...ArchivePage,
path: '/tag/:tag'
},
]
}
];
and here's how the loadData function is used once the component is needed by a certain route:
app.get( '*', ( req, res ) => {
const store = createStore( req );
const fetchedAuthCookie = req.universalCookies.get( authCookie );
const promises = matchRoutes( Routes, req.path ).map( ( { route } ) => {
return route.loadData ? route.loadData( store, req.path, fetchedAuthCookie ) : null;
}).map( promise => {
if( promise ) {
return new Promise( ( resolve, reject ) => {
promise.then( resolve ).catch( resolve );
});
}
});
...
}
Also, here's an example of the actions fired by the action creators. They all return promises:
export const fetchHomePageData = () => async ( dispatch, getState, api ) => {
const posts = await api.get( allPostsEP );
dispatch({
type: 'FETCH_POSTS_LIST',
payload: posts
});
}
and the reducer:
export default ( state = {}, action ) => {
switch( action.type ) {
case 'FETCH_POSTS_LIST':
return {
...state,
homepagePosts: action.payload.data
}
default:
return state;
}
}
So your actions return a Promise, and you are asking how can you return more than one Promise. Use Promise.all:
function loadData({ dispatch }) {
return Promise.all([
dispatch( fetchFeaturedNews() ),
dispatch( fetchHomePageData() ),
]);
}
But... remember that Promise.all will resolve when all of it's Promises resolve, and it will return an Array of values:
function loadData({ dispatch }) {
return Promise.all([
dispatch( fetchFeaturedNews() ),
dispatch( fetchHomePageData() ),
]).then(listOfResults => {
console.log(Array.isArray(listOfResults)); // "true"
console.log(listOfResults.length); // 2
});
}
So you will probably want to handle it differently.
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