Fetch request is not updating the state - reactjs

I have an react application connected to a database. Currently the app takes the database when it mounts and uses that to populate the state. The apps allows someone to post to the database. So far that works.
The issue is that I want the new posted content to be seen by the user. As it is the content only populates after I reload the page. I tried to repeat the coding in the componentDidMount() in a function that runs after the POST request, but for someone reason that is not working.
class App extends Component {
state = {
notes: [],
folders: [],
//noteID: 0,
//folderID: 0
};
componentDidMount() {
Promise.all([
fetch(`${config.API_ENDPOINT}/notes`),
fetch(`${config.API_ENDPOINT}/folders`)
])
.then(([notesRes, foldersRes]) => {
if (!notesRes.ok)
return notesRes.json().then(e => Promise.reject(e))
if (!foldersRes.ok)
return foldersRes.json().then(e => Promise.reject(e))
return Promise.all([
notesRes.json(),
foldersRes.json(),
])
})
.then(([notes, folders]) => {
this.setState({ notes, folders })
})
.catch(error => {
console.error({ error })
})
}
pageReload = () =>{
//console.log('pageReload ran');
Promise.all([
fetch(`${config.API_ENDPOINT}/notes`),
fetch(`${config.API_ENDPOINT}/folders`)
])
.then(([notesRes, foldersRes]) => {
if (!notesRes.ok)
return notesRes.json().then(e => Promise.reject(e))
if (!foldersRes.ok)
return foldersRes.json().then(e => Promise.reject(e))
return Promise.all([
notesRes.json(),
foldersRes.json(),
])
})
.then(([notes, folders]) => {
this.setState({ notes, folders })
})
.catch(error => {
console.error({ error })
})
}
folderSubmit = (f) => {
//console.log("folderSubmit ran " + f);
const newFolder = { "name" : f };
const postfolder = {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(newFolder)
};
fetch(`${config.API_ENDPOINT}/folders`, postfolder).then(this.pageReload())
}

It looks like you are not setting your state after post. see below where you need to set your state.
folderSubmit = (f) => {
//console.log("folderSubmit ran " + f);
const newFolder = { "name" : f };
const postfolder = {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(newFolder)
};
fetch(`${config.API_ENDPOINT}/folders`, postfolder)
.then(response => response.json())
.then(data => this.setState(data));
}

Related

React renders component many times when fetching data

I'm using fetch api so my goal is to fire a POST request and then store the result received from this call in a state. This is the code that I use:
interface IPreviewFile {
file: IFile;
}
export default function PreviewFileModal({ file }: IPreviewFile) {
const source = useSelector((state: AppState) => state.source);
const project = useSelector((state: AppState) => state.project);
const data = {
fileName: file.path,
accountName: source.Name,
bucket: source.bucket,
id: source.id
};
useEffect(() => {
Promise.all([
fetch(`${API_URL}/api/project/${project.id}/filepreview`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
])
.then(async ([aa]) => {
const a = await aa.json();
return [a];
})
.then((responseText) => {
setStringArray(readString(responseText[0].value).data);
})
.catch((err) => {
console.log(err);
});
}, [project.id, data]);
console.log(stringArray);
return (
<>
<div></div>
</>
);
}
The console.log(stringArray); prints in the console all the time after delay of 2-3 seconds. As you can see, I use Promise in order to avoid this but for some reason it still happens. Any ideas what causes the re-rendering all the time and how to fix it?
I've tried changing the code a bit to avoid re-rendering the component due to the data variable added as a dependency to useEffect. I don't see any reference to stringArray, so i've added it as a state variable.
export default function PreviewFileModal({ file }: IPreviewFile) {
const source = useSelector((state: AppState) => state.source);
const project = useSelector((state: AppState) => state.project);
const [stringArray, setStringArray] = useState("");
useEffect(() => {
fetch(`${API_URL}/api/project/${project.id}/filepreview`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName: file.path,
accountName: source.Name,
bucket: source.bucket,
id: source.id
})
})
.then(res => res.json())
.then((result) => {
result && setStringArray(readString(result.value).data);
})
.catch((err) => {
console.log(err);
});
}, [project.id]);
console.log(stringArray);
return (
<>
<div></div>
</>
);
}

ReactJS: wait until state is filled before making next call

