React + fetch: adding extra characters and backslashes to my url - reactjs

I have this code in React 17
useEffect(() => {
getLocalJson('../json/login/login.json', props.headers)
.then(resp => {
setFields(resp);
});
}, [props.headers]);
And the getLocalJson method is in a different file:
export const getLocalJson = async (url, headers) => {
console.log(url)
const resp = await fetch(url, {'headers': headers});
const json = await resp.json();
return json;
}
However the call to load the local JSON file from the public folder is:
Request URL: http://localhost:3000/json/login/%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5Clogin.json
Ths is the JSON
[
{
"order": 0,
"systemName": "title",
"friendlyName": "Login",
"dataType": {
"type": "TITLE"
}
},
{
"order": 1,
"required": true,
"systemName": "username",
"friendlyName": "Username",
"errorMsg": "Invalid username",
"dataType": {
"type": "TEXT"
}
},
{
"order": 2,
"required": true,
"systemName": "password",
"friendlyName": "Password",
"errorMsg": "Invalid password",
"dataType": {
"type": "PASSWORD"
}
},
{
"order": 3,
"systemName": "title",
"friendlyName": "Login",
"dataType": {
"type": "BUTTON",
"submit": true
}
}
]
And it makes the call over and over and over
This exact code works on my ubuntu dev box, but is failing as abovw on my windows box

I think there is some issue with the way you are passing down the headers, look into the documentation to have a better idea.

Put your function in the body of your component where you're using useEffect and wrap it with useCallback like this:
const getLocalJson = useCallback( async (url, headers) => {
console.log(url)
const resp = await fetch(url, {'headers': headers});
const json = await resp.json();
return json;
},[])

Related

How to format API response

I'm writing a react app that fetches data from a DynamoDB table using a Lambda function. The function successfully retrieves an array of objects as shown below. I've tested this in the AWS console and in Postman.
[
{
"Obj1Key1": {
"S": "Obj1Value1"
},
"Obj1Key2": {
"S": "Obj1Value2"
},
"Obj1Key3": {
"S": "Obj1Value3"
},
"Obj1Key4": {
"S": "Obj1Value4"
}
},
{
"Obj2Key1": {
"S": "Obj2Value1"
},
"Obj2Key2": {
"S": "Obj2Value2"
},
"Obj2Key3": {
"S": "Obj2Value3"
},
"Obj2Key4": {
"S": "Obj2Value4"
}
},
{
"Obj3Key1": {
"S": "Obj3Value1"
},
"Obj3Key2": {
"S": "Obj3Value2"
},
"Obj3Key3": {
"S": "Obj3Value3"
},
"Obj3Key4": {
"S": "Obj3Value4"
}
}
]
..and below is the function in the React app that gets the data:
...
...
const [ response , setResponse ] = useState();
...
async function fetchData() {
try {
const body = JSON.stringify({
etc....
});
await fetch("https://url..etc", {
method: "POST",
headers: { "Content-Type": "application/json"},
body: body
}).then(res => setResponse(res))
} catch (error) {
console.log(error);
}
};
However When I get the raw response from the React app, and I console log it, my console shows the following
How can I get the array of objects from this response? I tried different combinations of parsing and stringifying but was not able to get the array as an array. Adding .then(res => res.text()). before .then(res => setResponse(res)) gets me the response I'm looking for but as a string. I need to get it as an array.
Thank you

Axios send strange array to React

