fetching a plain/text with redux-api-middleware - reactjs

Can I fetch a txt file with redux-api-middleware? If yes, how? According to the doc the body is set to undefined if not application/JSON compliant, but what I will get is a simple text/plain

Basically what I did was altering fetch action and doing my custom stuff with response data. Example:
fetch: async (...args) => {
const response = await fetch(...args);
if (response.ok && response.status === 200) {
const reader = response.body.getReader();
const type = response.headers.get('content-type');
const filename = filenameExtractor(response.headers.get('content-disposition'));
const { value } = await reader.read();
fileSaver({ value, filename, type });
}
return response;
},
This piece of code reads response contents and pushes them for download to the browser. filenameExtractor and fileSaver are custom functions. This way response is not altered.
Using such approach you can further modify response and create json yourself for data to be able to be saved in redux like:
const response = await fetch(...args);
return new Response(
JSON.stringify({ data: response.body })
);

Related

how to use form data with custom hook in react js?

I have my custom axios hook, to send data without files it works perfectly, but when I send files it does not recognize any data.
......
const axiosFetch = async (params) => {
const { method, url, data } = params;
try {
setLoading(true);
const control = new AbortController();
setController(control);
const res = await axios[method.toLowerCase()](url, {
...data,
signal: control.signal,
});
setResponse(res.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
......
If I modify this part, the file upload works, but it loses the signal. How can I implement both properties.
const res = await axios[method.toLowerCase()](url, data);
For the POST, PUT, and PATCH requests data and configuration are the second and third arguments.
See Instance Methods
Try instead:
const res = await axios[method.toLowerCase()](
url,
data,
{ signal: control.signal }
);
For GET, DELETE, HEAD, and OPTIONS requests, omit the data argument.
const res = await axios[method.toLowerCase()](
url,
{ signal: control.signal }
);
You'll need to split these request types out logically.
Alternatively you can use a request config:
const res = await axios({
url,
method: method.toLowerCase(),
data,
signal: control.signal,
});

Receiving undefined instead of JSON

I am using React axios to receive the JSON (array of objects) from server (server side is written on Go, I checked via Postman, JSON is sent properly).
Here is how I get the data on client side:
export const getPostData = async () => {
const URL = 'http://localhost:8083/test'
try {
const { data: { data }} = await axios.get(URL);
console.log(data)
return data;
} catch (error) {
console.log(error)
}
};
And this is how the getPostData is called in App.js:
const App = () => {
const [ posts, setPosts ] = useState([]);
useEffect(() => {
getPostData()
.then((data) => {
setPosts(data)
console.log(data)
})
},[]);
The problem is I get undefined in browser console. I found many similar questions asked here, but I could not find the solution (the Access-Control-Allow-Origin header is set when I send the JSON).
What should I learn more, where could be the problem? I would be very grateful for any help!
If this could be helpful, here is how I send the JSON in Go:
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Content-Type", "application/json")
c.JSON(http.StatusOK, gin.H{
"message": "Hello",
})
This looks suspect:
const { data: { data }} = await axios.get(URL);
That tries to read a property called data from an object on the data property of the response from axios, like this without the destructuring:
const data = (await axios.get(URL)).data.data;
Your Go code doesn't look like it puts a {"data": ___} wrapper around what it sends, and Axios only adds one layer of {data: ___} wrapper to what it gives you in the response, not two.
If you want the object from the JSON response, remove the inner destructuring:
const { data } = await axios.get(URL);
data will be {message: "Hello"} assuming the Go code sends the JSON {"message": "Hello"}.
Separately, your JavaScript code seems to expect an array of posts, but your Go code is just sending {"message": "Hello"}.

Why do I keep getting Syntax Error: JSON parse even though my code works with another API?

Working with React and I continually get a SyntaxError:JSON.parse when I try to fetch from the API. I'm able to fetch the data just fine when working with a different API.
What am I doing wrong here? I'm a complete newb when it comes to API's so please don't eviscerate me if this is a stupid question :D
const API_URL = 'www.themealdb.com/api/json/v1/1/search.php?s=Arrabiata';
const getMealRequest = async()=>{
const response = await fetch(API_URL)
const data = await response.json()
console.log(data)
}
useEffect(()=>{
getMealRequest()
},[])
There is an error on your API call which is not related to this question. You didn't implement try/cath block with the getMealRequest so in the failure case, the response is not a valid object to parse and you get the mentioned error.
To solve it:
const getMealRequest = async () => {
try {
const response = await fetch('https://www.themealdb.com/api/json/v1/1/search.php?s=Arrabiata'
)
const data = await response.json()
console.log(data)
} catch (error) {
console.log(error)
}
}
Now inspect your console and see what is wrong with your API call. also, you can check the network tab for further detail about the request and response on calling API_URL.
const API_URL = 'https://www.themealdb.com/api/json/v1/1/search.php?s=Arrabiata';
const getMealRequest = async() => {
const response = await fetch(API_URL)
const data = await response.json()
console.log(data)
}
getMealRequest()
You forgot to include protocol:
incude https:// before the address and it will work:

POST 400 (BAD REQUEST) when uploading file between React and Flask

Keep getting a 400 error when uploading an excel file in my react front end and passing it to a flask api.
The flask backend route is as such:
def post(self):
name = request.form['name']
file = request.files['file']
(....do stuff with file and name)
The react API call is as follows:
export const uploadFile = async (file) => {
const data = new FormData()
data.append("file", file)
data.append("name", 'temp')
const api_url = "http://localhost:5000/uploadFile"
const settings = {
method: "POST",
body: data
}
try {
const response = await fetch(api_url, settings)
const result = await response.json()
if (result.message === 'OK') {
return result
}
} catch (error) {
return "Unable to load file"
}
}
Interesting the NAME field is getting received by Flask but the file object for some reason is not being processed by Flask.
I've tried adding a multi-part/formdata Content-Type in the React Fetch API call as well, but that doesn't help either.
Figured out the issue.
Turns out you need to pass the [0] following in the React call:
data.append("file", file[0])
I was trying to post an image file to Flask just using a vanilla JS fetch call, and #Anubhav's answer helped me realise that I needed to use FormData, rather than just posting the file/blob as the body. In case someone is looking for some full, working example code:
<input type="file" id="fileEl"/>
let formData = new FormData();
formData.append("file", fileEl.files[0]);
let response = await fetch("http://example.com/upload", {
method: "POST",
body: formData,
}).then(r => r.json());

Redux testing multipart/form-data with jest, nock, axios and jsdom

I'm using jest+nock+jsdom modules to test my React\Redux application.
I need to test this async action function:
export function updateUserPhoto (file, token) {
const data = new FormData()
data.append('file', file)
return dispatch => {
dispatch(userPutPhotoRequest())
return axios({
method: 'PUT',
headers: {
'x-access-token': token
},
data: data,
url: API_URL + '/user/photo'
})
.then(res => dispatch(userPutPhotoSuccess(res.data)))
.catch(err => dispatch(userPutPhotoFilure(err)))
}
}
So i'm using jsdom to provide FormData and File objects into tests:
const {JSDOM} = require('jsdom')
const jsdom = (new JSDOM(''))
global.window = jsdom.window
global.document = jsdom.window.document
global.FormData = jsdom.window.FormData
const File = jsdom.window.File
global.File = jsdom.window.File
And this is the method to test "upload photo" function:
it('creates USER_UPDATE_SUCCESS when updating user photo has been done', () => {
const store = mockStore(Map())
const file = new File([''], 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
const expectedFormData = new FormData()
expectedFormData.append('file', file)
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', expectedFormData)
.reply(200, {body: {}})
const expectedActions = [
{
type: ActionTypes.USER_PUT_PHOTO_REQUEST
},
{
type: ActionTypes.USER_PUT_PHOTO_SUCCESS,
response: {
body: {}
}
}
]
return store.dispatch(actions.updateUserPhoto(file, token))
.then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
Where i'm using nock to mock axios requests, redux-mock-store to mock Redux store.
Creating File and FormData objects to compare it with response from axios.
And then i'm calling action function passing file and token as parameters.
In production action function works and dispatch action success fine. But in testing i'm receiving error:
Error: Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream
When i pass into axios empty object as data test passes, so problem in FormData object.
How can i mock FormData object for axios in an appropriate way to make this test work ?
This answer is coming way too late, but I was looking to do something similar and I wanted to post a solution here that someone else might stumble across and find useful.
The main problem here is that nock mocks network requests and not Javascript libraries. FormData is a Javascript object that eventually gets transformed to text when making network requests. By the time the FormData object makes it to nock, its been converted to a string or a Buffer, hence the error you see. nock is unable to use the FormData object for comparison.
You have a few options:
1. Easiest solution
Just don't match against the data in the PUT request. The reason you are mocking is because you don't want a real HTTP request to go out but you want a fake response back. nock only mocks the request once, so if you mock all PUT requests to /user/photo nock will catch it but only for that test:
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo')
.reply(200, {body: {}})
Before you implement the test this way, think about what your test is trying to verify. Are you trying to verify that the file is sent in the HTTP request? If yes, then this is a poor option. Your code could send a completely different file than the one dispatched and still pass this test. If however you have another test to verify the file is being put in the HTTP request properly then this solution might save you some time.
2. Easy solution for getting nock to fail on not matching the request
If you do want the test to fail if your code passed a corrupted or wrong file, then he simplest solution would be to test for the filename. Since your file is empty there is no need to match the content, but we can match on the filename:
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', /Content-Disposition\s*:\s*form-data\s*;\s*name="file"\s*;\s*filename="filename.txt"/i)
.reply(200, {body: {}})
This should match the simple case where you have one file uploading.
3. Matching the content of form data fields
Say you have additional fields to be added to your request
export function updateUserPhoto (file, tags, token) {
const data = new FormData()
data.append('file', file)
data.append('tags', tags)
...
OR you have fake content in the file that you want to match on
const file = new File(Array.from('file contents'), 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
This is where things get a bit complex. Essentially what you need to do is to parse the form data text back into an object and then write your own matching logic.
parse-multipart-data is a fairly simple parser that you could use:
https://www.npmjs.com/package/parse-multipart-data
Using that package your test might look something like this
it('creates USER_UPDATE_SUCCESS when updating user photo has been done', () => {
const store = mockStore(Map())
const file = new File(Array.from('file content'), 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', function (body) { /* You cannot use a fat-arrow function since we need to access the request headers */
// Multipart Data has a 'boundary' that works as a delimiter.
// You need to extract that
const boundary = this.headers['content-disposition']
.match(/boundary="([^"]+)"/)[1];
const parts = multipart.Parse(Buffer.from(body),boundary);
// return true to indicate a match
return parts[0].filename === 'filename.txt'
&& parts[0].type === 'text/plain'
&& parts[0].data.toString('utf8') === 'file contents'
&& parts[1].name === 'tags[]'
&& parts[1].data.toString('utf8') === 'tag1'
&& parts[2].name === 'tags[]'
&& parts[2].data.toString('utf8') === 'tag2';
})
.reply(200, {body: {}})
const expectedActions = [
{
type: ActionTypes.USER_PUT_PHOTO_REQUEST
},
{
type: ActionTypes.USER_PUT_PHOTO_SUCCESS,
response: {
body: {}
}
}
]
return store.dispatch(actions.updateUserPhoto(file, ['tag1', 'tag2'], token))
.then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
I was dealing with the same issue, the problem was that axios was setting http as the default adapter. And xhr is the one you need.
// axios/lib/defaults.js
function getDefaultAdapter() {
var adapter;
// Only Node.JS has a process variable that is of [[Class]] process
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
}
return adapter;
}
So, setting the xhr adapter explicitly on the axios calls worked for me.
Something like:
export function updateUserPhoto (file, token) {
const data = new FormData()
data.append('file', file)
return dispatch => {
dispatch(userPutPhotoRequest())
return axios({
method: 'PUT',
headers: {
'x-access-token': token
},
adapter: require('axios/lib/adapters/xhr'),
data: data,
url: API_URL + '/user/photo'
})
.then(res => dispatch(userPutPhotoSuccess(res.data)))
.catch(err => dispatch(userPutPhotoFilure(err)))
}
}
Also, I had issues with nock and CORS, so, if you have same issue, you can add access-control-allow-origin header
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
})
.defaultReplyHeaders({ 'access-control-allow-origin': '*' })
.put('/user/photo', expectedFormData)
.reply(200, {body: {}})

Resources