So I use react-query to handle API requests. Current problem is that when i try to submit form data, post request, mutation state is always idle, and loading is always false. I also use zustand for state management.
This is useSubmitFormData hook. Post request executes as expected, just mutation status and isLoading does not get changed.
export const useSubmitFormData = () => {
const { postDataPlaceholder } = usePlaceholderApi();
// data which is submiting is getting from the store - reducer
const { data, filesToUpload } = useFormDataStore();
const { mutate, status, isLoading } = useMutation(() => postDataPlaceholder({ data }), {
onMutate: () => console.log('onMutate', status),
onSuccess: (res) => console.log(res),
onError: (err) => console.log('err', err),
});
return {
submitForm: mutate,
isLoading,
};
};
Now on FormPage.jsx it is triggered like this:
const { submitForm, isLoading } = useSubmitFormData();
const onSubmit = () => submitForm();
And this is how usePlaceholderApi looks like. It is kind of custom hook in purpose to use axios in combination with interceptors to handle authorization token.
const usePlaceholderApi = () => {
const { post } = usePlaceholderAxios();
return {
postDataPlaceholder: async (data) => post('/posts', { data }),
};
};
export default usePlaceholderApi;
And this is usePlaceholderAxios.
import axios from 'axios';
const usePlaceholderAxios = () => {
axios.interceptors.request.use(async (config) => {
const api = 'https://jsonplaceholder.typicode.com';
if (config.url.indexOf('http') === -1) {
// eslint-disable-next-line no-param-reassign
config.url = `${api}${config.url}`;
}
return config;
});
return {
get: (url, config) => axios.get(url, config),
post: (url, data, config) => axios.post(url, data, config),
};
};
export default usePlaceholderAxios;
Any ideas what could go wrong here ? Am I missing something ? Also tried to call axios directly in mutation, without usePlaceholderApi hook in between, but same outcome.
Related
I am switching certain CRUD functionality that I used to provide with a token, but now I am using SWR and I don't know how to convert it.
I used this hook for GET methods but for others, I don't know what to do!
export default function useGetData(apiKey) {
const fetcher = async (...args) => await fetch(...args).then(res => res.json());
const { data, mutate, error } = useSWR(apiKey, fetcher);
const loading = !data && !error;
return {
loading,
user: data,
mutate
}
}
OK, I found the answer :
import useSWR from 'swr';
import { getTokenFromLocalStorage } from '../../services/storage';
export default function useGetData({ url, payload, options = {}}) {
const mainUrl = process.env.NEXT_PUBLIC_BASE_URL + URL;
const method = payload ? 'POST' : 'GET';
const fetcher = async () => {
const headers = {
Authorization: `Bearer ${getTokenFromLocalStorage()}`
};
const options = {
method,
headers,
...(payload && { body: payload }),
};
return await fetch(mainUrl, options).then((res) => res.json());
};
const defaultOptions = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
const { data, mutate, error, isValidating } = useSWR(url + method, fetcher, {
...defaultOptions,
...options,
});
const loading = !data && !error;
return { data, loading, error, mutate, isValidating };
}
I have a problem with async request return. It returns me back a Promise instead of data!!
So i tried to process data variable a second time but it did not work. I exported it as it needs, created initial state for repo=[], passed in to the reducer...
gitContext.jsx
const getRepos = async (login) => {
setLoading();
const params = new URLSearchParams({
sort:'created',
per_page:10
})
const response = await fetch(`http://api.github.com/users/${login}/repos?${params}`)
if (response.status === 404) {
window.location = './notfound'
} else {
const data = await response.json();
console.log(data);
dispatch({
type: 'GET_REPOS',
payload: data
})
}
}
Here is the function call from User.jsx
const { getUser, user, isLoading, repos, getRepos } = useContext(GitContext);
const params = useParams()
useEffect(() => {
getUser(params.login);
// console.log(getRepos(params.login?.repos))
getRepos(login?.repos);
}, [])
So, in useEffect I am fetching an object from the API then I am dispatching response data to the Context reducer and then updating the state. It looks something like this:
export const fetchItem = (id) => request({url: `/items/${id}`, method: 'get'});
...
const {dispatch, singleItem} = useProvider();
useEffect(() => {
const id = getItemIdFromUrl(props);
fetchItem(id).then((response) => {
dispatch(action(response.data.data));
});
}, [props, dispatch]);
I would like to write a good integration test for this. I am using react-testing-library with Jest. I am trying to mock the return value of the fetchItem function and then to check if everything is rendered correctly but constantly getting this warning:
act(() => {
/* fire events that update state */
});
/* assert on the output */
Any chance to do this correctly?
This is how the request method looks like:
import axios from 'axios';
import humps from 'humps';
import {getItem} from './localStorage';
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
});
api.interceptors.response.use(
(response) => humps.camelizeKeys(response),
(error) => Promise.reject(error.response),
);
api.interceptors.request.use(
(request) => {
request.data = humps.decamelizeKeys(request.data);
return request;
},
(error) => Promise.reject(error.request),
);
export default function request({url, method, headers = {}, data}) {
try {
const token = getItem('token');
headers.Authorization = token;
return api({method, url, headers, data});
} catch (error) {
if (error.status === 500) {
console.log('HANDLE ERROR: ', error);
}
throw error;
}
}
I'm wondering since my POST call doesn't give any data in response, do I need to have action types and reducer to maintain State?
This is my actioncreator. What will actionType and reducer contain?
export const postData = (...args) => async dispatch => {
const [ url, body ] = args;
const params = [ url, body, undefined, false, undefined , 'application/json'];
try {
const data = configs.ENV.DEVELOPMENT ? await API.getData(url, dispatch, actions, false) : await API.postData(params, dispatch, actions);
if (!data) {
window.location = configs.endpoints.RESULTS_PAGE;
}
}
catch(err) {
console.log('Please try again')
} };
I'm getting the error .then() is not a function while attempting to write a React app test w/ jest/enzyme. I don't think the test code is the problem, but for reference, I am following this Redux example.
Here's the code that throws the error:
return store.dispatch(actions.fetchFiles()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
});
My client's codebase uses redux-pack and some conventions that I am not familiar with and I'm having a hard time deciphering where the actual promise is being executed and thus how to chain a "then" function when calling it from my test code as shown in the redux example link posted above.
I've tried to do some debug logging and some variations on the syntax, for example, I attempted to call .then directly on actions.fetchFiles() since I was under the impression that it was the call that actually returned the promise, but it didn't work. I'm getting a tad lost in all this code and questioning where the promise is actually getting executed/returned.
The basic structure of the code is as follows:
A connected Container component that fetches a list of files from an API and then dispatches.
The actual page works fine, but my attempts to test (like the Redux article referenced above) blow up.
Here are what I believe to be the relevant blocks of code in play:
Container component
componentDidMount() {
const { actions } = this.props;
actions.fetchUpload();
}
const mapStateToProps = state => ({
...state.upload,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators({
...uploadActions,
}, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(UploadContainer);
actions.js
import api from '../../core/api';
export const FETCH_FILES = 'upload/fetch-files';
const actions = {
fetchFiles: () => ({
type: FETCH_FILES,
promise: api.upload.getFiles()
})
};
actions.fetchUpload = () => (dispatch) => {
dispatch(actions.fetchFiles());
};
export default actions;
reducer.js
import { handle } from 'redux-pack';
import { FETCH_FILES } from './actions';
const initialState = {
files: []
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_FILES:
return handle(state, action, { // this is redux-pack syntax
success: (s, a) => ({ ...s, files: a.payload.files })
});
default:
return state;
}
};
upload.js (api.upload.getFiles)
export default http => ({
getFiles: () => http.get('/file')
});
api.js - uses Axios
import Axios from 'axios';
import { SubmissionError } from 'redux-form';
import queryString from 'query-string';
import upload from './upload';
const axios = Axios.create({
baseURL: '/api/',
withCredentials: true,
paramsSerializer: params => queryString.stringify(params),
});
class HttpError extends Error {
constructor(status, error, errors = {}) {
super(`Http Error: ${status}`);
this.status = status;
this.error = error;
this.errors = errors;
}
getReduxFormError = defaultError => new SubmissionError({
_error: this.error || defaultError,
...this.errors,
});
}
const handleUnauth = (method, url, options) => (err) => {
const { status } = err.response;
if (status === 401) {
return axios.get('/users/refresh')
.then(() => method(url, options))
.catch(() => Promise.reject(err));
}
return Promise.reject(err);
};
const handleHttpError = (err) => {
const { status, data: { message = {} } } = err.response;
if (typeof message === 'string') {
return Promise.reject(new HttpError(status, message));
}
return Promise.reject(new HttpError(status, null, message));
};
const http = {
get: (url, options) => axios.get(url, options)
.then(response => response.data)
.catch(handleUnauth(axios.get, url, options))
.catch(handleHttpError),
post: (url, data, options) => axios.post(url, data, options)
.then(response => response.data)
.catch(handleUnauth(axios.post, url, options))
.catch(handleHttpError),
patch: (url, data, options) => axios.patch(url, data, options)
.then(response => response.data)
.catch(handleUnauth(axios.patch, url, options))
.catch(handleHttpError),
delete: (url, options) => axios.delete(url, options)
.then(response => response.data)
.catch(handleUnauth(axios.delete, url, options))
.catch(handleHttpError),
};
export default {
upload: upload(http)
};
I was expecting the tests to pass because the returned object should match the expected actions, but it errors. Here's the full message:
FAIL src/modules/upload/upload.test.js
Upload module actions › Returns an array of files when calling actions.fetchFiles
TypeError: store.dispatch(...).then is not a function
43 | // );
44 |
> 45 | return store.dispatch(actions.fetchFiles()).then(() => {
|
46 | expect(store.getActions()).toEqual(expectedActions);
47 | });
48 | });
at Object.then (src/modules/upload/upload.test.js:45:52)
When/where is the promise getting returned and how can I chain a .then function like the Redux test example linked above?