I am having a problem here with my code. When the Data is undefined or equals false it just has the error Maximum depth exceeded. But how does this happen? The setState method should only be called once and if the method sets the state to false, id shouldnt set any states anymore, or not? Where is my error.
class App extends Component {
constructor(props) {
super(props);
this.changeLogin = this.changeLogin.bind(this);
//Loggedin is false
this.state = {
loggedIn: undefined
};
}
componentDidMount() {
communicate("getToken").then(data => {
//if no data is available
if (!data) {
if (this.state.loggedIn === undefined)
this.setState({
loggedIn: false
});
}
//Fetch the key
else {
let reqBody =
"refresh_token=" + data.refresh_token + "&grant_type=refresh_token";
//Fetch via refresh_token
fetch(conf.apiDomain + "/oauth/token", {
method: "POST",
body: reqBody,
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF8",
Authorization: "Basic Y2xpZW50OlRlc3Q="
}
})
.then(response => response.json())
.then(json => {
//if error exists
if (json.error) {
throw new Error("Token not valid");
} else {
//save the new tokens in the json file
communicate("saveToken", json).then(res => {
//Set the loginstate
this.setState({
loggedIn: true
});
});
}
})
.catch(err => {
if (this.state.loggedIn === undefined) {
this.setState({
loggedIn: false
});
}
});
}
});
}
//only for the logincomponent
changeLogin() {
this.setState({
loggedIn: !this.state.loggedIn
});
}
render() {
return (
<Router>
<MuiThemeProvider theme={theme}>
<Decider loggedIn={this.state.loggedIn} />
<Route
path="/login/"
render={() => {
return <Login changeLogin={this.changeLogin} />;
}}
/>
<AppRoute
path="/app/"
component={Pyl}
loggedIn={this.state.loggedIn}
/>
</MuiThemeProvider>
</Router>
);
}
}
Error appears at: if(!data) if there is no data, if the data is there but invalid, its at the .catch statement
Full Error MSG: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
StackTrace: https://ibb.co/jZKKJT1
Github for full code: https://github.com/precodeeu/pyl
jsfiddle(works): https://jsfiddle.net/L8anmhvx/3/
It seems the real error is coming from Decider and your routing.
In Decider when loggedIn = false, it appears you are redirecting back, thus creating the infinite loop.
Related
I have a react component that is in charge of performing a network request using fetch. The API response will be shown on cards within the component.I have defined the structure of the response in the state as movimientos. But when updating the state inside filtrarDatos function with the response, an infinite loop is created and fetch requests are performed infinitely.
Here is my code:
export class Datepicker extends Component {
constructor(props) {
super(props)
this.state = {
startDate: "",
endDate: "",
focusedInput: "",
movimientos: {}
}
}
filtrarDatos(startDateString, endDateString) {
if (startDateString !== '' && endDateString !== '') {
const empresa = {
FECHA_INICIAL: startDateString,
FECHA_FINAL: endDateString
};
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(empresa)
}
fetch('http://localhost:4000/api/movimiento/filtrarfecha', options)
.then((res) => res.json())
.then((data) => {
const { movimientos } = data
console.log({ movimientos })
this.setState({ movimientos })
})
.catch((err) => console.log(err))
}
}
render() {
const endDateString = this.state.endDate && this.state.endDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
const startDateString = this.state.startDate && this.state.startDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
return (
<div className="DatePicker">
<DateRangePicker
startDate={this.state.startDate} // momentPropTypes.momentObj or null,
endDate={this.state.endDate} // momentPropTypes.momentObj or null,
onDatesChange={({ startDate, endDate }) => this.setState({ startDate, endDate })} // PropTypes.func.isRequired,
focusedInput={this.state.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null,
onFocusChange={focusedInput => this.setState({ focusedInput })} // PropTypes.func.isRequired,
endDatePlaceholderText={"Fecha inicial"}
startDatePlaceholderText={"Fecha final"}
displayFormat={"DD/MM/YYYY"}
numberOfMonths={1}
isOutsideRange={() => false}
showClearDates={true}
/>
{this.filtrarDatos(startDateString, endDateString)}
</div>
)
}
}
To be more clear the error is in the following part of the code, if I comment on the status update the program works correctly and only makes a single request. I am new to react and I cannot understand what is happening.
.then((data) => {
const { movimientos } = data
console.log({ movimientos })
this.setState({ movimientos })
})
This is a screenshot of my console during infinite network requests
Its happening because this.filtrarDatos is being called after each re-render (state changes), creating a infinite loop (change data, render, change data, rend...)
You can move the { this.filtrarDatos(startDateString, endDateString) } to componentDidMount:
Remove { this.filtrarDatos(startDateString, endDateString) }
Add this lifecycle function, after constructor:
componentDidMount() {
const endDateString = this.state.endDate && this.state.endDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
const startDateString = this.state.startDate && this.state.startDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
this.filtrarDatos(startDateString, endDateString);
}
You are calling {this.filtrarDatos(startDateString, endDateString)} in your render body, then in this method you update state, so it creates an infinite loop because react rerender your component after state changes.
I have two components i.e. Project component and MainContainer. Maincontainer will fetch data again and again using fetchData method, so in this I am able to change the url as well as component when I move forward but during backward it will not change the component but url can change.
Project Component:
conditionalRendering(project) {
let text;
if (project.attributes.folder_id) {
text = <Link to={`/folder/${project.attributes.folder_id}`}>
{project.attributes.name}</Link>
} else {
text = <span>{project.attributes.name}</span>
}
return text;
}
MainContainer Component:
componentDidMount() {
const paths = this.props.location.pathname.split('/')
const id = paths[paths.length - 1]
console.log('componentDidMount')
axios.get(this.state.url, {
params: {
folder_id: id
}
})
.then(response => {
console.log('Axios response of componentdid mount')
console.log(response)
this.setState({
isLoading: !this.state.isLoading,
documents: response.data.data
})
})
}
fetchData(folder_id) {
this.setState({
isLoading: true,
})
console.log('updating state fetch data')
axios.get(this.state.url, {
params: {
folder_id: folder_id
}
})
.then(response => {
console.log('Axios response inside fetchData')
console.log(response)
this.setState({
isLoading: false,
documents: response.data.data,
})
})
}
selectRow(document) {
this.setState({
selectedRow: true,
rowDetails: document
})
}
componentDidUpdate(prevProps, prevState){
console.log('componentDidUpdate')
if (prevState.isLoading === false) {
console.log('if will run isLoading is false')
this.setState({ isLoading: true })
console.log('if will run isLoading is true' )
const paths = this.props.location.pathname.split('/')
const id = paths[paths.length - 1]
axios.get(this.state.url, {
params: {
folder_id: id
}
})
.then(response => {
console.log('Axios response componentdidupdate')
console.log(response)
this.setState({
isLoading: !this.state.isLoading,
documents: response.data.data
})
})
}
}
documentsCollection() {
let documentComponents;
if (this.state.documents.length > 0 ) {
documentComponents = this.state.documents.map(document => {
return (
<tr key={document.id} onClick={() => this.selectRow(document)}>
<span className="file-thumbnail float-right"><i className={document.attributes.is_folder ? 'fas fa-folder' : 'far fa-file'}></i></span>
<td data-label="Name">
<Link to={`/folder/${document.attributes.id}`} onClick={() => this.fetchData(document.attributes.id)}>{document.attributes.filename}</Link>
</tr>
)
})
return documentComponents;
}
}
Router Component:
<Switch>
<Route exact path="/" component= {Project} />
<Route path="/folder/:id" component= {MainContainer} />
</Switch>
If you are already in Maincontainer and only the id value has changed, componentDidMount will not be activated. Please use componentDidUpdate and compare props.
I mostly do backend, so my javascript isn't all that, but I'm having a problem in my admin panel I'm designing. Some parts of the site can only be accessed by certain users.
Each time the protected component should load, I send a request to my REST server, which returns back either 200 or a 403, the 200 response contains a key called redirect, which is False. So my thinking was to do the following:
...
import { Redirect } from 'react-router-dom';
import axios from 'axios';
class MyProtectedComponent extends Component {
constructor(props) {
super(props);
this.state = {
authCalled: false,
redirect: true,
};
}
componentDidMount() {
console.log("mounting...")
axios.get('<https://url>',
{headers: {"Authorization": localStorage.getItem("token")}})
.then(res => {
this.setState({
redirect: res.data.data.redirect,
authCalled: true,
});
})
}
render() {
if (this.state.authCalled === false) {
return (
<div className="animated fadeIn">
<Row>
<Col>
authenticating...
</Col>
</Row>
</div>
)
}
if (this.state.redirect === true) {
return <Redirect to={{pathname: "/nonauthpage"}} />;
}
return ( ....... <main code> ..... )
Now if the server sends back the 200 for the user is allowed to access, the component loads, but if not, the page gets stuck in the <authenticating> phase and never Redirects.
All of my javascript is self-taught, If what I'm doing is bad practice for performing this type of thing, please let me know how to properly do it, or show me why this is not working so I get it working.
You're using axios which means if the response is not 200 (or 2XX) the then will not be executed and instead you will need to chain execute a .catch like below:
componentDidMount() {
console.log("mounting...")
axios.get('<https://url>',
{headers: {"Authorization": localStorage.getItem("token")}})
.then(res => {
this.setState({
redirect: res.data.data.redirect,
authCalled: true,
});
}).catch(error => {
// You can do additional checks here like e.g. check if the response code is 403 or 401
this.setState({
redirect: true,
authCalled: true
});
})
}
You can customize your code as below to make it works
....
import { Redirect } from 'react-router-dom';
import axios from 'axios';
class MyProtectedComponent extends Component {
constructor(props) {
super(props);
this.state = {
authCalled: false,
redirect: true,
};
}
componentDidMount() {
console.log("mounting...")
axios.get('<https://url>',
{headers: {"Authorization": localStorage.getItem("token")}})
.then(res => {
this.setState({
redirect: res.data.data.redirect,
authCalled: true,
});
}).catch(err => {
this.setState({
redirect: true,
authCalled: true,
});
})
}
render() {
if (this.state.authCalled === true) {
if (this.state.redirect === true) {
return <Redirect to={{pathname: "/nonauthpage"}} />;
} else {
return ( ....... <main code> ..... )
}
}
else {
return (
<div className="animated fadeIn">
<Row>
<Col>
authenticating...
</Col>
</Row>
</div>
)
}
}
}
I have a container component that fetches data to a Rails API but can't successfully iterator over that data without getting the following error;
TypeError: this.state.dryRedBottles.map is not a function
This was caused by the following code;
render() {
let searchResults = this.state.dryRedBottles.map((bottle) => <SearchResults key={bottle} name={bottle}/>)
As you can see in the code above, I am setting a variable equal to an iteration over this.state.dryRedBottles, which should map every bottle object to the presentational component SearchResults.
I also created a function, generateSearchResults to debug this.props and this.state. this.state.dryRedBottles is by default an empty array, but it's updated to be an array of objects. Since iterators like .map or .forEach only work on arrays, I tried to mitigate this on my Rails server;
def create
#wine_bottles = WineBottle.all
if params[:dryRedBottles][:fetchingRedDry] == true
#red_dry_bottles = []
#wine_bottles.each do |bottle|
if (bottle.w_type == 'red') & (bottle.dry == true)
bottle = [bottle] if !bottle.is_a?(Array)
#red_dry_bottles.push(bottle)
end
end
render json: #red_dry_bottles
else
nil;
end
end
I made sure each JSON object was push inside of an array, so at least this.state.dryRedBottles would return this; [[{}], [{}], [{}]].
My question is: what is causing this error?
What workarounds can I leverage to successfully use searchResults?
Below is my container component in its full glory;
class Red extends Component {
constructor(props) {
super(props);
this.state = {
// helps monitor toggling
redDryClick: false,
redBothClick: false,
redSweetClick: false,
fetchingRedDry: false,
fetchingRedSweet: false,
dryRedBottles: []
};
};
handleSweetRequest = (event) => {
event.preventDefault();
this.setState(prevState => ({
redDryClick: !prevState.redDryClick,
redBothClick: !prevState.redBothClick
}));
}
handleDryRequest = (event) => {
event.preventDefault();
this.setState(prevState => ({
redSweetClick: !prevState.redSweetClick,
redBothClick: !prevState.redBothClick,
fetchingRedDry: !prevState.fetchingRedDry
}));
}
componentDidUpdate(){
if (this.state.fetchingRedDry === true) {
let redDryState = Object.assign({}, this.state);
this.props.fetchDryReds(redDryState);
// this.props.dryRedBottles.length > this.state.dryRedBottles.length
if (this.props.dryRedBottles !== this.state.dryRedBottles ) {
this.setState({ dryRedBottles: this.props.dryRedBottles });
}
}
debugger;
}
handleBothRequest = (event) => {
event.preventDefault();
this.setState(prevState => ({
redDryClick: !prevState.redDryClick,
redSweetClick: !prevState.redSweetClick
}));
}
generateSearchResults = () => {
debugger;
if ( Array.isArray(this.props.dryRedBottles) ) {
this.props.dryRedBottles.map((bottle) => {
debugger;
return bottle;
})
}
}
render() {
let searchResults = this.state.dryRedBottles.map((bottle) => <SearchResults key={bottle} name={bottle}/>)
return (
<div>
<h2>Welcome to... Red</h2>
<FormControlLabel
control={
<Switch
// configuring #material-ui Switch componanet
value="hidden"
color="primary"
id="redSweet"
disableRipple
// handles previous State + redux + API call
onChange={this.handleSweetRequest}
disabled={this.state.redSweetClick}
/>
}
label="Sweet"
/>
<FormControlLabel
control={
<Switch
// configuring #material-ui Switch componanet
// value="hidden"
value="RedDry"
color="primary"
id="redDry"
disableRipple
// handles previous State + redux + API call
onChange={(event) => this.handleDryRequest(event)}
disabled={this.state.redDryClick}
/>
}
label="Dry"
/>
<FormControlLabel
control={
<Switch
// configuring #material-ui Switch componanet
value="hidden"
color="primary"
id="redBoth"
disableRipple
// handles previous State + redux + API call
onChange={this.handleBothRequest}
disabled={this.state.redBothClick}
/>
}
label="Both"
/>
<div>
{searchResults}
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
dryRedBottles: state.redWineReducer
};
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
fetchDryReds: fetchDryReds
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Red);
Below is my actionCreator;
export function fetchDryReds(redDryState) {
return (dispatch) => {
// debugger;
// dispatch({ type: 'LOADING_DRY_REDS' });
return fetch('http://localhost:3001/wine_bottles', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'},
body: JSON.stringify({dryRedBottles: redDryState})})
.then(response => response.json())
.then(dryRedBottles => {
dispatch({ type: 'FETCH_DRY_REDS', dryRedBottles })});
}
}
Below is my reducer;
export default function redWineReducer (state={}, action) {
switch (action.type) {
case 'FETCH_DRY_REDS':
// debugger;
return action.dryRedBottles
default:
return state;
}
}
This is the array of objects I am attempting to iterate over;
the initial state is an object... not an array so:
export default function redWineReducer (state={}, action) {
change it to:
export default function redWineReducer (state=[], action) {
I deployed a solidity contract to my local testrpc blockchain. All my contract method tests check out, but handling Web3 transactions and updating state accordingly is giving me trouble.
When I add a user account, my next operation is to return all user accounts for my contract. and well...update my state (RegisteredAccounts).
However, through my chain of promises I'm not seeing my states update. I understand setState is asynchronous too, so how can I see my states update without refreshing the page or calling ComponentDidMount()?
Here is my Solidity Accounts Contract (the parts that I've handled so far
pragma solidity ^ 0.4.4;
contract Accounts {
mapping(address => User) public mUsers;
address[] public Users; //users whitepages
struct User {
string handle;
bytes32[] taskList;
}
function addNewUser(string _handle) returns(bool success) {
address newUserAddr = msg.sender;
//if handle not in userAddresses & the handle is not null
if (bytes(mUsers[newUserAddr].handle).length == 0 && bytes(_handle).length != 0) {
mUsers[newUserAddr].handle = _handle;
Users.push(newUserAddr);
return true;
} else {
return false;
}
}
function getUsers() constant returns(address[]) {
return Users;
}
}
Here is my App container component -- relevant parts
registerNewUser() is my problem child right now.
class App extends Component {
state = {
modalOpen: false,
SenderAddress: null,
RegisteredAccounts: [],
isRegisteredUser: false,
SenderTaskList: [], //not set
AccountsCtrct: null,
web3: null
}
//#region APP METHODS
componentWillMount() {
// Get network provider and web3 instance. -- See utils/getWeb3 for more info.
getWeb3.then(results => {
this.setState({
web3: results.web3
})
this.instantiateContracts() //instantiate contract
}).catch(() => {
console.log('Error finding web3.')
})
}
instantiateContracts() {
this.setState({
AccountsCtrct: contract(AccountsContract)
})
this.state.AccountsCtrct.setProvider(this.state.web3.currentProvider)
//Get block chain addresses --- only returns the current address selected in metamask (web3 current addr)
this.state.web3.eth.getAccounts((error, accounts) => {
this.setState({
SenderAddress: accounts[0]
})
//INIT ACCOUNTS CONTRACT
var acctDeployed = this.state.AccountsCtrct.deployed()
acctDeployed.then((instance) => {
return instance.getUsers();
}).then((res) => {
this.setState({
RegisteredAccounts: res
})
if (this.state.RegisteredAccounts.includes(this.state.SenderAddress)) {
this.setState({
isRegisteredUser: true
})
}
})
})
}
registerUser = (handle) => {
var acctInstance
this.state.AccountsCtrct.deployed().then((inst) => {
//add current user to this account
acctInstance = inst
return acctInstance.addNewUser(handle, {
from: this.state.SenderAddress
});
}).then(() => {
//now we added our user -- update registeredAccounts setState
//pass response users array to promise
return acctInstance.getUsers()
}).then(res => {
this.setState({
RegisteredAccounts: res
})
if (res.includes(this.state.SenderAddress)) {
this.setState({
isRegisteredUser: true
})
}
})
}
toggleModal = () => {
this.setState(prevState => ({
modalOpen: !prevState.modalOpen
}));
}
//#endregion
render() {
return (
<div className="App">
<nav className="navbar pure-menu pure-menu-horizontal">
Truffle Box
{
!this.state.isRegisteredUser
? <a style={navLink} onClick={ this.toggleModal } href="#" className="pure-menu-heading pure-menu-link">Register</a>
: null
}
</nav>
<ModalUserNav visible={this.state.modalOpen}
toggleModal={this.toggleModal}
isRegistered={this.state.isRegisteredUser}
registerUser={this.registerUser} />
);
}
}
Last my Child component
class ModalUserNav extends Component {
state = {
unpl: "UserName",
pwpl: "Password",
errorCode: 'Registration Failed',
errorVisible: false
}
handleOnChangePL = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
handleSubmit = () => {
if (this.state.unpl !== "") {
this.props.registerUser(this.state.unpl)
this.props.toggleModal();
} else {
//if the input is empty update the error code and show
console.log('registration failed!')
this.setState({
errorCode: 'REGISTRATION ERR: empty handles are not allowed!',
errorVisible: true
})
}
}
render() {
return (
<section>
<Modal visible={this.props.visible} effect="fadeInUp">
<div className="pure-form">
<fieldset style={modalFormView}>
<legend style={{fontSize: "18px"}}><b>Register now. All you need is a handle!</b></legend>
<div className="flexContainer">
<input style={{marginTop: "7px", height: "2.6em", marginLeft: "5px", marginRight: "5px"}} type="text" name="unpl" placeholder={this.state.unpl} onChange={(event) => {this.handleOnChangePL(event)}} value={this.state.unpl} />
<button style={btnStyle} type="submit" className="pure-button pure-button-primary" onClick={() => {this.handleSubmit()}}><b>Register</b></button>
</div>
</fieldset>
</div>
</Modal>
</section>
)
}
}
In short, I want to follow up my 2 asynchronous tasks (addNewUser, getUsers) with a setState so I can automatically change my UI without refreshing. So what am I doing wrong?
You should move instantiateContracts to setState because setState does not update data immediately. https://reactjs.org/docs/react-component.html#setstate
this.setState({
web3: results.web3
}, () => {
this.instantiateContracts() //instantiate contract
})
Update 1: About registerUser: It should be
this.setState({
RegisteredAccounts: res
}, () => {
if (res.includes(this.state.SenderAddress)) {
this.setState({
isRegisteredUser: true
})
}
})