React Transforming Server Response (adding extra field to an Object) - javascript

I have a basic function that sends an axios GET request and sets the response (array of objects) in state.
getData(){
axios.get(url)
.then(response => this.setState({ data: response.data })
}
However I want to add an additional field to the object. How can I do this?
I figure something like:
getData(){
axios.get(url)
.then(response => transformRes(response.data))
.then(newResponse => this.setState({ data: newResponse })
}
transformRes(data){
data.forEach(d => {
// do something ??
}
// return newData ??
}
mock example data (from Server)
(2) [{...}, {...}]
0: {id: 1, name: 'foo'}
1: {id: 2, name: 'bar'}
mock expected result (after transformRes)
(2) [{...}, {...}]
0: {id: 1, name: 'foo', desc: 'active'} // added desc field.
1: {id: 1, name: 'bar', desc: 'active'} // added desc field.

Just add property in map function:
getData(){
axios.get(url)
.then(response => {
const res = response.data
this.setState({ data: res.map(object => {
return {...object, desc: 'active'}
})})
})
}
BTW ... is spread operator and ...object means every key/value in object.

Basically, you want to map the old object to a new object. You're on the right track using .forEach
getData() {
axios.get(url)
.then(response => {
var newData = response.map((data) => {
data.desc = 'active'; //add your properties here
return data;
});
this.setState({data: newData});
});
}
Let me know if this won't work for your situation or you have questions.

Related

Cannot populate select list from Database

I am new in frontend application development and trying to populate a Select list from database, but cannot set options using the following approach using similar ones to How to populate select dropdown elements with data from API - ReactJS
const [options, setOptions] = useState([]);
useEffect(() => {
async function fetchData() {
const results = []
// Fetch data
GetWithAuth("/categories")
.then(res => res.json())
.then(value => {
value.map(element => {
// --> the value has data and push them to results variable
results.push({
key: element.name,
value: element.id,
});
});
})
// --> options cannot be filled with results and just have the following value
setOptions([
{key: 'Select a category', value: ''},
...results
])
}
// Trigger the fetch
fetchData();
}, []);
Here is the service method that returns Promise:
export const GetWithAuth = (url) => {
var request = fetch("/api" + url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("tokenKey"),
},
});
return request;
};
So, how should I populate my select list from database? What is the problem with the code above?
You should update your state inside "then" function, also instead of using map function, you should use forEach (because you are not using return keyword), but if you want to use map function, then you can use it this way
const [options, setOptions] = useState([]);
useEffect(() => {
async function fetchData() {
// Fetch data
GetWithAuth("/categories")
.then(res => res.json())
.then(value => {
const results = value.map(element => {
// --> the value has data and push them to results variable
return {
key: element.name,
value: element.id,
}
});
setOptions([
{key: 'Select a category', value: ''},
...results,
])
})
}
// Trigger the fetch
fetchData();
}, []);
But if you want to use forEach function, then you can do it this way
const [options, setOptions] = useState([]);
useEffect(() => {
async function fetchData() {
// Fetch data
GetWithAuth("/categories")
.then(res => res.json())
.then(value => {
const results = [];
value.forEach(element => {
// --> the value has data and push them to results variable
results.push({
key: element.name,
value: element.id,
})
});
setOptions([
{key: 'Select a category', value: ''},
...results,
])
})
}
// Trigger the fetch
fetchData();
}, []);

even though fetch data is transformed into array, i cannot access those elements

After fetching some date from external API using this code
let fetchedData = [];
fetch(api)
.then(res => res.json())
.then(res => JSON.parse(res.response))
.then(arr => arr.map(e => fetchedData.push(e)) )
.catch(err => err);
fetchedData is populated without any problem, at that moment data inside look like this
{
Key: '0',
Record: {
a: 'a0',
b: 'b0'
}
},
{
Key: '1',
Record: {
a: 'a1',
b: 'b1'
}
},
{
Key: '2',
Record: {
a: 'a2',
b: 'b2'
}
}
for console.log(fetchedData) chrome tools display something like that:
[]
0: {Key: "0", Record: {…}}
1: {Key: "1", Record: {…}}
2: {Key: "2", Record: {…}}
length: 3
__proto__: Array(0)
but if I want to access and extract this array like console.log(fetchedData[0])
then i shows as undefined
or console.log(fetchedData.length) shows 0
but in the same script next call to console.log(fetchedData) shows normal array like previously
as a result in chrome tools i have
array
undefined
array
using code mentioned earlier
The reason this is happening is because you are doing an asynchronous operation, fetch returns a promise, so async means it will run after sync code runs: in this example it will run after js let fetchedData = []; runs. if that makes sense.
To handle this situation you could call this fetch operation inside a
js componentDidMount() {} or js useEffect(() => {})
This is an example, but try it #wokadakow with your data and check the result!
function App() {
const [data, setData] = useState([]);
async function AsyncData() {
// Here goes your async data, just replace it
// this is fake data
return await [{
id: 1,
name: 'john'
}]
}
useEffect(() => {
AsyncData()
.then((data) => setData(data))
}, [])
return (
<div className="App">
<h1> TEST DATA </h1>
<ul>
{data.map(d => {
return <li key={d.id}> {d.name} </li>
})}
</ul>
</div>
)
}
render(<App />, document.getElementById('root'));
I hope this helps!
You will have to wait until your fetch is done either by using async/await or by adding your console.log call into another then method:
let fetchedData = [];
fetch(api)
.then(res => res.json())
.then(res => JSON.parse(res.response))
.then(arr => arr.map(e => fetchedData.push(e)) )
.then(arr => console.log(arr) )
.catch(err => err);

How to use Ramda Pipe function with a mix of promises and static callbacks?

Based on the help of #ScottSauyet I have been able to create a function resolving static and promise based callbacks for an initial data object.
Now I want to be able to pipe this data object through a series of callbacks, but run into trouble once I add multiple promises into the mix.
Current setup
// Libaries
const R = require('ramda');
const fetch = require('node-fetch');
const Promise = require('bluebird');
// Input
const data = {
array: [['#', 'FirstName', 'LastName'], ['1', 'tim', 'foo'], ['2', 'kim', 'bar']],
header: 'FirstName',
more: 'stuff',
goes: 'here'
};
// Static and Promise Resolver (with Helper Function)
const transposeObj = (obj, len = Object.values(obj)[0].length) =>
[...Array(len)].map((_, i) => Object.entries(obj).reduce((a, [k, v]) => ({ ...a, [k]: v[i] }), {}));
const mergeCallback = async ({ array: [headers, ...rows], header, ...rest }, callback) => {
const index = R.indexOf(header, headers);
const result = await Promise.map(rows, row => {
return callback(row[index]);
})
.then(x => ({ changes: x.map(v => transposeObj(v.changes)) }))
.then(({ changes }) => ({
allHeaders: R.flatten([
...headers,
R.chain(t => R.chain(Object.keys, t), [...changes])
.filter(k => !headers.includes(k))
.filter((x, i, a) => a.indexOf(x) == i)
]),
changes
}))
.then(({ changes, allHeaders }) => ({
resultRows: R.chain(
(row, i = R.indexOf(row, [...rows])) =>
changes[i].map(change =>
Object.entries(change).reduce(
(r, [k, v]) => [...r.slice(0, allHeaders.indexOf(k)), v, ...r.slice(allHeaders.indexOf(k) + 1)],
row.slice(0)
)
),
[...rows]
),
allHeaders
}))
.then(({ resultRows, allHeaders, array }) => ({
array: [allHeaders, ...resultRows],
header,
...rest
}));
return result;
};
// Example Callbacks and their services
const adapterPromise1 = async name => {
const response = await fetch(`https://api.abalin.net/get/getdate?name=${name}&calendar=us`).then(res => res.json());
return {
changes: {
nameday: R.pluck('day', response.results),
namemonth: R.pluck('month', response.results)
}
};
};
const servicePromise1 = input => mergeCallback(input, adapterPromise1);
const adapterPromise2 = async name => {
const response = await fetch(`https://api.genderize.io?name=${name}`).then(res => res.json());
return {
changes: {
gender: R.of(response.gender)
}
};
};
const servicePromise2 = input => mergeCallback(input, adapterPromise2);
const adapterStatic1 = name => ({ changes: { NameLength: R.of(R.length(name)) } });
const serviceStatic1 = input => mergeCallback(input, adapterStatic1);
Pipe Attempt
const result = R.pipe(
servicePromise1,
servicePromise2,
serviceStatic1
)(data);
// console.log(result); <<< preferred resolution method, but not working due to promise
result.then(console.log);
Expected Result
{ array:  
[ [ '#', 
'FirstName', 
'LastName', 
'nameday', 
'namemonth', 
'gender', 
'NameLength' ], 
[ '1', 'tim', 'foo', 24, 1, 'male', 3 ], 
[ '1', 'tim', 'foo', 20, 6, 'male', 3 ], 
[ '2', 'kim', 'bar', 8, 9, 'male', 3 ], 
[ '2', 'kim', 'bar', 11, 10, 'male', 3 ] ], 
header: 'FirstName', 
more: 'stuff', 
goes: 'here' } 
Current result
Pipe works with any one service call, but as soon as I try to use two or more services, I receive the following error.
Cannot read property 'Symbol(Symbol.iterator)' of undefined 
Any hint on how to get this to work would be greatly appreciated.
Ramda's pipe is not Promise-aware. The old Promise-aware version, pipeP is being deprecated in favor of the more generic pipeWith. You can use it with Promises by passing R.then (soon to be renamed to R.andThen) like this:
R.pipeWith (R.then, [
//servicePromise1, // problem with CORS headers here.
servicePromise2,
serviceStatic1
]) (data)
.then (console .log)
For some reason your first API call is running into CORS issues for me when I try to run it from Ramda's REPL or a SO snippet, but the process should be clear without it.
This might be enough to fix your problem. It works for this test-case. But I see an outstanding issue: All versions of pipe pass through the result of the previous call to the next one. However you use a property of the data to configure something about how the next callback will be triggered, namely your header property. So that would have to stay fixed throughout your pipeline. It's fine if all calls are going to use the FirstName property, but my impression is that they need their own versions of it.
But it would be easy enough to write a custom pipeline function that let you pass this alongside the callback function. Then your call might look like this:
seq ([
['FirstName', servicePromise2],
['FirstName', serviceStatic1]
]) (data)
.then(console.log)
You can see a working version of that idea in this snippet:
// Input
const data = {
array: [['#', 'FirstName', 'LastName'], ['1', 'tim', 'foo'], ['2', 'kim', 'bar']],
header: 'FirstName',
more: 'stuff',
goes: 'here'
};
// Static and Promise Resolver (with Helper Function)
const transposeObj = (obj, len = Object.values(obj)[0].length) =>
[...Array(len)].map((_, i) => Object.entries(obj).reduce((a, [k, v]) => ({ ...a, [k]: v[i] }), {}));
const mergeCallback = async ({ array: [headers, ...rows], header, ...rest }, callback) => {
const index = R.indexOf(header, headers);
const result = await Promise.all(rows.map(row => {
return callback(row[index]);
}))
.then(x => ({ changes: x.map(v => transposeObj(v.changes)) }))
.then(({ changes }) => ({
allHeaders: R.flatten([
...headers,
R.chain(t => R.chain(Object.keys, t), [...changes])
.filter(k => !headers.includes(k))
.filter((x, i, a) => a.indexOf(x) == i)
]),
changes
}))
.then(({ changes, allHeaders }) => ({
resultRows: R.chain(
(row, i = R.indexOf(row, [...rows])) =>
changes[i].map(change =>
Object.entries(change).reduce(
(r, [k, v]) => [...r.slice(0, allHeaders.indexOf(k)), v, ...r.slice(allHeaders.indexOf(k) + 1)],
row.slice(0)
)
),
[...rows]
),
allHeaders
}))
.then(({ resultRows, allHeaders, array }) => ({
array: [allHeaders, ...resultRows],
header,
...rest
}));
return result;
};
// Example Callbacks and their services
const adapterPromise2 = async (name) => {
const response = await fetch(`https://api.genderize.io?name=${name}`).then(res => res.json());
return {
changes: {
gender: R.of(response.gender)
}
};
};
const servicePromise2 = input => mergeCallback(input, adapterPromise2);
const adapterStatic1 = name => ({ changes: { NameLength: R.of(R.length(name)) } });
const serviceStatic1 = input => mergeCallback(input, adapterStatic1);
const seq = (configs) => (data) =>
configs.reduce(
(pr, [header, callback]) => pr.then(data => callback({...data, header})),
Promise.resolve(data)
)
seq ([
['FirstName', servicePromise2],
['FirstName', serviceStatic1]
]) (data)
.then(console.log)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
I still think there is something awkward about this, though. The header name you're looking for to me does not belong in that input data at all. You could make it another property of your mergeCallback function and update your wrappers to pass it from there, like
const servicePromise2 = (input) => mergeCallback(input, 'FirstName', adapterPromise2);
Even better to my mind, even though I understand it would add some work to your existing callback functions, would be to pass the whole row to the callback function, structured as an object with all the headers as properties. Ramda's zipObj could be used like this:
const result = await Promise.all(rows.map(row => {
return callback(zipObj(headers, row));
}))
to pass to each callback objects like this:
{"#":"1", FirstName: "tim", LastName: "foo" /*, gender: 'male', ... */}
You could change the signature of the callback to look like
const adapterPromise2 = async ({FirstName: name}) => { ...use `name` ... }
and leave the body intact, or simply change the variable name to FirstName to match the object.
const adapterPromise2 = async ({FirstName}) => { ...use `FirstName`... }
Either way, this would leave the generic code simpler, remove the header property that feels quite awkward in your current API without significantly changing your existing callbacks.

Why is my setState function overwriting current state?

I'm relatively new to React and I'm trying to set state using data that I'm receiving from a fetch response. To do this, I'm chaining a function that gathers the data needed, adds it to a state object and uses setState to add it toState and then do the same to the next response that comes in.
My problem is that each attempt is getting overwritten by the next, meaning only the last response is processed and added to sate.
What am I not doing, or doing incorrectly, that's causing this?
I've already tried using prevState however this breaks the page and produces an error: "Invalid attempt to spread non-iterable instance"
buildDataObject = (str, arr) => {
this.setState(prevState => [...prevState, {
name: str,
labels: arr.map(obj => obj.description),
id: arr[0].mid
}]);
}
Here's my state before the script runs:
constructor(){
super();
this.state = {
images: []
}
}
On componentDidMount, I run a fetch request for each image in an array:
componentDidMount() {
images.forEach(index => this.getLabels(index));
}
Here's the fetch request:
getLabels = (path) => {
const url = getGoogleVisionUrl();
fetch((url), {
method: 'POST',
body: JSON.stringify(createRequestJSON([path]))
}).then(response => response.json())
.catch((err) => { console.log('error!', err); })
.then(data => data.responses[0].labelAnnotations)
.then(arr => this.buildDataObject(path, arr));
}
Which calls on a function that's supposed to process each response and add it to state:
buildDataObject = (str, res) => {
this.setState([{
name: str,
labels: res.map(obj => obj.description),
id: res[0].mid
}]);
}
The state ends up as being a single object as:
{
name: / string response data /,
labels: /an array or strings/,
id: /an ID number/
}
if you don't specify they key that you want to update in the state it will add an object to it, and overwrite it everytime, you need to add the object to the images array in the state :
buildDataObject = (str, arr) => {
this.setState(prevState => ({
images: [
...prevState.images,
{
name: str,
labels: arr.map(obj => obj.description),
id: arr[0].mid
}
]
}));
};

Unable to resolve promise chain

I am trying to write a Sequelize migration script win which I am trying to update my database but it is having many asynchronous operations (database queries and then updating database with particular id)
Here is my code
return db.organizationEntries
.findAll()
.then((entries) => {
return entries.forEach(entry => {
console.log(entry);
db.organizationEntries
.findAll({
attributes: [
[
db.sequelize.fn(
'MAX',
db.sequelize.col('organizationEntries.serial_number')
),
'maximum_serial_no'
]
],
where: {
organizationId: entry.organizationId
}
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no);
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 };
console.log(data)
//problem
db.organizationEntries.update(data, {
where: {
id: entry.id
}
})
.then((result) => {
console.log(result);
})
});
// promises.push(promise);
});
// return Promise.all(promises);
})
Actually what I am trying to do is I am trying to take the list of all orgEntries from the database and then I am finding maximum serial number for that organization_id and then updating that particular orgEntry and like this all these operations in a loop
Now the problem is coming all the things are going in order but after finding max_serial_no it is not updating the database and I am not able to resolve what I should do to make that asynchronous call work in this order
I think you can solve this in two ways:
Simultaneously Promises
In a following code I removed forEach in favor of Promise.all() and map()
The map() method create (and return) a new array with the results of calling a provided function on every element in the calling array.
Example:
let numbers = [1, 2, 3]
let doubledNumbers = numbers.map(n => n * 2)
// doubledNumbers [2, 4, 6]
The Promise.all() method take an array of Promises as argument and returns a single Promise that will be resolved when all promises will be resolved or rejected if one promise failed
Example:
let promise1 = findUserById(5)
let promise2 = findUserFriends(5)
Promise.all([promise1, promise2])
.then(values => {
// values: [user object, list of user friends]
})
Result:
db.organizationEntries.findAll()
.then(entries => {
return Promise.all(entries.map(entry => {
console.log(entry)
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
}))
})
.then(result => {
// result: Array of updated organizationEntries
console.log(result)
})
Step by step Promises with reduce() method
The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. (from MDN web docs)
Example:
let items = [{ name: 'pencil', price: 2 }, { name: 'book', price: 10 }]
let total = items.reduce((total, item) => total += item.price, 0)
// total: 12
Result:
db.organizationEntries.findAll()
.then(entries => {
return entries.reduce((previousPromise, entry) => {
console.log(entry)
return previousPromise
.then(_ => {
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
.then(updatedEntry => {
console.log(updatedEntry)
})
}, Promise.resolve())
})
.then(result => {
// result: Last updated organization entry
console.log('finished')
})

Categories