redux observable with axios onProgress - axios

i am creating an upload function that will show a progress bar to the client inside a React Redux and Redux-observable, and i use axios to do a put request to AWS S3.
My epics is as follow
...
function uploadFile(mimetype, url, file) {
const config = {
headers: {
'Content-Type': mimetype,
},
onUploadProgress(progress) {
const percentCompleted = Math.round((progress.loaded * 100) / progress.total)
uploadProgress(percentCompleted)
},
}
axiosRetry(axios, { retries: 3 })
return axios.put(url, file[0], config)
}
export const uploadEpic = (action$, store) => action$
.ofType(SIGNED_URL_SUCCESS)
.mergeMap(() => {
const file = store.getState().File.droppedFile
const mimetype = file[0].type
const { url } = store.getState().SignedUrl
const { fileData } = store.getState().Upload
return of(uploadFile(mimetype, url.data, file))
.concatMap(() => {
const uploadedData = {
url: fileData.url,
thumbUrl: `${fileData.folder}/${fileData.filename}-00001.png`,
}
return [
upload(uploadedData),
uploadSuccess(),
]
})
.catch(error => of(uploadFailure(error)))
})
export default uploadEpic
The upload seems to work, as i received an AWS SNS email telling that its done, but i can't seem to see that it is updating the Upload.progress state inside my Upload reducer.
The reason i am using axios is particulary because its axios-retry and its onUploadProgress, since i can't seem to find an example doing an onProgress using universal-rx-request
so two questions probably
How can i achieve this using axios
How can i achieve this using universal-rx-request

Thanks to this SO answer
I ended up not using axios at all
I got it working with this
import { of } from 'rxjs/observable/of'
import { Subject } from 'rxjs/Subject'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/dom/ajax'
import { SIGNED_URL_SUCCESS } from 'ducks/SignedUrl'
import {
upload,
uploadIsLoading,
uploadSuccess,
uploadFailure,
uploadProgress,
} from 'ducks/Upload'
export const uploadEpic = (action$, store) => action$
.ofType(SIGNED_URL_SUCCESS)
.mergeMap(() => {
const file = store.getState().File.droppedFile
const mimetype = file[0].type
const { url } = store.getState().SignedUrl
const { fileData } = store.getState().Upload
const progressSubscriber = new Subject()
const request = Observable.ajax({
method: 'PUT',
url: url.data,
body: file[0],
headers: {
'Content-Type': mimetype,
},
progressSubscriber,
})
const requestObservable = request
.concatMap(() => {
const uploadedData = {
...
}
return [
upload(uploadedData),
uploadIsLoading(false),
uploadSuccess(),
]
})
.catch(error => of(uploadFailure(error)))
return progressSubscriber
.map(e => ({ percentage: (e.loaded / e.total) * 100 }))
.map(data => uploadProgress(data.percentage))
.merge(requestObservable)
})
UPDATE: on rxjs 6 the merge operators is deprecated, so if you're using rxjs 6, change the code above to
// some/lib/folder/uploader.js
import { of, merge } from 'rxjs' // import merge here
import { ajax } from 'rxjs/ajax'
import { map, catchError } from 'rxjs/operators' // instead of here
import { Subject } from 'rxjs/Subject'
export function storageUploader(...args) {
const progressSubscriber = new Subject()
const request = ajax({...someRequestOptions})
.pipe(
map(() => success()),
catchError((error) => of(failure(error))),
)
const subscriber = progressSubscriber
.pipe(
map((e) => ({ percentage: (e.loaded / e.total) * 100 })),
map((upload) => progress(upload.percentage)),
catchError((error) => of(failure(error))),
)
return merge(subscriber, request) // merge both like this, instead of chaining the request on progressSubscriber
}
//the_epic.js
export function uploadEpic(action$, state$) {
return action$
.pipe(
ofType(UPLOAD),
mergeMap((someUploadOptions) => uploaderLib(
{ ...someUploadOptions },
actionSuccess,
actionFailure,
actionProgress,
)),
catchError((error) => of(actionFailure(error))),
)
}

Related

React Query always returning isError as False

