I've also created an issue in their github for further help
I am creating an app with a lot of relationships and with mui-datatables want to create links to different profiles within the cells. Sometimes a single cell may need to contain more than one link otherwise I would use the onCellClick function.
An example of this use case might be Pet owners and their pets (this isn't actually my use-case, just an example).
Say I create a table of Owners and then in one cell is a list of the pet names
Owners
Pet Names
Paige
Sally, Sage, Ember
I've gotten it to where I can push all the names of the animals related to a specific owner to a new array and then join that array to create a string that shows up properly with all the pets listed. When I click on the row I get taken to the owner profile with the onRowClick function which is exactly how I want it
Code Example
const [data, setData] = useState([]);
const [petColumns, setPetColumns] = useState([]);
const [columnData, setColumnData] = useState([]);
const [options, setOptions] = useState({
filter: true,
filterType: 'dropdown',
responsive: 'standard',
rowsPerPage: 10,
resizableColumns: true,
enableNestedDataAccess: '.',
selectableRows: 'none',
onRowClick: (rowData) => {
handleClick(rowData);
}
})
setPetColumns = [
{
name: "id",
label: "Id",
options: {
display: 'excluded',
}
},
{
name: "name",
label: "Owner Name",
},
{
name: "pet",
label: "Pets",
},
]
useEffect(() => {
API.getOwnerData(ownerId)
.then(res => setData(res.data))
.catch(err => console.log(err))
}, [ownerId])
useEffect(() => {
const ownerData = data.map((owner) => {
const petList = []
owner.Pets.forEach((pet) => {
petList.push({
name: pet.name,
})
})
const petObject = {
id: owner.id,
name: `${owner.User.firstName} ${owner.User.lastName}`,
pet: petList.map(p => p.name).join(', '),
}
return petObject
})
setColumnData(ownerData)
}, [data])
const handleClick = (rowData) => {
navigate(`${rowData[0]}`)
}
<MUIDataTable
className={styles.innerTable}
data={columnData}
columns={petColumns}
options={options}
>
</MUIDataTable>
Where I'm running into trouble is that I want to turn each of the pet names that are being listed into a link that will navigate to the pets respective profile when clicked on. I will show some of my attempts to do this and explain what the results were.
ATTEMPT 1
Here I added pet.id to the petList object. This id is used with react-router-dom and Link to travel to the proper profile page for the pet. I then set up a customBodyRender (I tried both customBodyRender and customBodyRenderLite) which is supposed to map through the pet list and create a link with the pet name for each entry and then join together the new link divs.
I'm actually fairly stumped on this because I keep getting "Uncaught TypeError: value.map is not a function". Which makes me think that the value of property is not being recognized as an array. However, I found several StackOverFlow posts where value is treated properly as an array and can be joined together such as here.
I also recognize though that the array in that stack overflow is flat which mine is not but I even added the "enableNestedDataAccess: '.'," to that specific set of options (even though it's already in the general options for the table) just to make sure. I tried some variations of metadata as well but couldn't get that to stick either, I honestly think I'm probably incorporating it wrong.
CODE EXAMPLE - I didn't include the code that remained unchanged
setPetColumns = [
{
name: "id",
label: "Id",
options: {
display: 'excluded',
}
},
{
name: "name",
label: "Owner Name",
},
{
name: "pet",
label: "Pets",
options: {
customBodyRenderLite: (value) => { ****ATTEMPTED customBodyRender TO RENDER THE LINK AND THEN JOIN ****
return (
<>
{value.map((e) => {
<Link to={`/pets/id=${e.id}`} >{e.name}</Link>
}).join(', ')}
</>
)
}
}
},
]
useEffect(() => {
const ownerData = data.map((owner) => {
const petList = []
owner.Pets.forEach((pet) => {
petList.push({
name: pet.name,
id: pet.id ****ID IS ADDED FOR NAVIGATION PURPOSES****
})
})
const petObject = {
id: owner.id,
name: `${owner.User.firstName} ${owner.User.lastName}`,
pet: petList
}
return petObject
})
setColumnData(ownerData)
}, [data])
When I console log columnData I get exactly what I'm expecting
0:
id: "30c9"
name: "Paige"
property: Array(3)
0:
id: "df43"
name: "Sally"
1:
id: "dea4"
name: "Sage"
2:
id: "ger7"
name: "Ember"
ATTEMPT 2
My attempt here was kind of a hail mary, I'd been working trying to get the custom render to work for a long while so I thought I'd go at it from a different direction. I trying to include Link within the petObject that initially worked in providing the plain joined names.
My result of this attempt was that my data table rendered but I got [ object Object ] within the pet Columns if I included return in the .map function or an empty string if there was no return. I honestly didn't really expect this to work but was at a loss of what else to try. When I consoled the columnData it was either [ object Object ] or ""
setPetColumns = [
{
name: "id",
label: "Id",
options: {
display: 'excluded',
}
},
{
name: "name",
label: "Owner Name",
},
{
name: "pet",
label: "Pets",
},
]
useEffect(() => {
const ownerData = data.map((owner) => {
const petList = []
owner.Pets.forEach((pet) => {
petList.push({
name: pet.name,
id: pet.id ****ID IS ADDED FOR NAVIGATION PURPOSES****
})
})
const petObject = {
id: owner.id,
name: `${owner.User.firstName} ${owner.User.lastName}`,
*****TRIED TO INCLUDE LINK WITHIN OBJECT****
pet: petList.map((p) => {
return <Link to=`/pet/id=${p.id} >p.name </Link> ****If return isn't included a blank string is returned****
}).join(', '),
}
return petObject
})
setColumnData(ownerData)
}, [data])
Tech
Version
Material-UI
4.12.3
MUI-datatables
4.1.2
React
17.0.2
browser
Google Chrome
Related
I'm feeding a Next.js blog with Contentful and querying for some posts at the frontpage. And even tho I can .filter() featured posts by using a simple boolean value I can't seem to understand how could I fetch posts for matching categories within my post structure.
Here's a sample of what my query for featured posts looks like.
{posts.filter((posts) => (posts.fields.postDestaque)) // it filters posts that have 'postDestaque' false or null
.slice(0, 6) // cuts off the excess
.map((post, i) => { // maps the posts and returns the component
return <PostVertical key={i} post={post} noCats={noCats} noData={noData} />
})}
The categories are held within an array with objects of fields that contain names and slugs for each one like this:
0:
fields:
categorias: Array(2)
0:
fields:
nome: "Main Category"
slug: "main-category"
1:
fields:
nome: "Other Category"
slug: "other-category"
I'm a front-end guy so I don't know how to filter the posts for a category. I thought .find() could work, but it would only return the object that matches with it, so, the category itself. Plus I'm not understanding how I could reach for a second array and test the post object itself. Is there another method I could use instead of filter or find?
Thanks a lot for any comments on this.
If its an array you're trying to filter, you can get away with just using .filter and .some I added small snippet below, but you just want to make sure that you return a boolean once you find the category want to filter in, .some does that.
posts.filter(post => post.fields.categorias.some(categoria => categoria.slug === 'main-category'));
const posts = [{
fields: {
categorias: [{
noma: 'Main Category',
slug: 'main-category',
},
{
noma: 'Other Category',
slug: 'other-category',
},
],
},
},
{
fields: {
categorias: [{
noma: 'Main Category1',
slug: 'main-category1',
},
{
noma: 'Other Category1',
slug: 'other-category1',
},
],
},
},
{
fields: {
categorias: [{
noma: 'Main Category2',
slug: 'main-category2',
},
{
noma: 'Other Category2',
slug: 'other-category2',
},
],
},
},
];
const byCategory = category => post => post.fields.categorias.some(categoria => categoria.slug === category);
const byMainCategory = byCategory('main-category');
const filteredByCategorias = posts.filter(byMainCategory)
console.log("filtered by categorias", filteredByCategorias)
I am doing some filtering using React Context and I am having some difficulty in updating a child's array value when a filter is selected.
I want to be able to filter by a minimum price, which is selected in a dropdown by the user, I then dispatch an action to store that in the reducers state, however, when I try and update an inner array (homes: []) that lives inside the developments array (which is populated with data on load), I seem to wipe out the existing data which was outside the inner array?
In a nutshell, I need to be able to maintain the existing developments array, and filter out by price within the homes array, I have provided a copy of my example code before, please let me know if I have explained this well enough!
export const initialState = {
priceRange: {
min: null
},
developments: []
};
// Once populated on load, the developments array (in the initialState object)
// will have a structure like this,
// I want to be able to filter the developments by price which is found below
developments: [
name: 'Foo',
location: 'Bar',
distance: 'xxx miles',
homes: [
{
name: 'Foo',
price: 100000
},
{
name: 'Bar',
price: 200000
}
]
]
case 'MIN_PRICE':
return {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: [
...state.developments.map(development => {
// Something here is causing it to break I believe?
development.homes.filter(house => house.price < action.payload);
})
]
};
<Select onChange={event=>
dropdownContext.dispatch({ type: 'MIN_PRICE' payload: event.value }) } />
You have to separate homes from the other properties, then you can apply the filter and rebuild a development object:
return = {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: state.developments.map(({homes, ...other}) => {
return {
...other,
homes: homes.filter(house => house.price < action.payload)
}
})
}
i have a vue app with ad group of data.
const data = [
{
name: 'value1',
id: 'id1',
supervisor: true,
},
{
name: 'value2'
id: 'id2',
supervisor: false,
}
]
but i just need to display data for supervisor = true only.
i'd tried to filter it with belows code but it did not work.
const test = data.filter(data => data.supervisor = true);
console.log(test);
can somenone share any reference for me to refer for this filtering function as i new in vue js. or anyone can share their knowledge. because actually i have the data from api which need to be display with some sort of condition.
i have tried using url/api/items?supervisor=true and then i was inform that the API did no support for that kind of way so i need to create a filter function in javascript from data that retrieve from APIs. i'm using above const data =[{...,...}] in this question for it easier to understand.
const test = data.filter(data => data.supervisor === true);
Full working example, since your data was also missing a comma, so the not working part can be from there:
const data = [
{
name: 'value1',
id: 'id1',
supervisor: true,
},
{
name: 'value2',
id: 'id2',
supervisor: false,
}
];
const test = data.filter(data => data.supervisor === true);
console.log(test);
You should use const test = data.filter(data => data.supervisor); instead of > true
I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256
I need to be able to create a user and add it's favourite movies (An array of objects with a reference to the Movies collection and his personal rating for each movie) in a single request.
Something that could look like this (pseudocode)
var exSchema = `
type Mutation {
addUser(
name: String!
favMovies: [{ movie: String! #ref to movies coll
personal_rating: Int! # this is different for every movie
}]
) : User
}
...
`
What is the graphql way of doing this in a single request? I know I can achieve the result with multiple mutations/requests but I would like to do it in a single one.
You can pass an array like this
var MovieSchema = `
type Movie {
name: String
}
input MovieInput {
name: String
}
mutation {
addMovies(movies: [MovieInput]): [Movie]
}
`
Then in your mutation, you can pass an array like
mutation {
addMovies(movies: [{name: 'name1'}, {name: 'name2'}]) {
name
}
}
Haven't tested the code but you get the idea
I came up with this simple solution - NO JSON used. Only one input is used. Hope it will help someone else.
I had to add to this type:
type Option {
id: ID!
status: String!
products: [Product!]!
}
We can add to mutation type and add input as follows:
type Mutation {
createOption(data: [createProductInput!]!): Option!
// other mutation definitions
}
input createProductInput {
id: ID!
name: String!
price: Float!
producer: ID!
status: String
}
Then following resolver could be used:
const resolvers = {
Mutation: {
createOption(parent, args, ctx, info) {
const status = args.data[0].status;
// Below code removes 'status' from all array items not to pollute DB.
// if you query for 'status' after adding option 'null' will be shown.
// But 'status': null should not be added to DB. See result of log below.
args.data.forEach((item) => {
delete item.status
});
console.log('args.data - ', args.data);
const option = {
id: uuidv4(),
status: status, // or if using babel status,
products: args.data
}
options.push(option)
return option
},
// other mutation resolvers
}
Now you can use this to add an option (STATUS is taken from first item in the array - it is nullable):
mutation{
createOption(data:
[{
id: "prodB",
name: "componentB",
price: 20,
producer: "e4",
status: "CANCELLED"
},
{
id: "prodD",
name: "componentD",
price: 15,
producer: "e5"
}
]
) {
id
status
products{
name
price
}
}
}
Produces:
{
"data": {
"createOption": {
"id": "d12ef60f-21a8-41f3-825d-5762630acdb4",
"status": "CANCELLED",
"products": [
{
"name": "componentB",
"price": 20,
},
{
"name": "componentD",
"price": 15,
}
]
}
}
}
No need to say that to get above result you need to add:
type Query {
products(query: String): [Product!]!
// others
}
type Product {
id: ID!
name: String!
price: Float!
producer: Company!
status: String
}
I know it is not the best way, but I did not find a way of doing it in documentation.
I ended up manually parsing the correct schema, since JavaScript Arrays and JSON.stringify strings were not accepted as graphQL schema format.
const id = 5;
const title = 'Title test';
let formattedAttachments = '';
attachments.map(attachment => {
formattedAttachments += `{ id: ${attachment.id}, short_id: "${attachment.shortid}" }`;
// { id: 1, short_id: "abcxyz" }{ id: 2, short_id: "bcdqrs" }
});
// Query
const query = `
mutation {
addChallengeReply(
challengeId: ${id},
title: "${title}",
attachments: [${formattedAttachments}]
) {
id
title
description
}
}
`;
What i understand by your requirement is that if you have the following code
const user = {
name:"Rohit",
age:27,
marks: [10,15],
subjects:[
{name:"maths"},
{name:"science"}
]
};
const query = `mutation {
createUser(user:${user}) {
name
}
}`
you must be getting something like
"mutation {
createUser(user:[object Object]) {
name
}
}"
instead of the expected
"mutation {
createUser(user:{
name: "Rohit" ,
age: 27 ,
marks: [10 ,15 ] ,
subjects: [
{name: "maths" } ,
{name: "science" }
]
}) {
name
}
}"
If this is what you wanted to achieve, then gqlast is a nice tag function which you can use to get the expected result
Simply grab the js file from here and use it as:
const user = {
name:"Rohit",
age:27,
marks: [10,15],
subjects:[
{name:"maths"},
{name:"science"}
]
};
const query = gqlast`mutation {
createUser(user:${user}) {
name
}
}`
The result stored in the variable query will be :
"mutation {
createUser(user:{
name: "Rohit" ,
age: 27 ,
marks: [10 ,15 ] ,
subjects: [
{name: "maths" } ,
{name: "science" }
]
}) {
name
}
}"
Pass them as JSON strings. That's what I do.
For those of you who don't need to pass in an array for one request, and are open to the idea of making a request for every mutation. (I am using Vue3, compisition Api, but React and Angular developers still can understand this).
You cannot for loop the mutation like this:
function createProject() {
for (let i = 0; i < state.arrOfItems.length; i++) {
const { mutate: addImplementation } = useMutation(
post_dataToServer,
() => ({
variables: {
implementation_type_id: state.arrOfItems[i],
sow_id: state.newSowId,
},
})
);
addImplementation();
}
}
this will give you an error, because the mutation must be in the setup().
(here is the error you will recieve: https://github.com/vuejs/vue-apollo/issues/888)
Instead create a child component, and map the array in the parent.
in Parent.vue
<div v-for="(card, id) in state.arrOfItems">
<ChildComponent
:id="id"
:card="card"
/>
</div>
in ChildComponent.vue
recieve props and:
const { mutate: addImplementation } = useMutation(
post_dataToServer,
() => ({
variables: {
implementation_id: props.arrOfItems,
id: props.id,
},
})
);