How do I set state from within a fetch in ReactJS? - javascript

I have the following component class:
import React, { Component } from "react";
import Form from "react-bootstrap/Form";
import Button from 'react-bootstrap/Button'
import './login.css';
export default class Login extends Component {
handleSubmit = (e) => {
var myRes = null;
fetch(
"/exist/apps/my-app/modules/who-am-i.xq?user=emh&password=emh",
{
}
)
.then((response) => response.json())
.then(
(result) => {
myRes = {
error: null,
loaded: true,
user: result
};
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
myRes = {
error: error,
loaded: true,
user: {}
};
}
);
this.setState(myRes);
}
render() {
return (
<div className="auth-wrapper">
<div className="auth-inner">
<Form onSubmit={this.handleSubmit}>
<h3>Sign In</h3>
.
.
.
<Button variant="primary" type="submit">Submit</Button>
</Form>
</div>
</div>
);
}
}
I have searched for the answer, but what I got was in (result) => {this.setState({error: null, loaded: true, user: result})}. Unfortunately the this is undefined within the fetch.
I want to in the result and error to set the value in state. Unfortunately the this is not defined within the fetch result. How do I set the state in Login from within the fetch?

The problem is you're calling setState too soon. You need to call it only when your promise has settled. The easiest way to do that is with a subsequent then, see *** comments:
handleSubmit = (e) => {
// *** No `let myRes` here
fetch(
"/exist/apps/my-app/modules/who-am-i.xq?user=emh&password=emh",
{
}
)
.then((response) => response.json())
.then(
(result) => {
// *** Transform the resolution value slightly
return {
error: null,
loaded: true,
user: result
};
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
// *** Turn rejection into resolution by returning
// a replacement for `myRes`.
return {
error: error,
loaded: true,
user: {}
};
}
)
.then(myRes => {
// *** You always end up here because both fulfillment and rejecction
// result in an object that's used to fulfill the promise
// created by the first call to `then`
this.setState(myRes);
})
// *** Still use a final handler to catch errors from
// fulfillment handlers
.catch(error => {
// Handle/report error
});
};

first of all fetch should always be async
second put const that = this; in the top of the handler and then you can do setState like this:
handleSubmit = (e) => {
const self = this
var myRes = null;
fetch(
"/exist/apps/my-app/modules/who-am-i.xq?user=emh&password=emh",
{
}
)
.then((response) => response.json())
.then(
result => {
self.setState({error: null, loaded: true, user: result})
myRes = {
error: null,
loaded: true,
user: result
};
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
myRes = {
error: error,
loaded: true,
user: {}
};
}
);
}
and advise you to create a file for the fetch like this:
const nameYouDecided = async () -> {
await fetch("/exist/apps/my-app/modules/who-am-i.xq?user=emh&password=emh" )
.then((response) => response.json())
}
and then when you call it in your code it is shorter
In addition if you have a couple of get request you should do the following
in different file like get.js
const get = async (url) => (
await fetch(url)
.then(response => response.json())
.then(json => json
)
);
const nameYouDecided = get("/exist/apps/my-app/modules/who-am-i.xq?user=emh&password=emh")

You need to set the state inside of .then() fetch function.
Fetch need time to fetch urls, setstate doesnt wait this time so it set a Promise. To turn around this, you need to put your setState inside a .then, the setState line will only be executed when your fetch done the job
fetch(
"/exist/apps/my-app/modules/who-am-i.xq?user=emh&password=emh",
{
}
)
.then((response) => response.json())
.then(
(result) => {
return {
error: null,
loaded: true,
user: result
};
},
(error) => {
return {
error: error,
loaded: true,
user: {}
};
}
).then(myRes => {
this.setState(myRes);
});
}

Related

Vue store dispatch error response not being passed to UI