We are using Axios along with react query. Can someone point me to correct example, so that all the react query keys like isError, isSuccess is properly set. As, I read through other answers, the catch block below might be the culprit.
function api<TResponse>({
method,
url,
body,
contentType = 'application/json',
}: IAccessInterceptor): Promise<TResponse> {
return new Promise((resolve, reject) => {
const payload: AxiosRequestConfig = {
method,
url,
headers: {
Accept: contentType,
'content-type': contentType,
},
};
// including 'data' if body is not null
if (body != null) {
payload.data = body;
}
axios(payload)
.then((response: { data: { message: TResponse; data: TResponse } }) => {
resolve(response.data?.message || response.data?.data);
})
.catch((error: { response: { data: IServerError } }) => {
reject(error?.response?.data);
});
});
}
// Some problem in above catch block.
const getABC = (
id: string,
): Promise<IABC> => {
const method = 'GET';
const url = `${BASE_URL}.get_abc?assignment_id=${assignmentId}`;
return api<IABC>({ method, url });
};
export const useABC = (
assignmentId: string | null,
): UseQueryResult<IABC> =>
useQuery<IABC, Error>(
queryKeys.getABC(id),
() => someFunction(id|| ''),
{ enabled: !!id},
);
"useABC().isError" is always false

Redux toolkit createAsyncThunk using Parameter

I need your help.
We implemented createAsyncThunk using the Redux Toolkit.
However, as shown in the picture, I need a function to change ChannelId flexibly. Can't I use the parameter in createAsyncThunk?
How can I use it if I can?
Or is there any other way?
I am sorry that the quality of the question is low because I am Korean using a translator.
enter image description here
// ACTION
export const getYoutubeList_PlayList = createAsyncThunk(
"GET/YOUTUBE_PLAYLIST",
async (data, thunkAPI) => {
try {
const { data } = await axios.get<youtubeResponse>(
`https://www.googleapis.com/youtube/v3/playlists?key=${youTubeAcsses.apiKey}&channelId=${channelId}&part=snippet&maxResults=30`
)
return data
} catch (err: any) {
return thunkAPI.rejectWithValue({
errorMessage: '호출에 실패했습니다.'
})
}
}
);
// SLICE
const youtube_PlaylistSlice = createSlice({
name: "YOUTUBE_PLAYLIST",
initialState,
reducers: {},
// createAsyncThunk 호출 처리 = extraReducers
extraReducers(builder) {
builder
.addCase(getYoutubeList_PlayList.pending, (state, action) => {
state.loading = true;
})
.addCase(getYoutubeList_PlayList.fulfilled, (state, action: PayloadAction<youtubeResponse>) => {
state.loading = false;
state.data = action.payload;
})
.addCase(getYoutubeList_PlayList.rejected, (state, action: PayloadAction<any>) => {
state.error = action.payload;
});
},
});
You named both the incoming argument data as well as the result of your axios call. That will "shadow" the original data and you cannot access it any more. Give those two variables different names.
Here I called it arg, which allows you to access arg.channelId.
export const getYoutubeList_PlayList = createAsyncThunk(
"GET/YOUTUBE_PLAYLIST",
async (arg, thunkAPI) => {
try {
const { data } = await axios.get<youtubeResponse>(
`https://www.googleapis.com/youtube/v3/playlists?key=${youTubeAcsses.apiKey}&channelId=${arg.channelId}&part=snippet&maxResults=30`
)
return data
} catch (err: any) {
return thunkAPI.rejectWithValue({
errorMessage: '호출에 실패했습니다.'
})
}
}
);
You would now dispatch this as dispatch(getYoutubeList_PlayList({ channelId: 5 }))

NEXT, upload file along with metadata (Axios and Formidable with Node JS)

