ReactJs : How to prevent componentWillUpdate from re-rendering multiple times - javascript

I am creating a simple Todo App, I am using componentDidMount to display the data from the database. But the problem is, Once I add a new data the data gets stored but it doesn't display on to the page unless I refresh it.
Then I came across componentDidUpdate. It works perfectly, But it re-renders multiple times, What I mean is it keeps requesting the server to check for new data.
I am using Express for backend
So could anyone tell me how to prevent this ? or if there is any better solution?
Here is the current code:
class Navbar extends Component {
state = {
userArray: [],
username: "",
email: ""
};
//Storing the Data
addBtn = e => {
e.preventDefault();
var data = {
username: this.state.username,
email: this.state.email
};
fetch("/user", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
})
.then(response => {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
})
.then(data => {
console.log(data);
if (data === "success") {
console.log("Yay");
}
})
.catch(err => {
console.log(err);
});
console.log(this.state.userArray);
};
componentDidMount() {
this.displayData();
}
componentWillUpdate() {
this.displayData();
}
//Displaying the Data
displayData() {
fetch("/user")
.then(data => data.json())
.then(data => {
this.setState({
userArray: data
});
});
}
//Handling the input values
logChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};

So, let's try and understand why there was a lot of calls to the server.
When componentDidMount is created, you called displayData, which then setState. As soon as setstate is called, it calls componentDidUpdate which calls displayData again, which then calls setState. And the loop goes on (probably till you run out of memory).
You could try this class:
import React from 'react';
export default class Navbar extends React.Component {
state = {
userArray: [],
username: '',
email: ''
};
componentDidMount() {
this.displayData();
}
addBtn = e => {
e.preventDefault();
var data = {
username: this.state.username,
email: this.state.email
};
fetch('/user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
return response.json();
})
.then(data => {
console.log(data);
if (data === 'success') {
this.displayData();
}
})
.catch(err => {
console.log(err);
});
};
displayData() {
fetch('/user')
.then(data => data.json())
.then(data => {
this.setState({
userArray: data
});
});
}
}
Basically, what I did was I removed the call to displayData in componentDidUpdate and then called the displayData when the ApI call was successful

componentDidMount is the right place to load the first time, and then, after creating new Todo, you need to refresh the list right after POST request complete
.then(data => {
console.log(data);
if (data === "success") {
console.log("Yay");
this.displayData();
}
})
To impove performace, you should return new Todo record after POST, so you only push it to the list userArray in state, no need to fetch whole list again

For this, you need to first understand how componentDidMount and componentWillUpdate works in React.
They are lifecycle methods of react.
componentDidMount gets called after the component is mounted. It gets called only once and never gets called again if not unmounted and mounted again.
componentWillUpdate gets called every time state changes and re-rendering is going to happen.
As commented by #trixn:
You need to call this.setState() in addBtn when you have the data instead of repeatedly calling this.displayData()

Everyone gave the right answer , But there is a tiny mistake.
You should call the displayData() outside of the if condition
.then(data => {
console.log(data);
if (data === "success") {
console.log("Yay");
}
this.displayData();
})

Related

I don't know how to get data from fetch correctly

