Completely new to JS, react, redux and thunk all together.
I am fetching data from an endpoint and I want to site to load / display an error if the fetch was resolved or rejected, but somehow I cant call .then on the fetch I return in my actioncreator.
//projectApi.js
function add(project){
const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(
project
)
};
return fetch(config.apiUrl + "/projects", requestOptions).then(handleResponse);
}
function handleResponse(response) {
return new Promise((resolve, reject) => {
if (response.ok) {
var contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
response.json().then(json => resolve(json));
} else {
resolve();
}
} else {
response.json().then(json => reject(json));
}
});
}
Then in my ActionCreator I'm doing this:
//projectActions.js
function add(project){
return dispatch => {
dispatch(request());
return projectApi.add(project)
.then( project => {
dispatch(success(project));
},
error => {
dispatch(failure(error));
}
);
};
function request() {
// left out for brevity
}
function success(project) {
// left out for brevity
}
function failure(error) {
// left out for brevity
}
}
However, if I now try to .then my dispatch...
//SomePage.js
handleSubmit(e) {
e.preventDefault();
var project = { name: this.state.projectName };
this.props.add(project).then(...);
}
...
function mapDispatchToProps(dispatch) {
return {
add: (project) => {
dispatch(projectActions.add(project));
}
};
}
I get "TypeError: this.props.add(...) is undefined", however all the actions are properly dispatched. (e.g. request, failure, success) and the store is updated.
Sorry if its a really stupid mistake.
Related
I am trying to call a function that calls fetch to an API from a React component in a separate file and am not finding the correct solution to get the correct response back.
When I debug, the result returns before the updateAccount function has completed and the final result is never returned to my update function.
Inside the fetch, the API returns the correct response whether it is successful or has validation errors and those results are correctly assigned to result.success and result.errors but the result doesn't get returned from the function so that the caller can make use of those values.
Inside of my React component:
import { updateAccount } from '../services/requests';
...
const update = (account: EditAccountModel) => {
const result = updateAccount(account);
if(result.errors.length > 0) {
// will notify of errors
console.log(result.errors); // is an empty array instead of validation errors
} else {
// will notify of success
console.log(result.success); // is an empty string instead of success message
}
}
...
My request file
export const updateAccount = (account: EditAccountModel | undefined): EditAccountResponseModel => {
const result = new EditAccountResponseModel();
fetch(baseUrl, {
method: 'PUT',
body: JSON.stringify(account),
headers
})
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
result.success = `${account?.name} was updated successfully!`
})
.catch(error => {
if (typeof error.json === "function") {
error.json().then(jsonError => {
result.errors.push(jsonError);
}).catch(genericError => {
result.errors.push(genericError);
});
}
});
return result;
}
The result reassignment happens inside then catch but it won’t be affective in the way you expected. The guaranteed way to return correct result is via a callback() passed to your updateAccount() if you could afford it:
export const updateAccount = (
account: EditAccountModel | undefined,
callback: Function
): EditAccountResponseModel => {
const result = new EditAccountResponseModel();
fetch(baseUrl, {
method: 'PUT',
body: JSON.stringify(account),
headers
})
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
result.success = `${account?.name} was updated successfully!`
callback(result);
})
.catch(error => {
if (typeof error.json === "function") {
error.json().then(jsonError => {
result.errors.push(jsonError);
callback(result);
}).catch(genericError => {
result.errors.push(genericError);
callback(result);
});
}
});
}
And inside your React component:
const update = (account: EditAccountModel) => {
const handleResult = (res) => {
// your result callback code
// ...
};
updateAccount(account, handleResult);
// ...
}
Alternative way that keeps your current structure is to change your current updateAccount() to an async function, then return await fetch().
You need to wait for the response . I'll let read more about how Promise work in JavaScript.
I wouldn't code updateAccount the same way you did, especially where you use the variable result and update it inside the flow of the promise (you really don't need that). You're also using React so you can use the state to store and update the result of the update function. But let's fix your problem first:
export const updateAccount = async (account: EditAccountModel | undefined): EditAccountResponseModel => {
const result = new EditAccountResponseModel();
await fetch(baseUrl, {
method: 'PUT',
body: JSON.stringify(account),
headers
})
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
result.success = `${account?.name} was updated successfully!`
})
.catch(error => {
if (typeof error.json === "function") {
error.json().then(jsonError => {
result.errors.push(jsonError);
}).catch(genericError => {
result.errors.push(genericError);
});
}
});
return result;
}
First make your function updateAccount async then await the result of the promise.
Now the same thing for the function update:
const update = async (account: EditAccountModel) => {
const result = await updateAccount(account);
if(result.errors.length > 0) {
// will notify of errors
} else {
// will notify of success
}
}
I'm testing a component that calls an API to populate a table with data. Though axios is used, axios is being wrapped in a convenience method of sorts to populate headers before executing the request via interceptors. I've tried axios-mock-adapter, but it's not working. I'm still new to testing React and I'm lost on how to mock data coming back from the api/axios. How do I go about mocking the api call to mock the data for my tests to pass??
This is my simple test:
test('<EmailTable/> ', async () => {
const { debug, getByText } = render(<CommunicationEmail />);
await waitFor(() => expect(getByText('Test Email Subject')).toBeTruthy());
}
This is the axios wrapper (api.js):
const instance = axios.create({
baseURL: `${apiUrl}/v1`,
timeout: 12000,
withCredentials: true,
headers: headers,
});
//intercept requests to validate hashed auth token
instance.interceptors.request.use((request) => {
const token = request.headers['X-Our-Access-Token'];
if (
localStorage.getItem('user_token') == null ||
SHA256(token).toString(enc.Hex) == localStorage.getItem('user_token')
) {
return request;
} else {
console.log({ what: 'Auth key invalid' });
return Promise.reject('Invalid token!');
}
});
//intercept responses to handle 401 errors
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
// handle 401 authentication errors and redirect to SSO
if (error.response != null && error.response.status != null && error.response.status === 401) {
console.error({ what: 'Authorization error', e: error });
}
return Promise.reject(error);
}
);
export default instance;
And here's a simplification of the component I'm trying to test:
import api from './api.js';
const EmailTable = () => {
const [emails, setEmails] = useState();
useEffect(() => {
if(!emails) {
getEmails();
}
}, [emails]);
const getEmails = async () => {
await api({
method: 'GET',
url: `/communications/emails`,
}).then((response) => {
if (response.success) {
setEmails(response.emails);
}
}
}
if(!emails) { return <div> Loading... </div> };
return <div>{emails}</div>;
}
UPDATE WITH SOLUTION:
To mock the axios wrapper that is my API, I had to mock the api module and return a resolved promise like so:
jest.mock('../api', () => {
return function (request) {
// If we want to mock out responses to multiple API requests, we could do if (request.url = "/blah/blah") { return new Promise.... }
return new Promise((resolve) => {
resolve({
data: { success: true, emails: [] },
});
});
};
});
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
So I want to have fetch request in separate file. Just for testing. And then call it in componentDidMount. I still get undefined. What is the proper way of doing it?
This is my function:
const fetchNewWord = () => {
fetch("https://wordsapiv1.p.rapidapi.com/words/?lettersMax=11&random=true", {
headers: {
"X-Rapidapi-Host": "wordsapiv1.p.rapidapi.com",
"X-Rapidapi-Key": "myKey"
}
})
.then(data => data.json())
.then(data => {
return data.word;
});
};
export default fetchNewWord;
You have return your fetch callback results as function's response:
export const fetchNewWord = () => {
return fetch("https://wordsapiv1.p.rapidapi.com/words/?lettersMax=11&random=true", {
headers: {
"X-Rapidapi-Host": "wordsapiv1.p.rapidapi.com",
"X-Rapidapi-Key": "myKey"
}
})
.then(data => data.json())
.then(data => data.word);
};
When you do const data = fetchNewWord(); console.log(data);, you'll see the result.
You can create a separate service for fetch in your code and use it as a independent providers
Here is the httpRequest.js which you can use default fetch api:
import axios from 'axios';
class HttpRequest {
constructor(baseURL) {
this.axios = axios.create({
baseURL,
});
}
reponseInterceptor() {
// Add a response interceptor
this.axios.interceptors.response.use(
response => (
// Do something with response data
response
),
error => (
// Do something with response error
Promise.reject(error)
),
);
}
requsetInterceptor() {
this.axios.interceptors.request.use(
config => (
// Do something before request is sent
config),
error => (
// Do something with request error
Promise.reject(error)
),
);
}
fetch(url, params, config = {}) {
return this.axios.get(url, {
params,
...config,
});
}
create(url, data, config = {}) {
return this.axios.post(url, data, {
...config,
});
}
update(url, data, config = {}) {
return this.axios.put(url, data, {
...config,
});
}
patch(url, data, config = {}) {
return this.axios.patch(url, data, {
...config,
});
}
remove(url, params, config = {}) {
return this.axios.delete(url, {
params,
...config,
});
}
}
export default HttpRequest;
Here is how you can create your words.js services:
import config from 'config';
import HttpRequest from './httpRequest';
export default class WordService extends HttpRequest {
constructor(servicePath) {
super(config.markMeHost);
this.path = `${config.markMeHost}/${servicePath}`;
}
listWords() {
return this.fetch(this.path, {});
}
createWords(data) {
return this.create(this.path, data);
}
updateWords(data, id) {
return this.update(`${this.path}/${id}`, data);
}
deleteWords(id) {
return this.remove(`${this.path}/${id}`);
}
}
Your api service index.js:
import WordService from './words';
// Give arg to provider to start endpoint with specific path for example = abc.com/api/words
export default new WordService('words');
For further details you can check my github account for axios service https://github.com/m-nathani/markme/tree/master/frontend/src/service/api
I want to write a test which mocks a promise in reactjs
I just need a mocked implementation of getHeaders() to return a string
export const loadAllProjects = () => {
return (dispatch) => {
getHeaders()
.then(headers => {
...do stuff
})
}
}
to clarify my original function was...
export const loadAllProjects = () => {
return (dispatch) => {
...do stuff
}
}
...and my test was...
it('should create SET_ALL_PROJECTS action when fetching projects', () => {
fetchMock
.getOnce('http://test.projects.api/api/projects',
{
body: [{name: "x"}],
headers: { 'content-type': 'application/json' }
}).spy()
const expectedActions = [
{ type: "SET_ALL_PROJECTS", json: [{name:"x"}] },
]
checkAsyncActionsWereDispatched(expectedActions, actions.loadAllProjects)
});
I want the test to work with the mocked header
const getHeaders = () => {
return new Promise((resolve, reject) => {
resolve("some string");
});
};
a = await getHeaders(); //some string
Use Promise.resolve
return Promise.resolve("your headers here");
You can use jest to mock a promise for testing
Example for the eventual completion:
const mockPostSpy = jest
.spyOn(axios, 'post')
.mockImplementation(() => {
return new Promise((resolve) => {
return resolve({
data: {},
});
});
});
Example for the operation failed:
const mockPostSpy = jest
.spyOn(axios, 'post')
.mockImplementation(() => {
return new Promise((resolve) => {
return reject({});
});
});
Good luck to you ^^
I have a react redux project that looks like this:
Reducer:
export function contentReducer(state = { loading: true }, action) {
switch (action.type) {
case types.RETRIEVE_CONTENT_SUCCESS:
return { ...state, loading: false, contentResults: action.payload.results }
default:
return state;
}
};
Action:
export function loginSuccess(loginResult) {
return { type: types.LOGIN_SUCCESS, loginResult };
}
export function login(formData) {
return function (dispatch) {
return submitLogin(formData).then(umvToken => {
dispatch(loginSuccess(umvToken));
}).catch(error => {
throw (error);
});
};
}
The Api itself:
export function submitLogin(login) {
var form = new FormData()
form.append('userIdentifier', login.email)
form.append('password', login.password)
return fetch("http://localhost:8080/login/login/umv",
{
method: "POST",
headers: {
'Accept': 'application/x-www-form-urlencoded;charset=UTF-8',
},
body: form
}).then(function (response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then(function (token) {
localStorage.setItem('umvToken', token.text())
return token
})
.catch(function (error) {
console.log('Looks like there was a problem: \n', error);
})
}
function mapStateToProps(state) {
return {
login: state.login.loginResponse
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(thunks, dispatch)
};
}
class ConnectedHome extends Component {
constructor() {
super();
this.processForm = this.processForm.bind(this);
}
processForm(form) {
this.props.actions.login(form)
}
render() {
return (
<div>
{this.ButtonAppBar()}
<LoginForm onSubmit={this.processForm} />
</div >
);
}
}
const Home = connect(mapStateToProps, mapDispatchToProps)(ConnectedHome);
export default Home
However when I try to access local storage from the home page like this:
var result = localStorage.getItem('umvToken')
console.log(result)
I get the following message:
[object Promise]
If I replace the setting of local storage with this:
localStorage.setItem('umvToken', 'test')
then the console.log will show test. But it doesn't work with the actual value. I'm not really sure how to resolve this?
Thanks for your help!
From first .then function in api call, return response.json(); or return response.text();. If you just return response, its a promise, which won't get resolved, unless you do response.json() or response.text(), depending on the type of response you are expecting.
I'm pretty sure token.text() returns a Promise, so you will need to wait for it to resolve to get the actual token string, i.e.
.then(function (token) {
return token.text();
})
.then(tokenText => {
localStorage.setItem('umvToken', tokenText);
})
Use the following code
.then(function (token) {
localStorage.setItem('umvToken', JSON.stringify(token))
return token
})