how to optimisticResponse a connection property in relay modern mutation? - relayjs

i have a comments connection in a mutation, this is the query:
export default mutationFromQuery(graphql`
mutation AddBookMutation($input: AddBookInput! $count: Int $cursor: String ) {
addBook(input: $input) {
book {
__typename
cursor
node {
id
title
owner
createdAt
comments(first: $count, after: $cursor)
#connection(key: "BookComments_comments", filters: []) {
__typename
edges {
node {
id
}
}
}
}
}
}
}
`)
This is how i did my optimisticUpdater that don't work:
optimisticUpdater: (store) => {
const userProxy = store.get(viewerId)
const owner = userProxy.getValue('username')
const id = uuidv1();
const book = store.create(id, 'Book');
book.setValue(id, 'id');
book.setValue(bookTitle, 'title');
book.setValue(owner, 'owner');
book.setValue(Date.now(), 'createdAt');
const comments = store.create(uuidv1(), 'comments')
comments.setLinkedRecords([], 'edges')
const pageInfo = store.create(uuidv1(), 'pageInfo')
pageInfo.setValue(null, 'endCursor')
pageInfo.setValue(false, 'hasNextPage')
pageInfo.setValue(false, 'hasPreviousPage')
pageInfo.setValue(null, 'startCursor')
comments.setLinkedRecord(pageInfo, 'pageInfo')
book.setLinkedRecord(comments, 'comments')
const bookEdge = store.create(uuidv1(), 'BookEdge');
bookEdge.setLinkedRecord(book, 'node');
console.log('bookEdge ', bookEdge)
booksUpdater(userProxy, bookEdge);
},
The problem i have is that comments always ends up on undefined as you can see above i've already set it. I also did this but i am still not getting an optimistic UI:
optimisticResponse: {
addBook: {
book: {
__typename: 'BookEdge',
cursor: uuidv1(),
node: {
id: uuidv1(),
title: bookTitle,
owner: username,
createdAt: Date.now(),
comments: {
__typename: 'CommentConnection',
edges: [],
pageInfo: {
endCursor: null,
hasNextPage: false
}
}
}
}
}
},
App don't crash with optimisticResponse code but no optimistic UI effect, but with the optimisticUpdater it's crashing with comments being undefined, for now I am settling with my updater:
updater: (store) => {
const userProxy = store.get(viewerId)
const payload = store.getRootField('addBook');
booksUpdater(userProxy, payload.getLinkedRecord('book'));
},
since the comments is undefined I guess we cannot use this for optimistic effect:
const comments = store.create(uuidv1(), 'comments')
comments.setLinkedRecords([], 'edges')
book.setLinkedRecord(comments, 'comments')
on my Book, this is the query which has the comments fragment that is undefined on optimistic update with the code above:
export default createRefetchContainer(
BookItem,
{
book: graphql`
fragment BookItem_book on Book
#argumentDefinitions(
count: { type: "Int", defaultValue: 5 }
cursor: { type: "String", defaultValue: null }
) {
id
title
owner
createdAt
...BookComments_book
}
`
},
graphql`
query BookItemQuery($id: ID!, $count: Int, $cursor: String) {
book: node(id: $id) {
...BookItem_book #arguments(count: $count, cursor: $cursor)
}
}
`
);
and now the query for the comments component where it gets the book.comments.edges is undefined:
export default createPaginationContainer(
BookComments,
{
book: graphql`
fragment BookComments_book on Book
#argumentDefinitions(
count: { type: "Int", defaultValue: 3 }
cursor: { type: "String", defaultValue: null }
) {
id
title
comments(first: $count, after: $cursor)
#connection(key: "BookComments_comments", filters: []) {
__typename
edges {
node {
id
text
owner
createdAt
}
}
pageInfo {
startCursor
endCursor
hasPreviousPage
hasNextPage
}
}
}
`
},
{
direction: 'forward',
getConnectionFromProps: (props) => props.book && props.book.comments,
getFragmentVariables: (prevVars, totalCount) => ({
...prevVars,
count: totalCount
}),
getVariables: (props, { count, cursor }, _fragmentVariables) => ({
count,
cursor,
id: props.book.id
}),
query: graphql`
query BookCommentsQuery($id: ID!, $count: Int, $cursor: String) {
book: node(id: $id) {
...BookComments_book #arguments(count: $count, cursor: $cursor)
}
}
`
}
);
maybe this is an anti pattern? but i just wanted to have a optimistic effect for this

