componentDidUpdate keeps getting called - reactjs

componentDidUpdate () {
this.showPosts();
}
showPosts = async () => {
var userID = await AsyncStorage.getItem('userID');
fetch(strings.baseUri+"getPostWithUserID", {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"user_id": userID
})
})
.then((response) => response.json())
.then((responseJson) => {
let jsonObj = JSON.parse(JSON.stringify(responseJson));
if (jsonObj.status=="true") {
this.setState({
data: responseJson.data,
imageSrc: responseJson.data.imgSrc,
});
}
else {
this.setState({show: false});
}
})
}
I'm calling showPosts function from componentDidUpdate to show my updated Flatlist. But componentDidUpdate keeps getting called. Should I use shouldComponentUpdate ?
========================== UPDATED CODE ============================
This is from Home Screen
async componentDidMount () {
this._isMounted = true;
await this.showPosts();
}
componentDidUpdate () {
this.showPosts();
}
showPosts = async () => {
try {
var userID = await AsyncStorage.getItem('userID');
fetch(strings.baseUri+"getPostWithUserID", {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"user_id": userID
})
})
.then((response) => response.json())
.then((responseJson) => {
let jsonObj = JSON.parse(JSON.stringify(responseJson));
if (jsonObj.status=="true") {
this.setState({
data: responseJson.data,
imageSrc: responseJson.data.imgSrc,
});
}
else {
if (this._isMounted) {
this.setState({show: false});
}
}
})
.catch((error) => {
console.error(error);
});
}
catch (err) {
console.warn(err);
}
}
componentWillUnmount () {
this._isMounted = false;
}
This is Image Descrpiption screen from where I'll navigate back to Home Screen
postData = async () => {
this.setState({loading: true});
var location = await AsyncStorage.getItem('location');
var path = await AsyncStorage.getItem('path');
var post_type = await AsyncStorage.getItem('post_type');
var userId = await AsyncStorage.getItem('userID');
var newPath = path.split("/");
var imageName = newPath[newPath.length-1];
const formData = new FormData();
var media = {
uri: path,
name: imageName,
type: 'image/jpg',
};
formData.append('image', media);
formData.append('user', userId);
formData.append('description',this.state.description);
formData.append('location',"usa");
formData.append('post_type',post_type);
formData.append('userprofile_picture',imageName);
fetch(strings.baseUri+"addPosts",{
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' },
body: formData,
})
.then((response) => response.json())
.then((responseJson) => {
let jsonObj = JSON.parse(JSON.stringify(responseJson));
if (jsonObj.status=="true") {
this.props.navigation.popToTop()
&& this.props.navigation.navigate('Home'); // This navigates me to the HomeScreen
}
else {
}
})
.catch((error) => {
console.error(error);
});
}

ComponentDidUpdate is an update LifeCycle Hook, this will get triggered when there is something is changed in the component State or Props.
Coming to your code:
You are calling a handler showPosts to setState, that will again trigger the update lifecycle.
This will lead to an infinite loop.
Solution
If you want to load the posts only in the first time, then move to Creational Life Cycle hook ( componentDidMount ).
componentDidMount() { // This just gets called once in creational lifecycle //
this.showPosts(); }
if you want this to always have the latest data, then there are two ways
Updating component is in the same component tree branch:, In this case , it's easy to achieve this you can pass the state from the updating component down to child component has props, your job is done OR if they are siblings then do a level up you can move the state one level up and have it coming in has props.
Updating component is in the different component tree branch: I recommend using REDUX, this is the main use of redux.
shouldComponentUpdate Yes definitely you can use this to verify the data and the load if needed, but be careful by using this your components update depends on the code in this.
Please check https://reactjs.org/docs/react-component.html#shouldcomponentupdate

You just need to call this in that way if you do in ComponentDidUpdate and update state in the method call by ComponentDidUpdate then a infinite loop start.
componentDidMount () {
this.showPosts();
}
================EDITED=======================
If you want to use only ComponentDidUpdate then you can use it like.
componentDidUpdate(prevProps, prevState) {
// only update if not match I don't know what's your data is so add a
// simple check like we use for strings.
if (prevState.data !== this.state.data) {
this.showPosts();
}
}
Just use prevState to match.