I want to upload a file to NEXT apis along with metadata:
const data = new FormData();
data.append('file', file);
data.append('body', JSON.stringify({ hello: 'world' }));
console.log('Sending');
axios
.post('/api/test-route', data, {
headers: {
'content-type': 'multipart/form-data',
'Authorization': 'json-token',
},
})
.then((response: AxiosResponse) =>
console.log('data = ', response.data)
)
.catch((error: unknown) => console.log(error));
Here's my API Code:
// Backend
import formidable from 'formidable';
import { NextApiRequest, NextApiResponse } from 'next';
import {
errorResponse,
genericResponse,
getErrorDetailsFromKey,
} from '#global-backend/utils/api/responseSynthesizer';
import {
ErrorCodes,
IResponse,
} from '#constants/interfaces/gcorn/backend/apis/response.interfaces';
export const config = {
api: {
bodyParser: false,
},
};
// eslint-disable-next-line import/no-anonymous-default-export
export default async (req: NextApiRequest, res: NextApiResponse) => {
const form = new formidable.IncomingForm();
//#ts-ignore
form.uploadDir = './'; //#ts-ignore
form.keepExtensions = true;
const opsDetails = getErrorDetailsFromKey(
ErrorCodes.INVALID_OR_CORRUPTED_FILE
);
let response = errorResponse({ opsDetails });
let status_code = 400;
const payload: { response: IResponse; status_code: number; error: boolean } =
await new Promise((resolve) => {
let flag = 0;
form.parse(req, (err, _, files) => {
const isError = err?.message !== undefined;
if (isError) {
response = errorResponse({
message: err.message,
opsDetails,
});
status_code = 400;
}
console.log('Files = ', Object.keys(files.file));
const fileCheck = checkImageFileValidity(files.file as unknown as File);
if (fileCheck.error) {
opsDetails.details = fileCheck.message;
response = errorResponse({
message: fileCheck.message,
opsDetails,
});
status_code = 400;
}
response = genericResponse({
status_code: 201,
opsDetails: getErrorDetailsFromKey(
ErrorCodes.FUNFUSE_PROFILE_UPDATE_SUCESS
),
});
status_code = 201;
flag = 1;
resolve({ response, status_code, error: false });
});
});
return res.status(payload.status_code).json(payload.response);
};
const checkImageFileValidity = (
file: File
): { error: boolean; message: string } => {
const { type, size } = file;
// Must be less than 5MBs in Size and Must be Image File
if (size > 5000000)
return { error: true, message: 'File Size More than 5MBs' };
if (!type.includes('image'))
return { error: true, message: 'File is not an image' };
return { error: false, message: 'File is valid' };
};
But for some reason, I don't know how can I parse body part of my form which extracts the info: {hello:world}.
Does anyone know a way to parse it and collect in the backend ?
Assuming everything else is correct, you need to check the _ variable

Ionic formData append showing null in server

I am trying to upload an image using formData. The api is working fine. But the data is displaying null in the server.
My function is
capture_dl_front(){
this.camera.getPicture(this.cameraOptions)
.then(imageData => {
this.customer.dl_front = normalizeURL(imageData);
this.upload_dl_front(imageData);
}, error => {
this.func.showAlert('Error',JSON.stringify(error));
});
}
upload_dl_front(imageFileUri: any): void {
this.file.resolveLocalFilesystemUrl(imageFileUri)
.then(entry => (<FileEntry>entry).file(file => this.readFile_dl_front(file)))
.catch(err => console.log('Error',JSON.stringify(err)));
}
private readFile_dl_front(file: any) {
const reader = new FileReader();
reader.onloadend = () => {
const imgBlob = new Blob([reader.result], { type: file.type });
this.dl_front_imageUri = imgBlob;
this.dl_front_imageName = file.name;
alert(this.dl_front_imageName)
const img = new FormData();
img.append('image', this.dl_front_imageUri, this.dl_front_imageName)
this.api.test(img).then(data=>alert("final: "+data))
};
reader.readAsArrayBuffer(file);
}
and my api function is
test(image){
let headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
});
return new Promise( resolve => {
this.http.post(url, image, { headers: headers})
.subscribe(
data => {
resolve(data['message']);
},
error => {
resolve(error.statusText);
}
);
});
}
and i am getting the file in my laravel server as
$image = $request->file('image');
but i am getting null in the image parameter.
What am i doing wrong here?
You should remove the headers in the api call.
test(image){
return new Promise( resolve => {
this.http.post(url, image)
.subscribe(
data => {
resolve(data['message']);
},
error => {
resolve(error.statusText);
}
);
});
}

