I am using react-apollo in my React application. Now it comes to pagination to fetch more data. Independent from the pagination, how do you update the state in updateQuery or update? Treating the deeply nested data structure as immutable makes it verbose, but I wouldn't want to add a helper library to it.
fetchMore({
variables: {
cursor: cursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
return {
...previousResult,
author: {
...previousResult.author,
articles: {
...previousResult.author.articles,
...fetchMoreResult.author.articles,
edges: [
...previousResult.author.articles.edges,
...fetchMoreResult.author.articles.edges,
],
}
}
}
},
Is it okay to mutate the previousResult instead or does it go against the philosophy of Apollo?
const { pageInfo, edges } = fetchMoreResult.author.articles;
previousResult.author.articles.edges.concat(edges);
previousResult.author.articles.pageInfo = pageInfo;
return previousResult;
Or is there another way to update the state?
From the docs:
Note that the function must not alter the prev object (because prev is compared with the new object returned to see what changes the function made and hence what prop updates are needed).
I would just bite the bullet and use immutability-helper like the docs recommend. Barring that, you could make a copy of the object first (Object.assign({}, fetchMoreResult)) and then you can do what you want to the copy.
Related
I have a polymorphic (as in arbitrary roles) QObject model that is mostly instantiated declaratively from QML, as in this answer, and I would like to be able to have custom data "views" that sort and filter the model via arbitrary, and potentially - runtime generated from code strings JS functors, something like that:
DataView {
sourceModel: model
filter: function(o) { return o.size > 3 }
sort: function(a, b) { return a.size > b.size }
}
The QSortFilterProxyModel interface doesn't seem to be particularly well suited to the task, instead being fixated on static roles and pre-compiled rules.
I tried using QJSValue properties on the C++ side, but it seems like it is not possible, the C++ code just doesn't compile with that property type. And if I set the property type to QVariant I get error messages from QML that functions can only be bound to var properties. Evidently, var to QVariant conversion doesn't kick in here as it does for return values.
As you mentionned, you could use QJSValue. But that's pretty static. What if you want to use a filter like filter: function(o) { return o.size > slider.value; } with a dynamic slider ? You'll have to manually call invalidateFilter().
As a more practical alternative, you could instead use QQmlScriptString as a property & QQmlExpression to execute it. Using QQmlExpression allows you to be notified of context changes with setNotifyOnValueChanged.
Your syntax would change to be like so : filter: o.size > slider.value.
If you are looking for an out of the box solution, I've implemented this in a library of mine : SortFilterProxyModel on GitHub
You can take a look at ExpressionFilter & ExpressionSorter, those do the same as what you initially wanted. You can check the complete source code in the repo.
How to use it :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: ExpressionFilter { expression: model.size > 3 }
sorters: ExpressionSorter { expression: modelLeft.size < modelRight.size }
}
But as #dtech mentionned, the overhead of going back and forth between qml and c++ for each row of the model is quite noticeable. That's why I created more specific filters and sorters. In your case, we would use RangeFilter and RoleSorter :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: RangeFilter {
roleName: "size"
minimumValue > 3
minimumInclusive: true
}
sorters: RoleSorter { roleName: "size" }
}
Doing like this, we have a nice declarative API and the parameters are only passed once from qml to c++. All the filtering and sorting is then entirely done on the c++ side.
Update:
Revisiting the issue, I finally came with a finalized solution, so I decided to drop in some updates. First, the relevant code:
void set_filter(QJSValue f) {
if (f != m_filter) {
m_filter = f;
filterChanged();
invalidate();
}
}
void set_sorter(QJSValue f) {
if (f != m_sort) {
m_sort = f;
sorterChanged();
sort(0, Qt::DescendingOrder);
}
}
bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const {
if (!m_filter.isCallable()) return true;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->index(sourceRow, 0, sourceParent).data().value<QObject*>()));
return m_filter.call(l).toBool();
}
bool lessThan(const QModelIndex & left, const QModelIndex & right) const {
if (!m_sort.isCallable()) return false;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->data(left).value<QObject*>()));
l.append(_engine->newQObject(sourceModel()->data(right).value<QObject*>()));
return m_sort.call(l).toBool();
}
I found this solution to be simpler, safer and better performing than the QQmlScriptString & QQmlExpression duo, which does offer automatic updates on notifications, but as already elaborated in the comments below GrecKo's answer, was kinda flaky and not really worth it.
The hack to get auto-updates for external context property changes is to simply reference them before returning the actual functor:
filter: { expanded; SS.showHidden; o => expanded && (SS.showHidden ? true : !o.hidden) }
Here is a simple expression using the new shorthand function syntax, it references expanded; SS.showHidden; in order to trigger reevaluations if those change, then implicitly returns the functor
o => expanded && (SS.showHidden ? true : !o.hidden)
which is analogous to:
return function(o) { return expanded && (SS.showHidden ? true : !o.hidden) }
which filters out objects based on whether the parent node is expanded, whether the child node is hidden and whether hidden objects are still displayed.
This solution has no way to automatically respond to changes to o.hidden, as o is inserted into the functor upon evaluation and can't be referenced in the binding expression, but this can easily be implemented in the delegates of views that need to dynamically respond to such changes:
Connections {
target: obj
onHiddenChanged: triggerExplicitEvaluation()
}
Remember that the use case involves a schema-less / single QObject* role model that facilitates a metamorphic data model where model item data is implemented via QML properties, so none of the role or regex stock filtering mechanisms are applicable here, but at the same time, this gives the genericity to use a single mechanism to implement sorting and filtering based on any criteria and arbitrary item data, and performance is very good, despite my initial concerns. It doesn't implement a sorting order, that is easily achievable by simply flipping the comparison expression result.
I currently use a {{link-to}} helper that was written by someone else to explicitly state the query params to pass to the next route and strip out others that are not stated. It looks like this:
//link-to example
{{#link-to 'route' (explicit-query-params fromDate=thisDate toDate=thisDate)} Link Text {{/link-to}}
//the helper
import {helper} from '#ember/component/helper';
import Object from '#ember/object';
import {assign} from '#ember/polyfills';
export function explicitQueryParams(params, hash) {
let values = assign({}, hash);
values._explicitQueryParams = true;
return Object.create({
isQueryParams: true,
values,
});
}
export default helper(explicitQueryParams);
// supporting method in router.js
const Router = EmberRouter.extend({
_hydrateUnsuppliedQueryParams(state, queryParams) {
if (queryParams._explicitQueryParams) {
delete queryParams._explicitQueryParams;
return queryParams;
}
return this._super(state, queryParams);
},
});
I've recently had a use case where I need to apply the same logic to a transitionTo() that is being used to redirect users from a route based on their access:
beforeModel() {
if (auth) {
this.transitionTo('route')
} else {
this.transitionTo('access-denied-route')
}
},
I am really struggling to see how I can refactor what I have in the handlebars helper to a re-usable function in the transitionTo() segment. I'm even unsure if transitionTo() forwards the same arguments as {{link-to}} or if I will have to fetch the queryParams somehow from a different location.
Any insight would be greatly appreciated.
Well first off, tapping into private methods like _hydrateUnsuppliedQueryParams is risky. It will make upgrading more difficult. Most people use resetController to clear stick query params. You could also explicitly clear the default values by passing empty values on the transition.
But, ill bite because this can be fun to figure out :) Check this ember-twiddle that does what you're wanting.
If you work from the beginning in the transitionTo case, we can see that in the router.js implementation:
transitionTo(...args) {
let queryParams;
let arg = args[0];
if (resemblesURL(arg)) {
return this._doURLTransition('transitionTo', arg);
}
let possibleQueryParams = args[args.length - 1];
if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
queryParams = args.pop().queryParams;
} else {
queryParams = {};
}
let targetRouteName = args.shift();
return this._doTransition(targetRouteName, args, queryParams);
}
So, if the last argument is an object with a query params obj, that's going directly into _doTransition, which ultimately calls:
_prepareQueryParams(targetRouteName, models, queryParams, _fromRouterService) {
let state = calculatePostTransitionState(this, targetRouteName, models);
this._hydrateUnsuppliedQueryParams(state, queryParams, _fromRouterService);
this._serializeQueryParams(state.handlerInfos, queryParams);
if (!_fromRouterService) {
this._pruneDefaultQueryParamValues(state.handlerInfos, queryParams);
}
}
which has the _hydrateUnsuppliedQueryParams function. So, to make this all work, you can't share the function directly from the helper you've created. Rather, just add _explicitQueryParams: true to your query params. Job done :)
The link-to version is different. The query params use
let queryParams = get(this, 'queryParams.values');
since the link-to component can take a variable number of dynamic segments and there needs to be some way to distinguish between the passed dynamic segments, a passed model, and query params.
I have a polymorphic (as in arbitrary roles) QObject model that is mostly instantiated declaratively from QML, as in this answer, and I would like to be able to have custom data "views" that sort and filter the model via arbitrary, and potentially - runtime generated from code strings JS functors, something like that:
DataView {
sourceModel: model
filter: function(o) { return o.size > 3 }
sort: function(a, b) { return a.size > b.size }
}
The QSortFilterProxyModel interface doesn't seem to be particularly well suited to the task, instead being fixated on static roles and pre-compiled rules.
I tried using QJSValue properties on the C++ side, but it seems like it is not possible, the C++ code just doesn't compile with that property type. And if I set the property type to QVariant I get error messages from QML that functions can only be bound to var properties. Evidently, var to QVariant conversion doesn't kick in here as it does for return values.
As you mentionned, you could use QJSValue. But that's pretty static. What if you want to use a filter like filter: function(o) { return o.size > slider.value; } with a dynamic slider ? You'll have to manually call invalidateFilter().
As a more practical alternative, you could instead use QQmlScriptString as a property & QQmlExpression to execute it. Using QQmlExpression allows you to be notified of context changes with setNotifyOnValueChanged.
Your syntax would change to be like so : filter: o.size > slider.value.
If you are looking for an out of the box solution, I've implemented this in a library of mine : SortFilterProxyModel on GitHub
You can take a look at ExpressionFilter & ExpressionSorter, those do the same as what you initially wanted. You can check the complete source code in the repo.
How to use it :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: ExpressionFilter { expression: model.size > 3 }
sorters: ExpressionSorter { expression: modelLeft.size < modelRight.size }
}
But as #dtech mentionned, the overhead of going back and forth between qml and c++ for each row of the model is quite noticeable. That's why I created more specific filters and sorters. In your case, we would use RangeFilter and RoleSorter :
import SortFilterProxyModel 0.2
// ...
SortFilterProxyModel {
sourceModel: model
filters: RangeFilter {
roleName: "size"
minimumValue > 3
minimumInclusive: true
}
sorters: RoleSorter { roleName: "size" }
}
Doing like this, we have a nice declarative API and the parameters are only passed once from qml to c++. All the filtering and sorting is then entirely done on the c++ side.
Update:
Revisiting the issue, I finally came with a finalized solution, so I decided to drop in some updates. First, the relevant code:
void set_filter(QJSValue f) {
if (f != m_filter) {
m_filter = f;
filterChanged();
invalidate();
}
}
void set_sorter(QJSValue f) {
if (f != m_sort) {
m_sort = f;
sorterChanged();
sort(0, Qt::DescendingOrder);
}
}
bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const {
if (!m_filter.isCallable()) return true;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->index(sourceRow, 0, sourceParent).data().value<QObject*>()));
return m_filter.call(l).toBool();
}
bool lessThan(const QModelIndex & left, const QModelIndex & right) const {
if (!m_sort.isCallable()) return false;
QJSValueList l;
l.append(_engine->newQObject(sourceModel()->data(left).value<QObject*>()));
l.append(_engine->newQObject(sourceModel()->data(right).value<QObject*>()));
return m_sort.call(l).toBool();
}
I found this solution to be simpler, safer and better performing than the QQmlScriptString & QQmlExpression duo, which does offer automatic updates on notifications, but as already elaborated in the comments below GrecKo's answer, was kinda flaky and not really worth it.
The hack to get auto-updates for external context property changes is to simply reference them before returning the actual functor:
filter: { expanded; SS.showHidden; o => expanded && (SS.showHidden ? true : !o.hidden) }
Here is a simple expression using the new shorthand function syntax, it references expanded; SS.showHidden; in order to trigger reevaluations if those change, then implicitly returns the functor
o => expanded && (SS.showHidden ? true : !o.hidden)
which is analogous to:
return function(o) { return expanded && (SS.showHidden ? true : !o.hidden) }
which filters out objects based on whether the parent node is expanded, whether the child node is hidden and whether hidden objects are still displayed.
This solution has no way to automatically respond to changes to o.hidden, as o is inserted into the functor upon evaluation and can't be referenced in the binding expression, but this can easily be implemented in the delegates of views that need to dynamically respond to such changes:
Connections {
target: obj
onHiddenChanged: triggerExplicitEvaluation()
}
Remember that the use case involves a schema-less / single QObject* role model that facilitates a metamorphic data model where model item data is implemented via QML properties, so none of the role or regex stock filtering mechanisms are applicable here, but at the same time, this gives the genericity to use a single mechanism to implement sorting and filtering based on any criteria and arbitrary item data, and performance is very good, despite my initial concerns. It doesn't implement a sorting order, that is easily achievable by simply flipping the comparison expression result.
I have a already json11 object build:
Json my_json = Json::object {
{ "key1", "value1" },
{ "key2", false },
{ "key3", Json::array { 1, 2, 3 } },
};
And I want to add a new value to key3 array like this:
my_json["keys3"].push_back(4);
How I can achieve that? I can't see anything to modify objects (all operator to access values are const!)
Unfortunately it seems you cannot modify directly an instance of Json.
It's an opaque wrapper around a JsonValue that is inaccessible.
Anyway, note that a Json::object is a std::map<std::string, Json>. You can create a copy of your original Json::object as it follows:
Json::object json_obj = my_json.object_items();
Then the key keys3 contains a Json::array, that is nothing more than a std::vector<Json>.
You can modify it as it follows:
json_obj["keys3"].push_back(4);
Finally you must create a new Json from your Json::object and that's all:
Json another_json = json_obj;
Quite expensive an operation.
I suspect the right way is to create your objects step by step and at the very end of your process create an instance of a Json.
I found next issues on github about this question:
[https://github.com/dropbox/json11/issues/20]: more o less the same that skypjack explain
The Json type is immutable, but the Json::object type is just a
std::map, so your code would work if the first line created a
Json::object instead. You can use that map to build whatever data you
want, then wrap it in as Json(data) when you're done modifying it. You
can also extract the map from a Json using object_items(), copy it,
mutate it, and use it to create a new Json, similar to a builder
pattern.
[https://github.com/dropbox/json11/issues/75]: This one is very interesting because explain why it's not possible to modify a json
The Json type is intended to be an immutable value type, which has a
number of advantages including thread safety and the ability to share
data across copies. If you want a mutable array you can use a
Json::array (which is just a typedef for a vector) and mutate it
freely before putting it into a Json object.
If you are using json11 you can do it like this:
Json json = Json::object
{
{
"num_neurons_in_each_layer", Json::array{ 1000, 1000, 10, 10 }
},
{
"non_editable_data",
Json::object
{
{"train_error", -1.0 },
{"validation_error", -1.0 }
}
}
};
Json* p_error = const_cast<Json*>(&json["non_editable_data"].
object_items().find("validation_error")->second);
*p_error = Json(2.0); //"validation_error" has been modified to 2.0
p_error = nullptr;
delete p_error;
I have three objects:
class Customer: Object {
dynamic var solution: Solution!;
...
}
class Solution: Object {
dynamic var data: Data!;
...
}
class Data: Object {
...
}
Now i need to move the Data Object from Solution to Customer so that it becomes:
class Customer: Object {
dynamic var solution: Solution!;
dynamic var data: Data!;
...
}
I have no idea how I have to implement my Realm Migration method so that everything works fine and that I wont lose data.
I did some experiments with the Realm migrations sample app and came up with this potential solution:
In a migration block, you can only interact with your Realm file via the migration object. Any attempts to directly access the Realm file mid-migration will result in an exception.
That being said, it's possible to have nested calls to migration.enumerateObjects referencing different Realm model object classes. As such, it should simply be a matter of initially enumerating through the Customer objects, and in each iteration, enumerate through the Solution objects to find the corresponding one with the right data value. Once found, it should be possible to set the Customer object with the data from the Solution object.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
migration.enumerateObjects(ofType: Customer.className()) { oldCustomerObject, newCustomerObject in
migration.enumerateObjects(ofType: Solution.className()) { oldSolutionObject, newSolutionObject in
//Check that the solution object is the one referenced by the customer
guard oldCustomerObject["solution"].isEqual(oldSolutionObject) else { return }
//Copy the data
newCustomerObject["data"] = oldSolutionObject["data"]
}
}
}
}
})
I feel I need to stress that this code is by no means tested and guaranteed to work in its present state. So I recommend you make sure you thoroughly test it on some dummy data you wouldn't miss beforehand. :)
Swift 4, Realm 3
I had to migrate a Realm object that linked to another object. I wanted to remove the explicit link and replace it with an object ID. TiM's solution got me most of the way there, and just needed a little refinement.
var config = Realm.Configuration()
config.migrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < CURRENT_SCHEMA_VERSION {
// enumerate the first object type
migration.enumerateObjects(ofType: Message.className()) { (oldMsg, newMsg) in
// extract the linked object and cast from Any to DynamicObject
if let msgAcct = oldMsg?["account"] as? DynamicObject {
// enumerate the 2nd object type
migration.enumerateObjects(ofType: Account.className()) { (oldAcct, newAcct) in
if let oldAcct = oldAcct {
// compare the extracted object to the enumerated object
if msgAcct.isEqual(oldAcct) {
// success!
newMsg?["accountId"] = oldAcct["accountId"]
}
}
}
}
}
}