Some things are still not very clear to me about how those components and queries work, so I'll update this answer later. (I don't know if you want to return new book optimistically from node() query or add it to some list/connection of books)
Please check if I used correct type names (CommentConnection / CommentsConnection, etc)
optimisticUpdater: (store) => {
const userProxy = store.get(viewerId)
const owner = userProxy.getValue('username')
const commentsParams = { // must be same keys and values as in comments(first: $count, after: $cursor)
first: count,
after: cursor
}
// Create Book
const id = uuidv1();
const book = store.create(id, 'Book');
book.setValue(id, 'id');
book.setValue(bookTitle, 'title');
book.setValue(owner, 'owner');
book.setValue(Date.now(), 'createdAt');
// Create comments connection
const comments = store.create(uuidv1(), 'CommentConnection')
comments.setLinkedRecords([], 'edges')
// Create PageInfo
const pageInfo = store.create(uuidv1(), 'PageInfo')
pageInfo.setValue(null, 'endCursor')
pageInfo.setValue(false, 'hasNextPage')
pageInfo.setValue(false, 'hasPreviousPage')
pageInfo.setValue(null, 'startCursor')
// Link created records
comments.setLinkedRecord(pageInfo, 'pageInfo')
book.setLinkedRecord(comments, 'comments', commentsParams) // don't forget commentsParams with same values as are used in comments graphql query
// I'm not sure about this final part, because I don't really get how that app works, but if you want this book to show as optimistic response for `node(id: $id)`, you'll do something like this:
store.getRoot().setLinkedRecord(book, 'node', { id: id }) // same id as used in BookItemQuery
}

Related

Relay Modern updater ConnectionHandler.getConnection() returns undefined when parent record is root

Debugging update:
So, we went a bit further in debugging this and it seems like 'client:root' cannot access the connection at all by itself.
To debug the complete store, we added this line in the updater function after exporting the store variable from the relay/environment.
console.log(relayEnvStore.getSource().toJSON())
If I use .get() with the specific string client:root:__ItemList_items_connection, I can access the records I have been looking for but it's definitely not pretty.
const testStore = store.get('client:root:__ItemList_items_connection')
console.log(testStore.getLinkedRecords('edges'))
Original:
I'm using Relay Modern and trying to update the cache after the updateItem mutation is completed with the updater. The call to ConnectionHandler.getConnection('client:root', 'ItemList_items') returns undefined.
I'm not sure if it's because I'm trying to use 'client:root' as my parent record or if there's a problem with my code. Has anyone found themselves with a similar issue?
Here's the paginationContainer:
const ItemListPaginationContainer = createPaginationContainer(
ItemList,
{
node: graphql`
fragment ItemList_node on Query
#argumentDefinitions(count: { type: "Int", defaultValue: 3 }, cursor: { type: "String" }) {
items(first: $count, after: $cursor) #connection(key: "ItemList_items") {
edges {
cursor
node {
id
name
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`
},
{
direction: 'forward',
getConnectionFromProps: props => props.node && props.node.items,
getVariables(props, { count, cursor }) {
return {
count,
cursor
}
},
query: graphql`
query ItemListQuery($count: Int!, $cursor: String) {
...ItemList_node #arguments(count: $count, cursor: $cursor)
}
`
}
)
Here's the mutation:
const mutation = graphql`
mutation UpdateItemMutation($id: ID!, $name: String) {
updateItem(id: $id, name: $name) {
id
name
}
}
`
Here's the updater:
updater: (store) => {
const root = store.getRoot()
const conn = ConnectionHandler.getConnection(
root, // parent record
'ItemList_items' // connection key
)
console.log(conn)
},
Turns out that I was setting my environment incorrectly. The store would reset itself every time I would make a query or a mutation, hence why I couldn't access any of the connections. I initially had the following:
export default server => {
return new Environment({
network: network(server),
store: new Store(new RecordSource())
})
}
All connections are accessible with this change:
const storeObject = new Store(new RecordSource())
export default server => {
return new Environment({
network: network(server),
store: storeObject
})
}