How to get response times from Axios

Can anyone suggest any ways to get response times from Axios? I've found axios-timing but I don't really like it (controversial, I know). I'm just wondering if anyone else has found some good ways to log response times.
You can use the interceptor concept of axios.
Request interceptor will set startTime
axios.interceptors.request.use(function (config) {
config.metadata = { startTime: new Date()}
return config;
}, function (error) {
return Promise.reject(error);
});
Response interceptor will set endTime & calculate the duration
axios.interceptors.response.use(function (response) {
response.config.metadata.endTime = new Date()
response.duration = response.config.metadata.endTime - response.config.metadata.startTime
return response;
}, function (error) {
error.config.metadata.endTime = new Date();
error.duration = error.config.metadata.endTime - error.config.metadata.startTime;
return Promise.reject(error);
});
This is my solution, by setting the header in the interceptor:
import axios from 'axios'
const url = 'https://example.com'
const instance = axios.create()
instance.interceptors.request.use((config) => {
config.headers['request-startTime'] = process.hrtime()
return config
})
instance.interceptors.response.use((response) => {
const start = response.config.headers['request-startTime']
const end = process.hrtime(start)
const milliseconds = Math.round((end[0] * 1000) + (end[1] / 1000000))
response.headers['request-duration'] = milliseconds
return response
})
instance.get(url).then((response) => {
console.log(response.headers['request-duration'])
}).catch((error) => {
console.error(`Error`)
})
Here's another way to do it:
const instance = axios.create()
instance.interceptors.request.use((config) => {
config.headers['request-startTime'] = new Date().getTime();
return config
})
instance.interceptors.response.use((response) => {
const currentTime = new Date().getTime()
const startTime = response.config.headers['request-startTime']
response.headers['request-duration'] = currentTime - startTime
return response
})
instance.get('https://example.com')
.then((response) => {
console.log(response.headers['request-duration'])
}).catch((error) => {
console.error(`Error`)
})
piggybacking off of #user3653268- I modified their answer to use with react hooks and display x.xxx seconds using a modulo.
import React, { useState } from 'react';
import axios from 'axios';
export default function Main() {
const [axiosTimer, setAxiosTimer] = useState('');
const handleSubmit = () => {
let startTime = Date.now();
axios.post('urlstuff')
.then(response => {
console.log('handleSubmit response: ', response);
axiosTimerFunc(startTime);
})
.catch(err => {
console.log("handleSubmit error:", err.response.data.message)
axiosTimerFunc(startTime);
setLoading(false);
});
}
const axiosTimerFunc = (startTime) => {
let now = Date.now();
let seconds = Math.floor((now - startTime)/1000);
let milliseconds = Math.floor((now - startTime)%1000);
setAxiosTimer(`${seconds}.${milliseconds} seconds`);
}
return(
<div>
<h2>Load Time: {axiosTimer}</h2>
</div>
)
}
easy way do this with async \ await, but not ideal :
const start = Date.now()
await axios.get(url)
const finish = Date.now()
const time = (finish - start) / 1000
This would be time about of axios call. Not so ideal, but showing and easy to implement
Another simple way to do it :
axios.interceptors.response.use(
(response) => {
console.timeEnd(response.config.url);
return response;
},
(error) => {
console.timeEnd(error.response.config.url);
return Promise.reject(error);
}
);
axios.interceptors.request.use(
function (config) {
console.time(config.url );
return config;
}, function (error) {
return Promise.reject(error);
});
Its way long after but this is my simple workaround
function performSearch() {
var start = Date.now();
var url='http://example.com';
var query='hello';
axios.post(url,{'par1':query})
.then(function (res) {
var millis = Date.now() - start;
$('.timer').html(""+Math.floor(millis/1000)+"s")
})
.catch(function (res) {
console.log(res)
})
}
this is my workaround
actually you can get it through the "x-response-time" header that you get from the response on the axios request
axios({
method: 'GET',
url: 'something.com',
})
.then((response) => {
console.log(response.headers['x-response-time']);
})