I have a Users class where I want to get data from the server for later writing it to state and passing data from state to the child component
export default class Users extends Component {
constructor(props) {
super(props);
this.state = {
users: this.getUsers(),
};
}
getUsers = async () => {
await return fetch(`http://localhost:3001/users`, {
method: 'POST',
accept: 'application/json'
}).then(res => {
if(res.ok) {
res.json();
}
})
}
}
this is what the console shows me when I output data about this.state.users
I tried to look for similar situations, but I didn't find anything worthwhile, so I ask for help here. I would be grateful for any advice or help. I'm only learning asynchrony in js
if you use async await, you don't have to pass callback function, just await the promises and update the state incase of successful response.
getUsers = async () => {
try {
const response = await fetch(`http://localhost:3001/users`, {
method: 'POST',
accept: 'application/json'
});
const users = await response.json();
this.setState({ users });
} catch (error) {
console.log(error);
}
}
and instead of calling getUsers function from the constructor, use componentDidMount
componentDidMount() {
this.getUsers();
}
and your state should be initially null or an empty array
this.state = {
users: []
};
Add componentDidMount and call getUsers and set state.
this.state = {
users: [],
};
getUsers = async () => {
return await fetch(`http://localhost:3001/users`, {
method: 'POST',
accept: 'application/json'
}).then(response => response.json())
.then(res => { this.seState({ users: res })})
.catch(e => { console.log(e)})
}
componentDidMount = () => {
this.getUsers()
.catch(e => console.log(e)
}

How can I run a code after another has already ended running?

In less than 1 second, I have to post something into a JSON file and then get that exact data updated. When running the code, it seems that I post the data in the JSON file but when I try to get it, it gets the old data, not the updated one.
How can i run get method after post method has ended running?
I ran this post method
import Game from "./components/Game/Game";
class App extends React.Component {
postUserInfo(){
fetch("http://localhost:8080/api/users" , {
method: "post" ,
mode: "cors",
headers: {
"Content-type": "application/json",
},
body:JSON.stringify({username:this.state.userInput, bestscore:0})
})
.then((res) => res.json())
.then((data => {console.log(data)}))
.catch((error) => {
console.log(error);
});
}
}
and then in the other class I run this get method right after the post method
class Game extends React.Component {
getUserInfo() {
fetch("http://localhost:8080/api/users" , {mode: "cors"})
.then((res) => res.json())
.then((data => {
this.setState({ usersInfoArray: data})
var _userid = data[data.length-1].id;
var _username = data[data.length-1].username;
var _bestscore = data[data.length-1].bestscore;
this.setState({ userid: _userid, username: _username, bestscore: _bestscore});
}))
}
}
componentDidMount(){
this.getUserInfo();
this.render();
}
I guess you may call postUserInfo() in another component then move to new Component
then after component mount, call getUserInfo()
Am i right?
If so
your navigate to(or create) other component logic must in postUserInfo()
near
.then((data => {console.log(data)}))
Probably the easiest way to do this is to maintain a dataPosted flag in App's state and pass that flag to the Game component. If the dataPosted flag is true, then load the data.
App.jsx
import Game from "./components/Game/Game";
class App extends React.Component {
constructor() {
super();
this.state = { dataPosted: false }
}
postUserInfo(){
fetch("http://localhost:8080/api/users" , {
method: "post" ,
mode: "cors",
headers: {
"Content-type": "application/json",
},
body:JSON.stringify({username:this.state.userInput, bestscore:0})
})
.then((res) => res.json())
.then(data => {
this.setState({ dataPosted: true })
})
.catch((error) => {
console.log(error);
});
}
}
render() {
<Game dataPosted={this.state.dataPosted} />
}
}
Game.jsx
class Game extends React.Component {
componentDidUpdate() {
if (this.props.dataPosted) {
this.getUserInfo();
}
}
getUserInfo() {
fetch("http://localhost:8080/api/users" , {mode: "cors"})
.then((res) => res.json())
.then((data => {
this.setState({ usersInfoArray: data})
var _userid = data[data.length-1].id;
var _username = data[data.length-1].username;
var _bestscore = data[data.length-1].bestscore;
this.setState({ userid: _userid, username: _username, bestscore: _bestscore});
}))
}
}

Problem with nested fetch request in React

