I want to normalise the responses I receive from an API. A typical response could look something like this:
// Get all projects
{data:[
{
id: 1
...
team:{
data: {
id:15
...
}
}
},
{
id:2,
....
},
{
id:3,
...
}
]}
How do I write my schemas so that it removes the 'data' container?
Currently, my schema looks like:
export const project = new schema.Entity('projects', {
team: team, // team omitted
},
{
processStrategy: (value, parent, key) => parent.data
}
)
export const arrayOfProjects = new schema.Array(project)
And I am using it like:
const normalizedProjects = normalize(jsonResponse, arrayOfProjects)
normalizedProjects then looks like this:
{
entities:{
projects:{
undefined:{
0:{
team:{
data:{
id:15,
...
}
}
},
1:{...},
2:{...}.
...
50:{...},
}
}
},
result:[] // length is 0
}
I'm not sure why the list of projects is contained in 'undefined', either?
I also use json_api schema.
How about like this?
const projectsSchema = new schema.Entity('projects', {}, {
processStrategy: processStrategy
});
export const processStrategy = (value, parent, key) => {
const attr = value.attributes;
delete value.attributes;
return { ...value, ...attr };
};
export const fetchProjectsSchema = {
data: [projectsSchema]
}
Each of your entity schema that you want to have the data omitted (or anything else fundamentalyl changed) needs to include a processStrategy that you write to remove or change any data. (see more examples in the tests)
Related
axios.defaults.transformRequest = [
(data, headers) => {
console.log('default config')
// ...some code
return qs.stringify(data)
}
]
function postData(value) {
axios.post('/xxx', value, {
transformRequest: [data => {
console.log('postData config')
const { aProp, ...rest } = data
if (isX(aProp)) {
return { ...rest, x: aProp }
}
return { ...rest, y: aProp }
}]
})
}
Expected output:
postData config
default config
But only output postData config.
The transformRequest seems like merged by index, not using concat. So how can I make both functions called?
Seems that's not possible like the way you written, as Axios is just overriding the transformRequest property of global config by request specific config.
Please take a look at the code that is in mergeConfig.js
in below code snippets
config2 is request specific config
config1 is global config
var valueFromConfig2Keys = ['url', 'method', 'data'];
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params'];
var defaultToConfig2Keys = [
'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer',
'timeout', 'timeoutMessage', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'decompress',
'maxContentLength', 'maxBodyLength', 'maxRedirects', 'transport', 'httpAgent',
'httpsAgent', 'cancelToken', 'socketPath', 'responseEncoding'
];
var directMergeKeys = ['validateStatus'];
Only mergeDeepPropertiesKeys are deep merged like here
function mergeDeepProperties(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(config1[prop], config2[prop]);
} else if (!utils.isUndefined(config1[prop])) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
}
utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties);
defaultToConfig2Keys are just overriden by request specific config like below and transformRequest is one of those properties.
utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(undefined, config2[prop]);
} else if (!utils.isUndefined(config1[prop])) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
});
PS: I would suggest you to write your own wrapper around axios or you can prepare your data ahead before making the request.
In my react app i'm coding a small photo gallery, with a GraphQL query i get all the images in a folder in this format:
{
"data": {
"allFile": {
"edges": [
{
"node": {
"childImageSharp": {
"fluid": {
"aspectRatio": 0.7518796992481203,
"originalName": "music_01.jpg"
}
}
}
},
{
"node": {
"childImageSharp": {
"fluid": {
"aspectRatio": 1.3333333333333333,
"originalName": "music_02.jpg"
}
}
}
},
{
"node": {
"childImageSharp": {
"fluid": {
"aspectRatio": 0.7518796992481203,
"originalName": "food_01.jpg"
}
}
}
}
]
}
},
"extensions": {}
}
it return about 50 entries, that (using links on the page) i need to filter out, something like a cateogry, where the category name is based on regex /category/ (or with indexOf) (music, foood and so) in the filename.
i was thinking to use a state to keep the original data separated from the filtered one, but it looks im not able to filter out the data to keep only the needed one.
my approach was something like
const Portofolio = ({data}) =>{
const [filtered, setFiltered]=useState();
function onFilterData(filter) {
//scroll through data and keep only the entries that match filter
//assign the kept data to filtered with setFiltered(keptdata)
}
return (
//render the gallery from filtered object
)
}
but im stuck on the filtered part and i cant get out of it!
any suggestion?
You can use useMemo to save the filtered list and avoid to use useEffect and setState and with this approach you save one render every time that your data changed.
You need something like this:
const filteredList = React.memo(() => {
return data.filter(({ childImageSharp: { fluid } }) => {
return fluid. originalName.indexOf(filter) > -1;
})
}, [data, filter])
data: is all your items list
filter: is the route that need to match with the item name
You use JS filter, check if your originalName exists and if it matches with your filter that comes from the fn argument
function onFilterData(filter = 'music') {
const edges = data?.allFile?.edges;
if (edges) {
const target = edges.filter(edge => {
const fileName = edge?.node?.childImageSharp?.fluid?.originalName;
if (fileName && fileName.includes(filter)) {
return true;
}
return false;
});
setFiltered(target);
}
}
I have an array of objects like this:
const data = [{
_id:"49847444033",
name:"yoko"
},{
_id:"49847433333",
name:"doira"
}]
I have to change each item name property to something like this :
...
{
_id:"49847433333",
name:{
en:"John"
}
}
My attempt is to loop object like following :
data.forEach((item) => {
item.name = {en:"john"}
console.log(item)
})
But this always console the original item and the name property value is not modified.
const newData = data.map(user => ({ _id: user._id, name: { en: user.name } }))
I created a library to express transformations like this very simply.
const { pipe, fork, get } = require('rubico')
const data =
[ { _id: '49847444033', name: 'yoko'}
, { _id: '49847433333', name: 'doira'}
]
const onData = pipe([
fork({
_id: get('_id'), // data => data._id
name: fork({ en: get('name') }), // data => ({ en: data.name })
}),
console.log,
])
data.map(onData) /*
{ _id: '49847444033', name: { en: 'yoko' } }
{ _id: '49847433333', name: { en: 'doira' } }
*/
I've commented the code above, but to really understand rubico and get started using it, I recommend you read the intuition and then the docs
try somthing like:
const newList = data.map(obj => {
return { _id: obj._id, name: { en: obj.name } }
});
and the newList list is your new data list so you can do it:
data = newList;
EDIT:
if you have more properties you can change the return line to:
return { ...obj, name: { en: obj.name } }
what will happen here, it will deploy all the object properties as they are, and modify the name property, unfortunately, every property you want to modify, you have to re-write it.
I'm pretty new to JavaScript and am wondering if I do it the correct way.
Consider this object:
const pageLinks = {
tickets: [
{ to: "/tickets/mytickets", },
{ to: "/tickets/newticket", },
{ to: "/tickets/followup", }
],
home: [
{ to: "/home/dashboard", }
],
about: [
{ to: "/about/author", }
]
}
When a user requests the route /tickets/followup I would like it to return tickets. This code, my first one ever, does exactly that:
const to = { path: '/tickets/followup' }
for (let page in pageLinks) {
let links = pageLinks[page]
links.forEach(element => {
if (element.to === to.path) {
console.log(page)
}
});
}
My question: is this the correct way of doing it? Or would it be better to use a filter() method?
Amusing each path has one destination page it would be faster to use the paths as keys and pages as values. This way lookup based upon path is simple and fast.
const pageLinks = {
tickets: [
{ to: "/tickets/mytickets", },
{ to: "/tickets/newticket", },
{ to: "/tickets/followup", }
],
home: [
{ to: "/home/dashboard", }
],
about: [
{ to: "/about/author", }
]
};
const routes = {};
for (const page in pageLinks) {
const links = pageLinks[page];
links.forEach(link => routes[link.to] = page);
}
console.log(routes);
const to = { path: '/tickets/followup' };
console.log(routes[to.path]);
In my opinion, the "correct way", or the "best way" of doing something is really relative. If your code solves the problem (and it does), it's fine. There are infinite ways of solving the same problem. Other two ways:
Using Object.keys and array.map:
const to = { path: '/tickets/followup' }
Object.keys(pageLinks).map(page => pageLinks[page].map(link => {
if (link.to == to.path) console.log(page)
}));
Using array.find:
const to = { path: '/tickets/followup' }
let found = Object.keys(pageLinks).find(page => pageLinks[page].find(link => link.to == to.path));
console.log(found);
I'm having the following JSON from my service:
[
{
"name":"Voter1",
"id":1,
"votingCard":{
"verificationCodes":[
"3I08jA",
"3x0eyE",
"2_i69I"
],
"votingCode":"7zCOelDnjfBm7TtFydc4QodgonG",
"finalizationCode":"jyYu",
"confirmationCode":"4ACfcpBVH45iAXqg7hJ0tbEe_tV"
}
},
.....
{
"id":5,
"name":"Voter5",
"votingCard":{
"verificationCodes":[
"2c9I9a",
"3bEeEa",
"1gPKx2"
],
"confirmationCode":"4Z7wNG35VR2UMO6-W-0aZVEhbLM",
"votingCode":"6YQ2x-c8LXJZF05gh3zTajU79ct",
"finalizationCode":"S0CY"
}
}
]
And would like to get it normalized, so a list of votingCards and a list of voters with a "votingCard" property referencing the votingCard by id.
import { normalize, schema } from 'normalizr';
const votingCard = new schema.Entity('votingCard');
const voter = new schema.Entity('voter', {
votingCard: votingCard,
});
const votersSchema = new schema.Array(voter);
const mutations = {
SOCKET_SYNCVOTERS: (state, data) => {
var input = JSON.parse(data);
const normalizedData = normalize(input, votersSchema);
console.log(normalizedData);
},
};
However, I'm not getting what I want:
Why is there an "undefined"?
I think you need to specify an ‘idAttribute’ in the options for the votingCard entity - the problem is that normalizr can’t find an ‘id’ field in those objects so they are all being picked up as id undefined and overwriting each other in entities. See: https://github.com/paularmstrong/normalizr/blob/master/docs/api.md#schema