I geting the data back from my API in React from a post request and I get just the first object of the entire Array.prototype
My API for the upload:
router.post("/uploads", upload.any(), async (req, res) => {
try {
if (!req.files) {
res.send({
status: false,
message: "No file uploaded",
});
} else {
let data = req.files;
res.send({
status: true,
message: "Files are uploaded",
data: data,
});
}
} catch (error) {
res.status(500).send(err);
}
});
POSTMAN gives me back:
{
"status": true,
"message": "Files are uploaded",
"data": [
{
"fieldname": "uploads\n",
"originalname": "46335256.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "client/uploads/",
"filename": "46335256-2020-08-04.jpg",
"path": "client/uploads/46335256-2020-08-04.jpg",
"size": 19379
},
{
"fieldname": "uploads\n",
"originalname": "120360358.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "client/uploads/",
"filename": "120360358-2020-08-04.jpg",
"path": "client/uploads/120360358-2020-08-04.jpg",
"size": 78075
}
]
}
perfect!
this is my function in React to upload
const uploadFiles = () => {
uploadModalRef.current.style.display = "block"
uploadRef.current.innerHTML = "File(s) Uploading..."
for (let i = 0; i < validFiles.length; i++) {
const formData = new FormData()
formData.append("images", validFiles[i])
axios
.post("http://localhost:5000/api/db/uploads", formData, {
onUploadProgress: progressEvent => {
const uploadPercentage = Math.floor(
(progressEvent.loaded / progressEvent.total) * 100
)
...// code for graphic upload
},
})
.then(resp => {
console.log(resp.data.data)
resp.data.data.map(item => {
console.log(item)
})
})
.catch(() => {
... // code
}
}
and with this I get (from the console):
[{…}]
0:
destination: "client/uploads/"
encoding: "7bit"
fieldname: "images"
filename: "46335256-2020-08-04.jpg"
mimetype: "image/jpeg"
originalname: "46335256.jpg"
path: "client/uploads/46335256-2020-08-04.jpg"
size: 19379
__proto__: Object
length: 1
__proto__: Array(0)
is an array(if I map it works) but with just the first object.
How is it possible ??
I tried even with async/await but nothing changes
Where I'm mistaking?
Thanks!

Connect uploaded file with related model with Strapi

I am using Strapi, Sqlite3 and React.
I want to send a form with a file attached.
I have a Job model, which looks like this:
{
"connection": "default",
"collectionName": "jobs",
"info": {
"name": "job",
"description": ""
},
"options": {
"increments": true,
"timestamps": true,
"comment": ""
},
"attributes": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"email": {
"type": "string"
},
"resume": {
"model": "file",
"via": "related",
"plugin": "upload"
},
"jobcategory": {
"model": "jobcategory",
"via": "jobs"
}
}
}
I am sending text input with submitCareer method, and uploadFile for uploading:
export async function submitCareer(url, formValues) {
try {
const entries = await rootUrl.createEntry(url, formValues);
return entries;
} catch (err) {
console.log(err);
}
}
export async function uploadFile(formValues) {
try {
const upload = await rootUrl.upload(formValues);
return upload;
} catch (err) {
console.log(err);
}
}
This is the usage in my Career component:
const handleSubmit = (event) => {
const formData = new FormData();
formData.append("files", fileInput.current.files[0]);
submitCareer('jobs', values);
uploadFile(formData);
setValues({
firstName: '',
lastName: '',
email: '',
resume: null
})
event.preventDefault();
}
I get this response:
{
"id": 66,
"firstName": "John",
"lastName": "Doe",
"email": "john#gmail.com",
"jobcategory": null,
"lname": null,
"created_at": 1561988031279,
"updated_at": 1561988031279,
"resume": {}
}
So, how can i connect resume with Job model?
Linking model to a file
You must create process with to two steps:
Create File -> POST /upload.
Create Jobs with id from FileResponse -> POST /jobs.
Example:
const handleSubmit = (event) => {
const formData = new FormData();
formData.append("files", fileInput.current.files[0]);
resumeUploadFile = await uploadFile(formData);
const jobsInput = {...jobs, ...{resume: resumeUploadFile.id}}
await submitCareer('jobs', jobsInput);
setValues({
firstName: '',
lastName: '',
email: '',
resume: null
})
event.preventDefault();
}
https://strapi.io/documentation/3.0.0-beta.x/guides/upload.html#file-upload
Linking files to an entry
You can linking too file to an entry wich is created, then you create first Jobs and then link upload ResumeFile with added new fields like refId from Jobs (Jobs -> id), ref in your case jobs and field=resume
https://strapi.io/documentation/3.0.0-beta.x/guides/upload.html#examples
When you want to upload a file and link it to an entry.
You have to first create the entry if it's not already done.
And then upload your file by sending the image information of the entry.
All the documentation is here https://strapi.io/documentation/3.0.0-beta.x/guides/upload.html#examples

ajax request doesn't execute when run test with marble diagrams

I use rxjs v6 and redux-observable v1
I have epic that send request to server and try to test epic like in doc. When I run test epic before send request emit 3 actions and it see on test result, but when epic comes to ajax call test finish. To mock request I use nock lib.
Epic:
import { ofType } from 'redux-observable'
import { of, merge, from } from 'rxjs'
import {
switchMap,
catchError
} from 'rxjs/operators'
import { apiRequest, handleAsyncError$ } from '../../async/lib'
import { actions as asyncActions } from '../../async'
import { LOADING_TYPES } from '../../async/constants'
import { actions as authActions } from '../reducer'
const setSignInLoading = (status) => of(asyncActions.setLoading({ type: LOADING_TYPES.signIn, status }))
const emitSignInPending = () => merge(
setSignInLoading(true),
of(authActions.signInPending())
)
const emitSignInSuccess = (payload) => merge(
setSignInLoading(false),
of(authActions.signInSuccess(payload))
)
const emitSignInFailed = (payload) => merge(
setSignInLoading(false),
handleAsyncError$({
action: authActions.signInFailure,
payload
})
)
// --------- ajax call -----------
const startSignIn = (payload) => apiRequest({
path: '/auth/signin/manager',
method: 'post',
body: payload
})
const mapSignInAction$ = ({ payload }) => merge(
// --------- emit 3 actions -----------
emitSignInPending(),
// --------- finish test -----------
startSignIn(payload)
.pipe(
switchMap((emitSignInSuccess)),
catchError(emitSignInFailed)
)
)
const signInEpic = action$ =>
action$
.pipe(
ofType(authActions.signIn),
switchMap(mapSignInAction$)
)
export default signInEpic
apiRequest:
import { get } from 'lodash'
import { throwError } from 'rxjs'
import { ajax } from 'rxjs/ajax'
import { map, catchError } from 'rxjs/operators'
import { API } from '../../../../config'
const apiRequest = ({ token, path, method, body }) => {
const settings = {
url: `${API}${path}`,
headers: { 'Content-Type': 'application/json' },
responseType: 'json',
crossDomain: true,
method,
body
}
if (token) {
settings.headers['Authorization'] = `Bearer: ${token}`
}
return ajax(settings)
.pipe(
catchError((request) => {
const error = get(request, 'response.error')
return throwError({ error, request })
}),
map(({ response }) => response)
)
}
export default apiRequest
Test:
nock(API)
.post('/auth/signin/manager')
.reply(200, response)
scheduler.run(({ hot, expectObservable }) => {
const source = hot('-a|', { a: authActions.signIn({ email: 'manager', password: '123123' }) })
const output$ = epic(source)
expectObservable(output$).toBe('-(bcde)', {
b: asyncAction.setLoading({ type: 'signIn', status: true }),
c: authActions.signInPending(),
d: asyncAction.setLoading({ type: 'signIn', status: false }),
e: authActions.signInSuccess(response)
})
})
Result:
Expected:
[{"frame": 1, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"error": false, "payload": {"status": true, "type": "signIn"}, "type": "[8] async/setLoading"}}}, {"frame": 1, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"error": false, "payload": undefined, "type": "[3] [2] auth/signIn/pending"}}}, {"frame": 1, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"error": false, "payload": {"status": false, "type": "signIn"}, "type": "[8] async/setLoading"}}}, {"frame": 1, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"error": false, "payload": {"data": {"token": "skldjf", "user": {"email": "manager", "id": 2, "passwordHash": "asdf", "passwordSalt": "6819c23dc7", "role": {"name": "user"}, "roleId": 1}}}, "type": "[4] [2] auth/signIn/success"}}}]
Received:
[{"frame": 1, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"error": false, "payload": {"status": true, "type": "signIn"}, "type": "[8] async/setLoading"}}}, {"frame": 1, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"error": false, "payload": undefined, "type": "[3] [2] auth/signIn/pending"}}}]
Ajax resolves as microtask so epic doesn't emit it sync, so marble diagrams can't handle it, I can't find how to do it with marble diagrams. So simple solutions is:
it('return token and user 2', async (done) => {
const response = {...}
nock(API)
.post('/auth/signin/manager')
.reply(200, response)
const source = authActions.signIn({ email: 'manager', password: '123123' })
const action$ = ActionsObservable.of(source)
epic(action$).pipe(toArray()).subscribe((actions) => {
expect(actions).toEqual([
asyncAction.setLoading({ type: 'signIn', status: true }),
authActions.signInPending(),
asyncAction.setLoading({ type: 'signIn', status: false }),
authActions.signInSuccess(response)
])
done()
})
})
Please write if you found how do it with marble diagrams.