New to React, I'm currently trying to create a data table with data from an API.
I want to have a first fetch, and then run another with response from the first (id) in order to complete my table.
Here is my code :
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {},
data: []
};
}
componentDidMount() {
this.setState({
user: JSON.parse(localStorage.getItem('user'))
}, function () {
this.loadAllObjectsInfo()
});
}
// Fetch all object info in order to fill the table
loadAllObjectsInfo() {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'bbuser': this.state.user.userId,
'bbtoken': this.state.user.secret
},
};
fetch('https://xxxxx/api/objects', requestOptions)
.then(response => response.json())
.then((data) => {
this.setState({ data: data })
})
}
With this code, I have the data I want to render my table but I need to run another fetch to get other info with the id coming from the first request.
How can I do that nested fetch request ?
Thanks a lot,
Matthieu
You can easily manage this with async/await:
async loadAllObjectsInfo() {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'bbuser': this.state.user.user
'bbtoken': this.state.user.secret
},
};
let response = await fetch('https://xxxxx/api/objects', requestOptions);
let data = await response.json();
// here is another fetch - change to fit your request parameters (this is just example)
let info = await fetch('https://xxxxx/api/objects/' + data.id);
this.setState({ data });
}
You can read more about async function.
#JourdanM, you should return a new fetch request from one of the then handlers. I've made a simple snippet for you. There are no data validators and spinners. This is a simple showcase. =)
A fetch request returns a promise, and you can chain promises by simply returning them from the then handlers. Here is a good article about it, it has great examples: https://javascript.info/promise-chaining
function fetchUser (user) {
return fetch(`https://api.github.com/users/${user.login}`)
}
class User extends React.Component {
state = {
user: null
}
componentDidMount () {
fetch("https://api.github.com/users")
.then(response => response.json())
.then(users => fetchUser(users[0]))
.then(response => response.json())
.then(user => {
this.setState({user})
})
}
render () {
return (
<div>
<pre>{JSON.stringify(this.state.user, null, 2)}</pre>
</div>
)
}
}
ReactDOM.render(<User />, document.querySelector("#root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can write the code as below.
fetch('https://xxxxx/api/objects', requestOptions)
.then(response => response.json())
.then((res1) => {
fetch('https://xxxxx/api/objects', requestOptions)
.then(response => response.json())
.then((res2) => {
this.setState({ data: res2 });
});
});
Hope this will work for you!
You can also use axios like below
axios.post(url, data, header).then(res => {
if(res.status === 200){
console.log('1st data')
axios.post(url, data, header)
.then(response => {
if (response.status === 200) {
console.log('2nd data')
} else {
console.log('2nd error')
}
});
}else{
console.log('1st error')
}
});

Call Function(that render component) in async Function

Good day everyone.
I have problem with this piece of code:
It's 2 function:
1.renderModal() - it's responsible for rendering ModalSuccess at the moment where data sucesfully will be added to databbase (to inform user about correctly fill form.
Component ModalSuccess when call it's render modal.
2.submitToServer - it's sending all data from redux-form to API.
In end of try, i trying call function renderModal.
How can i make it correctly?
function renderModal() {
return (
<div>
<ModalSuccess/>
</div>
);
}
//async function send to server
export async function submitToServer(values) {
//FUND
try {
let response = await fetch('endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeader()
},
body: JSON.stringify(values),
});
let responseJson = await response.json();
return responseJson;
renderModal();
} catch (error) {
console.error(error);
}
I call submitTo server in 2 places:
1.
export var submit =(values) =>{
let isError =false;
if (isError) {
// throw new SumissionError(error);
} else{
return submitToServer(values)
.then(data =>{
if (data.errors) {
console.log(data.errors);
throw new SubmissionError(data.errors);
} else{
console.log(values)
console.log('server added data to database');
}
});
}
}
2.
<form onSubmit={handleSubmit(submitToServer)}>
I think you can restructure your code a bit better. Instead of returning the modal you can just mount the modal once and control its visibility leveraging the state.
Take a look at how I think your component should be structured.
class Comp extends React.Component {
state = {
isOpen: false
};
submitToServer = async values => {
try {
let response = await fetch("endpoint", {
method: "POST",
headers: {
"Content-Type": "application/json",
...authHeader()
},
body: JSON.stringify(values)
});
let responseJson = await response.json();
this.setState({ isOpen: true });
return responseJson;
} catch (error) {
console.error(error);
}
};
render() {
/* your component */
<ModalSuccess isOpen />;
}
}
As it stands your renderModal() invocation will never register since you are returning once the response it has been returned.
What you'd need to is something like this:
let responseJson = await response.json();
if (responseJson) {
renderModal();
}

React-native Invoke API from one common handler class

I have a common function which uses FETCH to get data from external web service. this function will be invoked and parsed in multiple screens under componentDidMount(). instead of repeating the same code at multiple places, I put below under a common class, but unfortunately, data is not returned to those screens.
Common Function
export function convertValue(fromVal, toVal) {
var requestObj = {};
let apiEndpoint = '<target endpoint>'
return fetch(apiEndpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
.then((response) => response.json())
.then((responseJson) => {
return responseJson;
})
.catch((error) => {
console.log('Error: ', error);
});}
Sample call below, and no pop-up when screen loaded.
componentDidMount () {
AsyncStorage.getItem('user_default').then((value) => {
this.setState({userDefault: value});
}).then((value) => {
var sample = convertValue('A', 'B');
Alert.alert(
'Success',
JSON.stringify(sample),
[
{text: 'OK',
onPress: () => {
console.log('.');
}}
]
)
});}
componentDidMount () {
AsyncStorage.getItem('user_default').then((value) => {
this.setState({userDefault: value});
convertValue('A', 'B').then((json)=>{
alert(json)
})
})}
This might work for you. The problem was improper chaining of asynchronous calls.
Nick is right, found the right way -
convertValue('A', 'B')
.then((responseJson) => {
this.setState({returnedValue: responseJson.convertedValue});
});

Categories

Resources