I have quite a big function that retrieves a bunch of information about Spotify playlists. Because the data is paginated I have a to make a couple of calls and append data to the state recursively. After that's done, I want to pass the state along with a POST request to another endpoint, to make some calculations. The returned values are then stored in state as well.
const fetchPlaylist = (playlistId) => {
showLoading()
setTitles([])
setArtists([])
setFeatures([])
setTSNEfeatures([])
setIds([])
setLabels([])
const getPlaylistDataRecursively = (url) => {
return fetch('/spotify/get-track-ids', {headers: {
'url': url
}})
.then(response => response.json())
.then(data => {
console.log(data)
setTitles(titles => ([...titles, ...data.title]))
setArtists(artists => ([...artists, ...data.artist]))
setFeatures(features => ([...features, ...data.features]))
setIds(ids => ([...ids, ...data.track_ids]))
if (data.next_url) {
const next_url = data.next_url.replace('https://api.spotify.com/v1', '')
return getPlaylistDataRecursively(next_url)
} else {
return fetch('/spotify/get-dimension-reduction', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(features)
})
.then(response => response.json())
.then(data => {
setTSNEfeatures(data)
})
}
})
}
return getPlaylistDataRecursively(`/playlists/${playlistId}/tracks/?offset=0&limit=100`)
.then(() => {
hideLoading()
});
}
The problem is that fetch('/spotify/get-dimension-reduction' ... ) is ran before getPlaylistDataRecursively is done filling the features state. How can I tackle this issue?

Unable to get response using fetch in React