You can do this too
Common parent component
Create a new component say Posts.
import React, { Component } from 'react';
import HomeScreen from '../../HomeScreen';
import ImageDescription from '../../ImageDescription';
class Posts extends Component {
constructor(){
super();
this.state = {
dataEditted: false;
}
}
newDataHandler = () =>{
this.setState({dataEditted:true}); // this is flag to identify that there is change in data //
}
resetnewDataHandler = () =>{
this.setState({dataEditted:false}); // this handler is used to reset the falg back to initial //
}
render () {
const homeScreen = <HomeScreen editted={this.state.editted} resetHandler={this.resetnewDataHandler}/>;
const imageDescription = <ImageDescription newdataHandler={this.resetnewDataHandler}/>
return (
<div>
{homeScreen}
{imageDescription}
</div>
)
}
}
export default Posts;
This component is going to serve as a bridge to move data between.
Whenever there is fresh data in ImageDescription Component use the newDataHandler passed has props to update the common parent, then the dataEditted will be updated and passed has props to homeScreen Component, now in this componentDidUpdate of homeScreen check whether its true, then call this.showPosts() and also call resetnewDataHandler.

componentDidUpdate() is called when the state is changed (calling setState()) and if you do it inside the showPosts that is also inside the componentDidUpdate() you are creating an infinite state updating.

Related

Fetch not working after switch from class to functional component

I'm converting some class components to functional.
Console.log(data) returns the expected output but then once i try to set it using useState and check the value, it returns an empty array.
On the class component its working by using the state.
Functional Component ( Not Working )
const [submitting, setSubmitting] = useState(false)
const [watingForResult, setWaitingForResult] = useState(false)
const [submission, setSubmission] = useState([]);
const [scoringResults, setScoringResults] = useState([]);
+
function submitSubmission() {
setSubmitting(true);
setResult([]);
setError('');
let data = {
code: btoa(code),
language: { name: language.name },
users: { username: parseLocalJwt().username },
problem: { name: textToLowerCaseNoSpaces(problem.name) }
}
fetch(URL + '/submission', {
method: 'POST',
body: JSON.stringify(data),
headers: new Headers({
...getAuthorization(),
'Content-Type': 'application/json'
})
}).then(res => res.json()).then(data => {
setSubmitting(true);
setWaitingForResult(true);
setSubmission(data);
console.log('submitSubmission' + JSON.stringify(data));
window.secondsWaiting = new Date().getTime();
window.resultsListener = setInterval(fetchForResults(data.id), 1000);
});
}
...
function fetchForResults() {
console.log('data on fetchForResults' + submission.id)
}
...
<button
type="button"
onClick={submitSubmission}>
Submit!
</button>
console.log screenshot
Class component ( Working )
super(props);
this.state = {
problem: [],
sentSubmission: {
submitting: false,
waitingForResults: false,
submission: [],
scoringResults: []
},
results: {
loaded: false,
result: [],
error: ''
},
language: { mode: 'java', name: 'Java' },
code: ``
}
submitSubmission() {
this.setState({ sentSubmission: { submitting: true }, results: { result: [], error: '' } })
let data = {
code: btoa(this.state.code),
language: { name: this.state.language.name },
users: { username: parseLocalJwt().username },
problem: { name: textToLowerCaseNoSpaces(this.state.problem.name) }
}
fetch(URL + '/submission', {
method: 'POST',
body: JSON.stringify(data),
headers: new Headers({
...getAuthorization(),
'Content-Type': 'application/json'
})
}).then(res => res.json()).then(data => {
this.setState({ sentSubmission: { submitting: true, waitingForResults: true, submission: data } })
window.secondsWaiting = new Date().getTime();
window.resultsListener = setInterval(this.fetchForResults, 1000);
});
}
Some portion of the code is missing but, since you switched from a class component's state to a functional component state, I guess the issue is related to how you are using your state.
In functional components, when you set your state, you are changing the whole object in your state. So, when in your code you do setSentSubmission({ submitting: true });, your state becomes:
previousState = {
submitting: false,
waitingForResults: false,
submission: [],
scoringResults: []
}
nextState = {
submitting: true
//you lost watingForResults, submissions and scoring results
}
When using useState it is always suggested to decompose your object in different states:
const [submitting, setSubmitting] = useState(false)
const [watingForResult, setWaitingForResult] = useState(false)
//and so on...
//And then update them singularly:
//This...
setSentSubmission({ submitting: true });
//... become this
setSubmitting(true);
Finally, side note on lists. If you need to add a single element to a list in your state you can do:
setListState(currentList => [...currentList, newElement])
If this approach doesn't fit your use case and you need a more complex state management system I suggest you to look at useReducer (link).
Update
To print the content of your fetch you have either 2 choices.
Print directly the content retrieved in your fetch function
fetch('http://myendpoint.com/mydata.json')
.then(response => response.json())
.then(data => console.log(data));
If you need to do some processing to your data and you want to print what is the content of your updated state you can just use a useEffect hook. For instance, if you want to print the content of your scoringResult state you just write:
useEffect(() => {
console.log(scoringResult)
},[scoringResult])
This hook will be triggered every time scoringResult is updated (plus once when the component was mounted in the beginning)
Problem with your code is this:
setSentSubmission(...)
console.log(data);
console.log(sentSubmission.submission)
You can not set state and then to expect immediately to access that new state, new state can be accessed after component rerenders(in next iteration), all this is because you are reading sentSubmission.submission from closure which will be recreated only when component rerenders. So everything works fine with you code, you are just doing logging in a wrong place, move that log outside of the submitSubmission and you will see that state is updated successfully and that log will be printed after component rerenders(state updates).
When function submit() call, you set new value for sentSubmission.
Try this instead:
setSentSubmission((prevState) => (…prevState, { submitting: true }));