I'm trying to get the error response from my Vue store dispatch method, into my component, so I can tell the user if the save failed or not.
store/userDetails.js
const state = {
loading: {
user_details: false,
}
}
const getters = {
// Getters
}
const actions = {
save({commit, dispatch, rootState}, payload) {
commit('setLoading', {name: 'users', value: true});
axios(
_prepareRequest('post', api_endpoints.user.details, rootState.token, payload)
).then((response) => {
if (response.data) {
commit('setState', {name: 'user_details', value: response.data.units});
commit('setLoading', {name: 'user_details', value: false});
dispatch(
'CommonSettings/setSavingStatus',
{components: {userDetails: "done"}},
{root:true}
);
}
}).catch((error)=> {
console.log(error)
return error
}
)
}
My component method
views/Users.vue
send() {
this.$store.dispatch({
type: 'Users/save',
userDetails: this.current
}).then(response => {
console.log(response)
});
},
Above, I'm logging out the response in two places.
The response in my store/userDetails.js file is logged out fine, but it's not being passed to my send() function in my component - it comes up as undefined. Any reason why it wouldn't be passed through? Is this the correct way to do this?
This works for me. Try this solution.
store.js
actions: {
save(context, payload) {
console.log(payload);
return new Promise((resolve, reject) => {
axios(url)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
},
},
My Component method
App.vue
save(){
this.$store.dispatch("save", dataSendToApi).then((response)=>{
console.log(response)
})
}
Try returning axios call in the Store Action:
// add return
return axios(
_prepareRequest('post', api_endpoints.user.details, rootState.token, payload)
)
.then() // your stuff here
.catch() // your stuff here
If that won't work, use Promise in the Store Action. Like this:
return new Promise((resolve, reject) => {
return axios() // simplify for readibility reason, do your stuff here
.then((response) => {
//... your stuff here
resolve(response) // add this line
})
.catch((error) => {
// ... your stuff here
reject(error) // add this line
})
})
you should return a promise, reference link:vue doc

Jest - How to get code coverage for callback inside promise?

When running a coverage test, I am unable to get a specific block of code to be covered. It's a promise with a callback function (which sets the react state),
but I am having a hard time getting the callback function to be covered correctly in the coverage test. Here is the unit test:
TestPage.test.js
it('Handle form submit for entity with processToWaitFor', () => {
const wrapper = shallow( <TestPage data={{ processToWaitFor: 'submission' }} /> );
wrapper.instance().setState({ redirect: false });
wrapper.instance().submitForm();
const checkEntityFormStatus = jest.fn().mockImplementation(() => Promise.resolve('ended'));
const mockCallbackFunc = () => { wrapper.instance().setState({ redirect: true }) };
expect(checkEntityFormStatus('', '', '', mockCallbackFunc())).resolves.toBe('ended');
expect(wrapper.state().redirect).toEqual(true);
});
TestPage.js
submitForm = () => {
const {processToWaitFor} = this.props.data;
if (processToWaitFor) {
return checkEntityFormStatus( // Defined in APIcalls.js below
entity,
token,
processToWaitFor,
(response) => {
// Unit test not covering this callback which sets the state
this.setState({redirect: true});
return response;
}
);
}
}
APIcalls.js
export const checkEntityFormStatus = (
entity,
token,
processToWaitFor,
callback
) => {
const fetchStatus = async (n) => {
try {
const response = await API.checkEntityFormStatus(entity, token, processToWaitFor).then(API.handleResponse);
if (response == 'ended') {
return callback(response);
} else {
return new Error('No Status');
}
} catch (error) {
throw error;
}
};
};
Any idea how to get the unit test should look like so our coverage test covers the callback which sets the state when response is 'ended' from checkEntityFormStatus?

How to solve an error 'cb is not a function' in React Native?

current code
Index.js
import Auth from 'app/src/common/Auth';
export default class Index extends React.Component {
async componentDidMount() {
this.props.navigation.addListener('willFocus',
Auth.me().then(async (response) => {
await this.setState({ isLoggedIn: response });
}));
}
...
}
Auth.js
import axios from 'axios';
import { ENV } from 'app/env';
import { AsyncStorage } from 'react-native';
const { baseApiUrl } = ENV;
export default {
async me() {
try {
let result = false;
let token = await AsyncStorage.getItem('token');
token = token.replace(/"/g, '');
const response = await axios.get(`${baseApiUrl}/api/auth/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.data) {
result = true;
}
return result;
} catch (error) {
console.log(error);
}
},
};
error
I keep getting this error.
TypeError: cb is not a function. (In 'cb(data)', 'cb' is an instance of Promise)
I would appreciate it if you could give me any advice.
Its hard to tell without detail knowledge of your code (or react), but from the name i would expect this.props.navigation.addListener to take a callback function. Instead you pass a promise.
this.props.navigation.addListener('willFocus',
Auth.me().then(async (response) => {
await this.setState({ isLoggedIn: response });
})
);
Try changing the code to:
this.props.navigation.addListener('willFocus', () => {
Auth.me().then(async (response) => {
await this.setState({ isLoggedIn: response });
})
});
EDIT: #kai answer is better (and correct) for the current problem. I will leave the answer though, using async/await on the setState function is wrong anyway
You should remove the await from setState:
this.props.navigation.addListener('willFocus',
Auth.me()
.then((response) => {
this.setState({ isLoggedIn: response });
})
);
By using await, Javascript expects a Promise. But this.setState does not return a function.
On a sidenote, if you need to await for a setState function to be applied, you could use the callback as second parameter:
this.setState({ data }, () => console.log("Now the new state has been applied!"))
I resolved the same error by removing the listener from componentDidMount method
//this.props.navigation.addListener('focus', this._onFocus);

How do I update an object state in react via hooks

This is a simple question. How do I successfully update state object via react hooks?
I just started using hooks, and I like how it allows to use the simple and pure JavaScript function to create and manage state with the useState() function, and also, make changes that affect components using the useEffect() function, but I can't seem to make update to the state work!
After making a request to an API, it return the data needed, but when I try to update the state for an error in request and for a successful request, it does not update the state. I logged it to the browser console, but no change was made to the state, it returns undefined.
I know that I'm not doing something right in the code.
Here is my App component, Its a single component for fetching and updating:
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
export default function App() {
// Set date state
const [data,setData] = useState({
data: [],
loaded: false,
placeholder: 'Loading'
});
// Fetch and update date
useEffect(() => {
fetch('http://localhost:8000/api/lead/')
.then(response => {
if (response.status !== 200) {
SetData({placeholder: 'Something went wrong'});
}
response.json()
})
.then(result => {
console.log(data);
setData({data: result});
});
},[]);
return (
<h1>{console.log(data)}</h1>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
There are a few things you can improve:
the react-hook useState does not behave like the class counterpart. It does not automatically merge the provided object with the state, you have to do that yourself.
I would recommend if you can work without an object as your state to do so as this can reduce the amount of re-renders by a significant amount and makes it easier to change the shape of the state afterwards as you can just add or remove variables and see all the usages immediately.
With a state object
export default function App() {
// Set date state
const [data,setData] = useState({
data: [],
loaded: false,
placeholder: 'Loading'
});
// Fetch and update date
useEffect(() => {
fetch('http://localhost:8000/api/lead/')
.then(response => {
if (response.status !== 200) {
throw new Error(response.statusText); // Goto catch block
}
return response.json(); // <<- Return the JSON Object
})
.then(result => {
console.log(data);
setData(oldState => ({ ...oldState, data: result})); // <<- Merge previous state with new data
})
.catch(error => { // Use .catch() to catch exceptions. Either in the request or any of your .then() blocks
console.error(error); // Log the error object in the console.
const errorMessage = 'Something went wrong';
setData(oldState=> ({ ...oldState, placeholder: errorMessage }));
});
},[]);
return (
<h1>{console.log(data)}</h1>
);
}
Without a state object
export default function App() {
const [data, setData] = useState([]);
const [loaded, setLoaded] = useState(false);
const [placeholder, setPlaceholder] = useState('Loading');
// Fetch and update date
useEffect(() => {
fetch('http://localhost:8000/api/lead/')
.then(response => {
if (response.status !== 200) {
throw new Error(response.statusText); // Goto catch block
}
return response.json(); // <<- Return the JSON Object
})
.then(result => {
console.log(data);
setData(data);
})
.catch(error => { // Use .catch() to catch exceptions. Either in the request or any of your .then() blocks
console.error(error); // Log the error object in the console.
const errorMessage = 'Something went wrong';
setPlaceholder(errorMessage);
});
},[]);
return (
<h1>{console.log(data)}</h1>
);
}
The correct way to update an Object with hooks it to use function syntax for setState callback:
setData(prevState => {...prevState, placeholder: 'Something went wrong'})
Following method will override your previous object state:
setData({placeholder: 'Something went wrong'}); // <== incorrect
Your final code should look like this:
.then(response => {
if (response.status !== 200) {
setData(prevObj => {...prevObj, placeholder: 'Something went wrong'});
}
return response.json()
})
.then(result => {
setData(prevObj => {...prevObj, data: result});
});

Jest Axios test is passing when it shouldn't

I am writing a script (see below) to make sure that an axios function throws an error when it receives a certain status code. I found out, however, that even when I make this test fail, Jest still says that the test passes, even though it returns an error in the console (see below). Why is Jest saying this test has passed when it actually failed? Does it have something to do with me trying to expect an error, so even if the test fails, jest still receives an error (that the test failed) and thinks this means that I got what I expected? Thanks.
foo.test.js:
import axios from 'axios';
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve({ data: 'payload' })),
}));
const getData = async (url) => {
const response = await axios.get(url);
if (response.status !== 200) {
return response.text().then((error) => {
throw new Error(error.message);
});
} else {
return response.data;
}
};
test('testing that an error is thrown', async () => {
axios.get.mockImplementation(() =>
Promise.resolve({
data: {data: 'payload'},
status: 400,
text: () => Promise.resolve(JSON.stringify({message: 'This is an error.'})),
})
);
const expectedError = async () => {
await getData('sampleUrl');
};
// The error should return 'This is an error.' and instead
// is expecting 'foo', so this test should fail.
expect(expectedError()).rejects.toThrowError('foo');
});
You need two changes to get the test to fail as expected.
Don't stringify the resolved value from text
await the expect that uses rejects
Here is an updated version that fails as expected:
import axios from 'axios';
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve({ data: 'payload' })),
}));
const getData = async (url) => {
const response = await axios.get(url);
if (response.status !== 200) {
return response.text().then((error) => {
throw new Error(error.message);
});
} else {
return response.data;
}
};
test('testing that an error is thrown', async () => {
axios.get.mockImplementation(() =>
Promise.resolve({
data: {data: 'payload'},
status: 400,
text: () => Promise.resolve({message: 'This is an error.'}), // <= don't stringify
})
);
const expectedError = async () => {
await getData('sampleUrl');
};
await expect(expectedError()).rejects.toThrowError('foo'); // <= await
});

Categories