I am trying to call 3rd party API, to fetch some data. I am getting the response in Postman, but not getting expected response when I execute my code.
I tried in 2 ways. Both ways I am getting "Promise pending".What could be the reason??
//request.js
Method 1
export const callSearchGiftsAPI = inputs => dispatch => {
dispatch(searchGifts());
let url = new URL(GIFT_SEARCH_API_URL),
params = {
apiKey: GIFT_SEARCH_API_KEY,
query: inputs.item,
country: 'us',
itemsPerPage: 3
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
return new Promise((resolve, reject) => {
setTimeout(() => resolve(
fetch(url, {
method: 'GET',
// mode: 'no-cors',
headers: {
'Content-Type': 'application/json',
Authorization: `secret ${SECRET}`
}
})
.then(res => {
if (!res.ok) {
return Promise.reject(res.statusText);
}
console.log("hi", res.json());
return res.json();
})
.then(gifts => dispatch(searchGiftsSuccess(gifts)))
.catch(err => dispatch(searchGiftsError(err)))), 500)
});
}
Method 2:
export const callSearchGiftsAPI = inputs => dispatch => {
dispatch(searchGifts());
let url = new URL(GIFT_SEARCH_API_URL),
params = {
apiKey: GIFT_SEARCH_API_KEY,
query: inputs.item,
country: 'us',
itemsPerPage: 3
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
fetch(url, {
method: 'GET',
// mode: 'no-cors',
headers: {
'Content-Type': 'application/json',
Authorization: `secret ${SECRET}`
}
})
.then(res => {
if (!res.ok) {
return Promise.reject(res.statusText);
}
console.log('result', res.json());
return res.json();
})
.then(gifts => dispatch(searchGiftsSuccess(gifts)))
.catch(err => dispatch(searchGiftsError(err)));
};
//form.js
class Form extend React.Component{
onSubmit(values) {
const inputs = Object.assign({}, values);
return this.props.dispatch(callSearchGiftsAPI(inputs));
}
//Remaining code
}
Also please note that I have installed CORS plugin in Chrome, to allow the request.If I disable it and add mode:'no-cors' I am getting as 401 unauthorized.What else am I supposed to do?
What happens is that you are creating a new Promise and returning it, but you are not waiting for it to resolve. You can either use then of the new async/await syntax to get the correct result :
onSubmit = async values => {
const inputs = Object.assign({}, values);
return await this.props.dispatch(callSearchGiftsAPI(inputs));
}
The code above will work with your first method.
Since your second method does not return anything, you will never get your result, you need to return your fetch's result and apply the code I gave above :
return fetch(url, {
This worked.
I was trying to put console.log in the wrong place and hence was not able to see the response properly.
export const callSearchGiftsAPI = inputs => dispatch => {
dispatch(searchGifts());
let url = new URL(GIFT_SEARCH_API_URL),
params = {
apiKey: GIFT_SEARCH_API_KEY,
query: inputs.item,
country: 'us',
itemsPerPage: 3
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
console.log(url);
return fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `secret ${SECRET}`
}
})
.then(res => {
console.log('result');
return res.json();
})
.then(response => {
console.log(response); // changed
dispatch(searchGiftsSuccess(response.items));
})
.catch(err => dispatch(searchGiftsError(err)));

problem with fetch in componentDidMount()

my list of users is undefined when i try to console.log it.
Maybe i didn't get something ?
I'd like to get my list of users from my api who works (tested with postman) and put it into the console next i'd like to map my users to show it on the app
class Test extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
}
}
componentDidMount() {
console.log("component did mount");
fetch("/user/list")
.then(res => {
return res.json();
})
.then(users =>
this.setState({users}, () =>
console.log("list of users => " + users)));
}
render() {
return (
<div className="form">
<ul>
{this.state.users.map((user) =>
<li key="user._id">{ user.name }</li>
)}
</ul>
</div>
);
}
} export default Test;
Thanks for help !
You are calling res.json() rather than returning res.json() from the first then on your fetch call
I've found this pattern to be helpful:
fetch(url)
.then(res => res.ok ? res.json() : Promise.reject())
As your code is now, users (the parameter in the second then would be undefined, because you are not returning anything from the first then
you have to return the res.json() to use it in the next .then()
.then(res => {
res.json();
})
should be
.then(res =>
res.json();
)
Or
.then(res => {
return res.json();
})
https://javascript.info/promise-chaining
You should be passing your res into res.json() and returning the results into your state.
componentDidMount() {
console.log("component did mount");
fetch("/user/list")
.then(res => res.json())
.then(users =>
this.setState(users,
() => {
console.log("list of users => " + users)
})
);
}
Michael Jasper response help me so much!
I found that fetch with GET method does not work if we pass any request body.
the full example is here
https://github.com/alexunjm/todo-list-react
const buildRequestOptions = ({
method = "GET",
raw = null, // I had my error here!, with GET raw need to be null
customHeaders = {name: 'value'},
}) => {
var myHeaders = buildHeaders(customHeaders);
var requestOptions = {
method,
headers: myHeaders,
body: raw,
redirect: "follow",
};
return requestOptions;
};
const listTasks = () => {
const url = `${uriBase}/task/sample`;
const requestOptions = buildRequestOptions({
customHeaders: { "Content-Type": "application/json" },
});
return fetch(url, requestOptions);
}
const asyncFn = ({
promiseToWait,
pendingFn,
successFn,
errorFn,
}) => {
return (dispatch) => {
dispatch(pendingFn());
promiseToWait
.then((res) => {
if (res.ok) {
return res.json();
}
// handled from server status 422 and 401
if (res.status === 422) {
// error message on body from server
return res.json();
}
if (res.status === 401) {
// custom error message hardcoded
return {errors: {action: 'no authorized'}}
}
console.log("http response no controlled", res);
return Promise.reject();
})
.then((body) => {
if (body.errors) {
const errors = Object.keys(body.errors).map(
(key) => key + " " + body.errors[key]
);
dispatch(errorFn(errors.join("; ")));
} else {
dispatch(successFn(body));
}
return body;
})
.catch((error) => {
console.log("error", error);
dispatch(errorFn("Unavailable server connection"));
});
};
};
const queryTasks = () => {
return asyncFn({
promiseToWait: listTasks(),
pendingFn: apiPending,
successFn: apiSuccessList,
errorFn: apiError,
});
}

React Native refreshing data

I'm trying to refresh some data when users re-vistis the screen. The way im using other places and it works. but can't figure out why this won't fly on this screen?
componentDidMount = () => {
this.props.navigation.addListener('didFocus', this.handleDidFocus)
}
async handleDidFocus() {
...
}
This is how I load data the first time and want to load it again when users revisits.
componentWillMount() {
this.getGroupAccepted();
}
async getGroupAccepted() {
if (this.state.token == null) {
var token = await AsyncStorage.getItem("token");
this.setState({ "token": token });
}
fetch('https://.../api/group/getActive', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: this.state.token
})
})
.then(response => response.json())
.then((data) => {
this.setState({
groups_accepted: data.groups_active,
loading: false,
});
})
.catch((error) => {
console.error(error);
});
}
This is what worked. Now when a user revisits the screen it loads the data once again.
componentDidMount = () => {
this.props.navigation.addListener('didFocus', this._handleDataChange)
}
_handleDataChange = () => {
this.getGroupAccepted();
}

Resources