A lot of unnecessary requests

I have Posts component:
class ProfilePosts extends React.Component {
constructor(props){
super(props);
this.state = {
posts: []
}
}
getData = async (url) => {
const res = await fetch(url, {
method: 'GET',
headers: {
'Authorization' : `${window.localStorage.getItem('token')}`
}
});
return await res.json();
}
getPosts = async () => {
if (window.location.pathname === '/profile/undefined%7D')
{
window.location.pathname = `/profile/${window.localStorage.getItem('id')}`
}
await this.getData(`https://api.com/posts/${window.location.pathname.slice(9)}`)
.then(data => {
this.setState({posts: data}
);
})
}
componentDidMount(){
if (window.localStorage.getItem('id') != ''){
this.getPosts();
}
}
componentDidUpdate(){
if (window.localStorage.getItem('id') != ''){
this.getPosts();
}
}
renderItems(posts){
return Object.values(posts).map(post => {
return (
<ProfilePost likes={post.likes} comments={post.comments} userId={window.localStorage.getItem('id')} token={window.localStorage.getItem('token')} Postid={post.id} key={post.id} sender={post.sender} content={post.content} time={post.sent_time}/>
)
});
}
render() {
const {posts} = this.state;
const items = this.renderItems(posts);
return(
<div className="profile-posts">
{items}
</div>
);}
}
export default ProfilePosts;
I don't understand what is the reasons for re-rendering this component so many times (I don't know the actual number but it's huge). Therefore, a lot of requests to the database, and so on. How can I fix this?
I tried using another state but that didn't work for me. If you press the button, the state changed and then I checked if the state changed then I call this.getPosts() and then set the state to false again. It caused Error: Maximum update depth exceeded.
Your componentDidUpdate runs every time the component updates, after render, so it's calling this.getPosts();, which in turn calls setState, which results in another re-render and update, and so on.
Remove the componentDidUpdate entirely and let the componentDidMount method (which implements the same logic) alone take care of things.

How to pass parameter in api url based on selected option?

I would like to fetch data based on selected job id.
Job id should be selected from drop down list.
Once the job id will be selected, api url should be adjusted with the property job id.
I added the select option and fetch statement. However I cannot pass the parameter in the url.
const jsonify = res => res.json();
var chart_request = new Request(
`https://xxxx.com/prod/job-id?job_id_number=${this.state.selectVal}`,
{
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json'
})
}
);
const dataFetch = fetch(chart_request).then(jsonify);
export default class ZYZ extends Component {
constructor(props) {
super(props);
this.state = {
selectVal : "650"
}
}
setSelectValue = (event) => {
this.setState({
selectVal: event.target.value
});
}
render() {
return
<React.Fragment>
<select value={this.state.selectVal} onChange={this.setSelectValue}>
<option value = "650">650</option>
<option value = "1052">1052</option>
</select>
<p>{this.state.selectVal}</p>
</React.Fragment>
}
}
You can't use your state outside your class.
But if you insist, you can use componentDidMount & componentDidUpdate lifecycle methods on initial load and every select respectively, and pass the id as an argument to fetchData & chart_request as follow:
componentDidMount() {
// calling fetch, resolving the promise, and storing the data in state
fetchData(this.state.selectVal).then(data => this.setState({ data }));
}
componentDidUpdate() {
// same as above
fetchData(this.state.selectVal).then(data => this.setState({ data }));
}
The modifications for chart_request & fetchData:
const chart_request = id =>
fetch(
`https://xxxx.com/prod/job-id?job_id_number=${id}`
)
.then(response => response.json()) // instead of "jsonify"
.then(data => JSON.stringify(data))
.catch(error => error);
const fetchData = id => chart_request(id);
I have modified the SandBox so you can test your output.