Node async map not excecuting properly inside redux action

I connect to an API which is returning data in this format:
[
{
"id": 23,
"name": "11:40AM July 2",
"airplayEnabled": false,
"airplayCodeEnabled": true,
"type": 0,
"groups": [
{
"id": 4,
"name": "Hallway",
"notes": "Any devices present in the hallway",
"_pivot_device_id": 23,
"_pivot_group_id": 4
},
{
"id": 5,
"name": "Middle school",
"notes": "In classrooms 6-8",
"_pivot_device_id": 23,
"_pivot_group_id": 5
}
],
"mac": "123456789ABC"
},
{
"id": 26,
"name": "1:54PM July 5",
"airplayEnabled": false,
"airplayCodeEnabled": true,
"type": 0,
"groups": [
{
"id": 5,
"name": "Middle school",
"notes": "In classrooms 6-8",
"_pivot_device_id": 26,
"_pivot_group_id": 5
}
],
"mac": "123456789ABC"
}
]
I want to modify each returned item and remove the excess data, so it will end up like this:
[
{
"id": 23,
"name": "11:40AM July 2",
"airplayEnabled": false,
"airplayCodeEnabled": true,
"type": 0,
"groups": [4, 5],
"mac": "123456789ABC"
},
{
"id": 26,
"name": "1:54PM July 5",
"airplayEnabled": false,
"airplayCodeEnabled": true,
"type": 0,
"groups": [5],
"mac": "123456789ABC"
}
]
I created the following code to remove the excess data:
const deleteExcessInfo = async function(group) {
if(group.id) {
return group.id;
} else {
return null;
}
}
const modGroups = async function(device) {
async.map(device.groups, deleteExcessInfo, function(err, res) {
device.groups = res;
});
return device;
}
var newDevices;
async.map(devices, modGroups, (error, results) => {
console.log("results are " + JSON.stringify(results));
});
When I execute this code in a stand-alone node program (run from the command line), I get the expected output with the excess data removed. However, it does not work when I put it in a redux action, like this:
export function getDevices() {
return function(dispatch) {
return fetch("http://my-awesome-api:1357/device", {mode: "cors"})
.then(handleErrors)
.then(json)
.then(function(data) {
async.map(data, fixGroups, async function(error, res) {
console.log("dispatching...");
console.log(JSON.stringify(res));
dispatch({
type: "GET_DEVICES",
devices: res
});
});
}).catch((err) => {
dispatch({
type: "GET_DEVICES_ERROR",
error: err
});
alert("Oh shoot we have an error " + err);
return(err);
});
}
};
I do not see "dispatching..." or the res printed to the console, so it appears the callback function is not being executed for some reason. Any ideas on why it's not working, and on how to fix it? Thanks!
What I've tried
I tried implementing a promise as Moonjsit recommended, but it did not work. I have also tried implementing a promise in my Redux action, like this:
export function getDevices() {
return function(dispatch) {
return fetch("http://localhost:1357/device", {mode: "cors"})
.then(handleErrors)
.then(json)
.then(function(data) {
return new Promise((resolve, reject) => {
async.map(data, fixGroups, (error, results) => {
console.log("results are " + JSON.stringify(results));
resolve(dispatch({
type: "GET_DEVICES",
devices: results
});
});
});
}).catch((err) => {
dispatch({
type: "GET_DEVICES_ERROR",
error: err
});
alert("Oh shoot we have an error " + err);
return(err);
});
}
};
Either way, the code inside the callback does not execute.
Hey so you have a problem in your code.
const modGroups = async function(device) {
async.map(device.groups, deleteExcessInfo, function(err, res) {
device.groups = res;
});
return device;
}
Calling function above immediately starts async.map which is asynchronus, and then returns unchanged device variable. So effectively it gives same reults as:
const modGroups = async function(device) {
return device;
}
To fix it you can eg. wrap it in Promise:
const modGroups = async function(device) {
return new Promise((resolve, reject) => {
async.map(device.groups, deleteExcessInfo, function(err, res) {
if (err) {
reject(err);
} else {
device.groups = res;
resolve(device);
});
})
}

Resources