Expected
When fetchServices() is called, api.getServices is called and in the promise this.setState is called to change fetchingServices to false. Which then hides the loading spinner animation.
Results
For some reason the App is stuck in an infinite loop:
In my ServicesContainer
constructor(props) {
super(props);
this.state = {
services: props.state.servicesReducer.services,
fetchingServices: true,
addingService: false
}
this.fetchServices = this.fetchServices.bind(this);
}
The return()
return (
<div className='services-container'>
<ul className='services-list'>
<li>
<AddServiceContainer />
</li>
{ this.state.fetchingServices
? <div className="icon-spin5 animate-spin"></div>
: null }
{ this.fetchServices() }
</ul>
</div>
)
Finally fetchServices()
fetchServices() {
console.log('fetchServices')
api.getServices(12345).then(res => {
console.log(' api.getServices res:', res)
this.setState({
fetchingServices: false
});
});
}
Full code
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { AddServiceContainer } from './AddServiceContainer'
import { ServiceCard } from '../../components'
import { getServices } from '../../actions'
import * as api from '../../services/api'
export class ServicesContainer extends Component {
constructor(props) {
super(props);
this.state = {
services: props.state.servicesReducer.services,
fetchingServices: true,
addingService: false
}
this.fetchServices = this.fetchServices.bind(this);
}
onFormSubmit(e, user) {
e.preventDefault();
this.props.searchUser(user)
}
fetchServices() {
console.log('fetchServices')
api.getServices(12345).then(res => {
console.log(' api.getServices res:', res)
this.setState({
fetchingServices: false
});
});
}
render() {
return (
<div className='services-container'>
<ul className='services-list'>
<li>
<AddServiceContainer />
</li>
{ this.state.fetchingServices
? <div className="icon-spin5 animate-spin"></div>
: null }
{ this.fetchServices() }
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
state
}
}
const mapDispatchToProps = (dispatch) => {
return {
getServices: (services) => { dispatch(getServices(services)) }
}
}
const ServicesListContainer = ServicesContainer;
export default connect(mapStateToProps, mapDispatchToProps)(ServicesListContainer)
Whenever you do setState, the render method is called again. Now problem here is that you are calling fetchServices() method inside the render method. Now whenever fetchServices() is called it calls an api. When the result of the api come, you are setting the state using setState, which causes rerender(i.e. your render method is called again), which calls the fetchServices() again. This is why it is going in infinite loop.
The solution: You should call your fetchServices() in componentWillMount/componentDidMount method like this:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { AddServiceContainer } from './AddServiceContainer'
import { ServiceCard } from '../../components'
import { getServices } from '../../actions'
import * as api from '../../services/api'
export class ServicesContainer extends Component {
constructor(props) {
super(props);
this.state = {
services: props.state.servicesReducer.services,
fetchingServices: true,
addingService: false
}
this.fetchServices = this.fetchServices.bind(this);
}
componentWillMount(){
this.fetchServices();
}
onFormSubmit(e, user) {
e.preventDefault();
this.props.searchUser(user)
}
fetchServices() {
console.log('fetchServices')
api.getServices(12345).then(res => {
console.log(' api.getServices res:', res)
this.setState({
fetchingServices: false
});
});
}
render() {
return (
<div className='services-container'>
<ul className='services-list'>
<li>
<AddServiceContainer />
</li>
{ this.state.fetchingServices
? <div className="icon-spin5 animate-spin"></div>
: null }
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
state
}
}
const mapDispatchToProps = (dispatch) => {
return {
getServices: (services) => { dispatch(getServices(services)) }
}
}
You should never fetch data in render function. You should do it in componentDidMount function.
render is called after each state or props change, and if you execute an api call in render function, it will trigger setState and by doing so - render again and again and again...
See link
Related
I'd like to call getAlbums() method so I can use the data from the get request and display album data on the client side. I don't know where to call it though. I tried to call it in render() but it creates an infinite loop.
Albums.js
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import AlbumCard from "./AlbumCard";
export class Albums extends Component {
constructor(props) {
super(props);
this.state = { albums: [] };
this.getAlbums = this.getAlbums.bind(this);
}
async getAlbums() {
const {
match: { params },
} = this.props;
console.log(params.id);
try {
const res = await axios.get(
`http://localhost:4000/albums/${encodeURIComponent(params.id)}`,
{
params: {
id: params.id,
},
}
);
console.log(`Returned album data from the server: ${res}`);
this.setState({ albums: res.data });
} catch (err) {
console.log(err);
}
}
render() {
return (
<>
<div className="container" style={{ color: "white" }}>
hello
</div>
</>
);
}
}
export default Albums;
I wanna do something like this inside the div.
this.state.albums.map((album) => (<AlbumCard img={album.img}/>))
The reason you get an infinite loop is because you're calling setState in render. Here is what's happening behind the scenes:
1.getAlbums is called in the render method.
2.The function triggers setState.
3.setState causes re-render.
4.In the render method, getAlbums is called again.
Repeat 1-4 infinitely!
Here's is what you could do:
Create a button and bind getAlbums as a method to the onClick event handler.
2.Run getAlbums on ComponentDidMount like so:
componentDidMount() {
this.getAlbums();
}
componentDidMount() is the best place for making AJAX requests.
The componentDidMount() method will set state after the AJAX call fetches data. It will cause render() to be triggered when data is available.
Here is the working example with componentDidMount()
import React, { Component } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import AlbumCard from "./AlbumCard";
export class Albums extends Component {
constructor(props) {
super(props)
this.state = { albums: [] }
}
componentDidMount() {
axios.get(
`http://localhost:4000/albums/${encodeURIComponent(this.props.id)}`,
{ params: { id: this.props.id } }
)
.then(response => {
console.log(`Returned album data from the server: ${res}`)
this.setState({ albums: response.data })
}
)
.catch(e => {
console.log("Connection failure: " + e)
}
)
}
render() {
return (
<div>
{/* Code for {this.state.albums.map(item => )} */}
{/* render() method will be called whenever state changes.*/}
{/* componentDidMount() will trigger render() when data is ready.*/}
</div>
)
}
}
export default Albums
More information:
https://blog.logrocket.com/patterns-for-data-fetching-in-react-981ced7e5c56/
use componentDidMount()
componentDidMount(){
getAlbums()
}
Since i am new on React JS,i tried to use map function but it gives me the following error:Uncaught TypeError: totalData.map is not a function.It seems everything is ok in the code,please provide me some feedback.Following below is my codes:
import React, { Component } from 'react';
import axios from 'axios';
export default class TotalData extends Component {
constructor() {
super();
this.state = {
totalData: [],
isfinalData: false
}
}
componentDidMount() {
axios.get('https://nepalcorona.info/api/v1/data/nepal')
.then(res => {
this.setState({
totalData: res.data,
isfinalData: true
})
})
}
render() {
console.log("final data>>", this.state);
const { totalData, isfinalData } = this.state;
let finalData = isfinalData
? totalData.map((item, deaths) => (
<div>
<p>{item.deaths}</p>
</div>
))
: <p>Isloading</p>
return (
<div>
{finalData}
</div>
)
}
}
what may be the issue on my code ?
Following below are my fetched data from API and error i got:
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
constructor() {
super()
this.state = {
totalData: [],
isfinalData: false
}
}
componentDidMount() {
axios.get('https://nepalcorona.info/api/v1/data/nepal').then((res) => {
this.setState({
totalData: res.data,
isfinalData: true
})
})
}
render() {
console.log('final data>>', this.state)
const { totalData, isfinalData } = this.state
let finalData = isfinalData ? (
<div>
<p>{totalData.deaths}</p>
</div>
) : (
<p>Isloading</p>
)
return <div>{finalData}</div>
}
}
you don't need to use map because you have only one object
I am trying to migrate my previously working local state to redux. Now loading available Players works just fine, but deleting will somehow stop in the playerActions.js file, where I dispatch and then return an API Call. So to further give details here are my code parts in relevance:
PlayerPage.js (Component):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadPlayers, deletePlayer } from '../../redux/actions/playerActions';
import PlayerForm from './playerform';
import PlayCard from './playercard';
import PropTypes from 'prop-types';
import { toast } from 'react-toastify';
class PlayerPage extends Component {
constructor(props) {
super(props);
this.handleDeletePlayer = this.handleDeletePlayer.bind(this);
state = {};
componentDidMount() {
const players = this.props;
players.loadPlayers().catch(err => {
alert('Loading players failed. ' + err);
});
}
handleDeletePlayer = player => {
toast.success('Player deleted');
try {
deletePlayer(player);
} catch (err) {
toast.error('Delete failed. ' + err.message, { autoClose: false });
}
};
render() {
const styles = {
margin: '20px'
};
return (
<div className="container-fluid">
<div>
<h2 style={styles}>Add Player</h2>
<div className="container-fluid">
<PlayerForm handleAddNewPlayer={this.handleAddPlayer} />
</div>
</div>
<hr></hr>
<div>
<h2 style={styles}>Available Player</h2>
<div className="container-fluid">
{this.props.players.map(player => (
<PlayCard
player={player}
key={player.id}
imageSource={`${process.env.API_URL}/${player.profileImg}`}
onDeletePlayer={this.handleDeletePlayer}
/>
))}
</div>
</div>
</div>
);
}
}
PlayerPage.propTypes = {
players: PropTypes.array.isRequired
};
function mapStateToProps(state) {
return {
players: state.players
};
}
const mapDispatchToProps = {
loadPlayers,
deletePlayer
};
export default connect(mapStateToProps, mapDispatchToProps)(PlayerPage);
And the Action being called is in here:
playerActions.js:
import * as types from './actionTypes';
import * as playerApi from '../../api/playerApi';
export function loadPlayersSuccess(players) {
return { type: types.LOAD_PLAYERS_SUCCESS, players };
}
export function deletePlayerOptimistic(player) {
return { type: types.DELETE_PLAYER_OPTIMISTIC, player };
}
export function loadPlayers() {
return function(dispatch) {
return playerApi
.getAllPlayers()
.then(players => {
dispatch(loadPlayersSuccess(players));
})
.catch(err => {
throw err;
});
};
}
export function deletePlayer(player) {
console.log('Hitting deletePlayer function in playerActions');
return function(dispatch) {
dispatch(deletePlayerOptimistic(player));
return playerApi.deletePlayer(player);
};
}
The console.log is the last thing the app is hitting. But the API Call is never made though.
API Call would be:
playerApi.js:
import { handleResponse, handleError } from './apiUtils';
const axios = require('axios');
export function getAllPlayers() {
return (
axios
.get(`${process.env.API_URL}/player`)
.then(handleResponse)
.catch(handleError)
);
}
export function deletePlayer(id) {
return (
axios
.delete(`${process.env.API_URL}/player/${id}`)
.then(handleResponse)
.catch(handleError)
);
}
I was like spraying out console.log in different places and files and the last one I am hitting is the one in playerActions.js. But after hitting it the part with return function(dispatch) {} will not be executed.
So if someone could point me in a general direction I'd be more than grateful.
It looks like you are calling your action creator deletePlayer but you aren't dispatching it correctly. This is why the console.log is being called but not the method that does the request.
I'd recommend taking a look at the documentation for mapDispatchToProps to fully understand how this works. In your example, you should just need to change the call to deletePlayer in your PlayerPage component to this.props.deletePlayer() to use the action creator after it's been bound to dispatch properly.
this how the mapDispatchToProps should be:
const mapDispatchToProps = dispatch => {
return {
load: () => dispatch(loadPlayers()),
delete: () => dispatch(deletePlayer()),
}
}
then call load players with this.props.load() and delete player with this.props.delete()
I want to fetch data from server & show it inside tables. When I directly put code inside the render it works. But, When I encapsulate inside the addElementsToDisplay function & call that function inside render method it doesn't work. Actually, the function gets called, but response is not rendered in table format. Below is my code:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Button } from 'semantic-ui-react';
import ResponseRenderer from './responseRenderer';
import "./App.css";
const responseDataContext = React.createContext({});
class App extends Component {
constructor(props) {
super(props);
this.getPastJobs = this.getPastJobs.bind(this);
this.addElementsToDisplay = this.addElementsToDisplay.bind(this);
this.state = { pastJobs: [] }
}
render() {
return (
<div className="App">
<Button onClick={this.getPastJobs}>Get Past Jobs</Button>
<h1> Hello, World! </h1>
{this.addElementsToDisplay()}
</div>
);
}
addElementsToDisplay() {
console.log("state: ", JSON.stringify(this.state));
this.state.pastJobs.map((value, index) => {
return <ResponseRenderer key={Math.random()} data={value} />
});
}
getPastJobs() {
fetch('http://localhost:9090/getPastJobs', {
method: 'POST',
body: JSON.stringify({})
})
.then((response) => {
if (response.status !== 200) {
return;
}
response.json().then((jobs) => {
console.log(jobs);
this.setState({ pastJobs: jobs.data })
});
})
.catch((err) => {
console.log(err.message, err.stack);
});
}
}
export default App;
You are not returning the response and hence it is not rendered, just return the mapped response and it will work fine
addElementsToDisplay() {
console.log("state: ", JSON.stringify(this.state));
return this.state.pastJobs.map((value, index) => {
return <ResponseRenderer key={Math.random()} data={value} />
});
}
Here is my code:
ChartActions.js
import * as types from './ChartTypes.js';
export function chartData(check){
return { type: types.CHART_DATA,check };
}
ChartTypes.js
export const CHART_DATA = 'CHART_DATA';
ChartReducers.js
import {
CHART_DATA,
}from './ChartTypes.js';
const initialState = {
chartData : [],
}
export default function ChartReducers(state = initialState, action) {
switch (action.type) {
case CHART_DATA :
return Object.assign({}, state, {
chartData : action.check
});
default:
return state;
}
}
I am so sure that I setup redux quite accurate and it works perfectly. My problem is:
In a component A I dispatch a function:
handleClick(){
this.props.ChartActions.chartData("test string")
}
so in theory, a component B in my project will receive the string "test string" right after the handeClick function triggered, like this
componentWillReceiveProps(){
console.log(this.props.chartData) // test string
}
But I have no idea why SOMETIMES (it only happens sometimes) I have to trigger handleClick function TWO times in component A so that the component B could be able to get the updated state (in this example, it is "test string"). I supposed it's a bug.
I need the component B will receive the updated state (i.e "test string") RIGHT AFTER the handleClick is triggered only ONE TIME.
I have a container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ChartActions from '../../components/center-menu/services/ChartActions.js';
import CenterMenu from '../../components/center-menu/center-menu-index.js'
import RightMenu from '../../components/right-content/right-content-index.js'
class Home extends Component {
render() {
return (
<div>
<CenterMenu
ChartActions = {this.props.ChartActions}
/>
<RightMenu
ChartProps={this.props.ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Here is the component A where I fire an actions:
import React, { Component } from 'react';
class CenterMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
handleClick(){
this.props.ChartActions.chartData('test string')
}
render() {
return (
<div className="center_menu" onClick={this.handleClick.bind(this)}>
Some stuff
</div>
)
}
}
export default CenterMenu;
And in another component B:
import React, { Component } from 'react';
class RightMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
componentWillReceiveProps(){
console.log(this.props.ChartProps.chartData, "right here")
}
render() {
return (
<div className="center_menu">
Some stuff
</div>
)
}
}
export default RightMenu;
Weird thing:
In Component A, if I trigger the handleClick function by clicking in a div tag, it fires an action that change the initial state to "test string"
But...
In the component B the statement
console.log(this.props.ChartProps.chartData, "right here")
show empty string first like this:
right here
But when I trigger the handleClick function the SECOND TIME in component A , then in component B, in the statement
console.log(this.props.ChartProps.chartData, "right here")
it show the following:
test string "right here"
which is the result I want to achieve.
But I don't understand why I HAVE TO trigger the handleClick function twice. I need it by one click.
The problem is your Home component doesn't rerender the children. Try keeping ChartProps in a state in Home like so:
class Home extends Component {
state = {
ChartProps: null //you can use some default value, this might cause undefined is not an object error in you children
}
componentDidMount() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
componentWillReceiveProps() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
render() {
const { ChartProps } = this.state
return (
<div>
<CenterMenu
ChartActions={this.props.ChartActions}
/>
<RightMenu
ChartProps={ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);