unable to get correct data using `useLazyLoadQuery` when compose fragment into query

I do see a sucessfull API request
const data = useLazyLoadQuery<brandQuery>(
graphql`query
brandQuery {
...brand_autoBrands
}
`,
{
first: 10,
},
{
fetchPolicy: "network-only",
}
);
console.log(data);
But I got following output. it is supposed to be a json object returned from my API.
brand_autoBrands fragment somewhere
const autoBrands = graphql`
fragment brand_autoBrands on AdminQuery
#argumentDefinitions(
first: { type: "Int", defaultValue: 10 }
after: { type: "String", defaultValue: "" }
last: { type: "Int" }
before: { type: "String" }
filters: { type: "[Filter]" }
sorters: { type: "[Sorter]"}
)
#refetchable(queryName: "BrandListPaginationQuery") {
autoBrands(first: $first, after: $after, last: $last, before: $before, filters: $filters, sorters: $sorters)
#connection(key: "BrandList_autoBrands") {
edges {
node {
...brandFragment
}
}
pageInfo {
startCursor
}
totalCount
}
}
`;
const {
data,
loadNext,
hasNext
} = usePaginationFragment<BrandListPaginationQuery, _>(
autoBrands,
props.autoBrands,
);
You can't log a fragment data object in the parent. That data belongs to the component that defined a fragment. For performance, relay needs to know that this data is fetched for a child component but the parent doesn't need to know what the actual data object is. You just need to pass the data to its component then you should be able to log it in the child component.
const data = useLazyLoadQuery<brandQuery>(
graphql`query
brandQuery {
...brand_autoBrands
}
`,
{
first: 10,
},
{
fetchPolicy: "network-only",
}
);
data && <Brand autoBrands={data} />

Angular 7 Cannot read property 'map' of undefined

I'm having this problem to use mat-autocomplete async, I have tried several solutions mainly from here and even then I did not succeed. follow my code ... Thanks
component.ts
filteredEmpresas: Observable<IEmpresaResponse>;
empresasForm: FormGroup;
this.empresasForm = this.fb.group({
empresaInput: null
})
this.filteredEmpresas = this.empresasForm.get('empresaInput').valueChanges
.pipe(
debounceTime(300),
switchMap(value => this.appService.search({text: value}, 1))
);
service.ts
search(filter: {text: string} = {text: ''}, page = 1):
Observable<IEmpresaResponse> {
return this.http.get<IEmpresaResponse>(this.apiURL + '/busca/'+filter.text)
.pipe(
tap((response: IEmpresaResponse) => {
response.results = response.results
.map(empresa => new Empresa(empresa.idEmpresa, empresa.nomeEmpresa, empresa.ativo));
return response;
})
);
}
class.ts
export class Empresa {
constructor(public idEmpresa: number, public nomeEmpresa: string, public ativo: Boolean) {}
}
export interface IEmpresaResponse {
total: number;
results: Empresa[];
}

Relay uses initial variable during setVariables transition, not "last" variable