AsyncStorage.getItem in react native not working as expected

I am trying to fetch data using AsyncStorage. whenever i call my action creator requestData and do console on the data which is passed , i get something like below .I have two version of getItem .In both the version i get useless value for property field . Property value should be readable
{"fromDate":"20160601","toDate":"20160701","property":{"_40":0,"_65":0,"_55":null,"_72":null},"url":"/abc/abc/xyz"}
async getItem(item) {
let response = await AsyncStorage.getItem(item);
let responseJson = await JSON.stringify(response);
return responseJson;
}
async getItem(item) {
try {
const value = AsyncStorage.getItem(item).then((value) => { console.log("inside componentWillMount method call and value is "+value);
this.setState({'assetIdList': value});
}).then(res => {
return res;
});
console.log("----------------------------value--------------------------------------"+value);
return value;
} catch (error) {
// Handle errors here
console.log("error is "+error);
}
}
componentWillMount() {
requestData({
fromDate: '20160601',
toDate: '20160701',
assetId: this.getItem(cmn.settings.property),
url: '/abc/abc/xyz'
});
}
You are getting property as a promise, you need to resolve it.
Try to use something link that.
assetId: this.getItem(cmn.settings.property).then((res) => res)
.catch((error) => null);
Since AsyncStorage is asynchronous in nature you'll have to wait for it to return the object AND THEN call your requestData method; something like the following -
class MyComponent extends React.Component {
componentWillMount() {
this.retrieveFromStorageAndRequestData();
}
async getItem(item) {
let response = await AsyncStorage.getItem(item);
// don't need await here since JSON.stringify is synchronous
let responseJson = JSON.stringify(response);
return responseJson;
}
async retrieveFromStorageAndRequestData = () => {
let assetId = await getItem(cmn.settings.property);
requestData({
fromDate: '20160601',
toDate: '20160701',
assetId,
url: '/abc/abc/xyz'
}) ;
}
// rest of the component
render() {
// render logic
}
}

flux controller-view not subscribing for the event change in store

I want to show a list of messages when the page is loaded. i call action and addChangeListener to subscribe for changes in componentDidMount, hoping i can get the data back from server from store, though addChangeListener is called, but the callback is not being invoked. It seems like this.on('change', callback) in store is not working as it should. does anyone know what is the issue of my code? Im following the example on flux's repo
Also, where is the proper place to make api calls for backend data in store? is ok if i do it in getter in a store class?
thanks.
components/MessageList.js
class MessageList extends Component {
constructor(props) {
super(props)
this.renderMessage = this.renderMessage.bind(this)
this.state = {
loaded: false,
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
})
}
}
componentDidMount() {
messageActions.getAll()
messageStore.addChangeListener(this._onChange)
}
_onChange() {
console.log('on change') // <-- not being called from this point
this.setState({
dataSource: this.state.dataSource.cloneWithRows(messageStore.getAll())
})
}
//...
}
stores/MessageStore.js
let _messages = {}
function create(text, latitude, longitude) {
fetch('http://localhost:3000/api/message', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: text,
latitude: latitude,
longitude: longitude
})
})
.then(r => r.json())
.then(data => {
this.props.navigator.pop()
})
.done()
}
function getAllMessages(callback) {
fetch('http://localhost:3000/api/message')
.then((res) => res.json())
.then((data) => {
callback(data)
})
.done()
}
class MessageStore extends EventEmitter {
constructor() {
super()
}
emitChange() {
this.emit('change')
}
addChangeListener(callback) {
console.log('here') // <-- works
// this.on('change', callback) // <-- not working
this.on('change', () => console.log('helloooo')) // <-- not working
}
getAll() {
return _messages
}
}
dispatcher.register(action => {
switch(action.actionType) {
case 'MESSAGE_CREAT':
text = action.text.trim();
if (text !== '') {
create(text, action.latitude, action.longitude)
messageStore.emitChange()
}
break
case 'MESSAGE_ALL':
console.log('store..');
getAllMessages(data => _messages = data)
messageStore.emitChange()
}
})
const messageStore = new MessageStore()
export default messageStore
You cannot call the change listener in the store, you are only setting it up there. Try adding:
addChangeListener(callback) {
this.on('change', callback);
}
Instead of:
addChangeListener(callback) {
console.log('here') // <-- works
// this.on('change', callback) // <-- not working
this.on('change', () => console.log('helloooo')) // <-- not working
}
When a change happens, the changeListener in the store will trigger the change listener in MessageList:
messageStore.addChangeListener(this._onChange)
Then the _onChange function will be called.

Resources