I have a page where a bunch of file ids get loaded from localStorage, then when the component mounts / receives new props, it calls setVariables. While this works and the new variables are set, the results from the initial variables is used during the transition, which causes an odd flickering result.
Why would Relay give me something different during the transition at all? My expectation would be that this.props.viewer.files.hits would be the same as the previous call while setVariables is doing its thing, not the result from using the initial variables.
const enhance = compose(
lifecycle({
componentDidMount() {
const { files, relay } = this.props
if (files.length) {
relay.setVariables(getCartFilterVariables(files))
}
},
}),
shouldUpdate((props, nextProps) => {
if (props.files.length !== nextProps.files.length && nextProps.files.length) {
props.relay.setVariables(getCartFilterVariables(nextProps.files))
}
return true
})
)
export { CartPage }
export default Relay.createContainer(
connect(state => state.cart)(enhance(CartPage)), {
initialVariables: {
first: 20,
offset: 0,
filters: {},
getFiles: false,
sort: '',
},
fragments: {
viewer: () => Relay.QL`
fragment on Root {
summary {
aggregations(filters: $filters) {
project__project_id {
buckets {
case_count
doc_count
file_size
key
}
}
fs { value }
}
}
files {
hits(first: $first, offset: $offset, filters: $filters, sort: $sort) {
${FileTable.getFragment('hits')}
}
}
}
`,
},
}
)
Ah I finally figured this out. prepareParams was changing the value
export const prepareViewerParams = (params, { location: { query } }) => ({
offset: parseIntParam(query.offset, 0),
first: parseIntParam(query.first, 20),
filters: parseJsonParam(query.filters, null), <-- setting filters variable
sort: query.sort || '',
})
const CartRoute = h(Route, {
path: '/cart',
component: CartPage,
prepareParams: prepareViewerParams, <--updating variable
queries: viewerQuery,
})

Data not getting displayed in jqgrid

I am using jqgrid to display,sort and filter records. At the moment, I have go the js code working, but I think there is something wrong with my controller code, that is not populating the grid. I can see only an empty blak jqgrid with the message "No records to display". Please let me know whats going wrong.
Here is my code:
Controller:
public JsonResult GetData(string sidx, string sord, int page, int rows)
{
int pageIndex = Convert.ToInt32(page) - 1;
int pagesize = rows;
var custList = db.Customers.Select(
c => new
{
c.ID,
c.Company,
c.FirstName,
c.EMail,
c.Status
});
int totalCustomers = custList.Count();
var totalPages = (int)Math.Ceiling((float)totalCustomers / (float)rows);
if(sord.ToUpper() == "DESC")
{
custList = custList.OrderByDescending(s => s.FirstName);
custList = custList.Skip(pageIndex * pagesize).Take(pagesize);
}
else
{
custList = custList.OrderBy(s => s.FirstName);
custList = custList.Skip(pageIndex * pagesize).Take(pagesize);
}
var jsonData = new
{
total = totalPages,
page,
customers = totalCustomers,
rows = custList
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
Solved my problem. Dint know I would get the solution so quickly. I removed all the existing code from the controller and just added this:
public JsonResult GetData()
{
var customers = db.Customers.Select(m => new { ID = m.ID, Company = m.Company, FirstName = m.FirstName, Email = m.EMail, Status = m.Status }).ToList();
return Json(customers, JsonRequestBehavior.AllowGet);
}
This is all it needs,just two lines of code, rest of the functionalities, ie paging, sorting, filtering is already provided by jqwidgets:) Hope this helps someone who was facing a similar problem as mine.
The script is as follows:
var source =
{
//localdata: GetData(),
url: '/Client/GetData',
datatype: "json",
mtype: 'POST',
datafields: [
{ name: 'ID', type: 'int' },
{ name: 'Company' },
{ name: 'FirstName' },
{ name: 'EMail' },
{ name: 'Status' }
]
};
var dataAdapter = new $.jqx.dataAdapter(source);
// initialize jqxGrid
$("#jqxgrid").jqxGrid(
{
source: dataAdapter,
sortable: true,
filterable: true,
pageable: true,
columns: [
{ text: 'Client Id', datafield: 'ID', width: 200 },
{ text: 'Company', datafield: 'Company', width: 200 },
{ text: 'Username', datafield: 'FirstName', width: 180 },
{ text: 'Email', datafield: 'EMail', width: 100 },
{ text: 'Status', datafield: 'Status', width: 140 }
]
});
});

Resources