Update a store property - redux

I am getting a response from an API as below:
{
"results": {
"6": {
"id": 6,
"name": "akkha madrasi mirchi1",
},
"7": {
"id": 7,
"name": "almonds",
},
"8": {
"id": 8,
"name": "alu1",
}
}
}
I am storing response.results in results reducer store {} so that I can access results as store.results. I want to modify an object inside the results. The API only returns the updated object. Now I want to update id: 6 in store.results.
{
"results": {
"6": {
"id": 6,
"name": "Modified akkha madrasi mirchi1",
}
}
}
If I do this in the results reducer:
[...state,action.response]
will the object with key 6 replace the object with matching keys?
I am expecting state to be (note name changed to Modified akkha madrasi mirchi1 for id: 6):
"results": {
"6": {
"id": 6,
"name": "Modified akkha madrasi mirchi1",
},
"7": {
"id": 7,
"name": "almonds",
},
"8": {
"id": 8,
"name": "alu1",
}
}
I want objects to be updated even if they are nested. Just match the key and keep on replacing.

If I do this in the results reducer:
[...state,action.response]
will the object with key 6 replace the object with matching keys?
No. This is what you might use if your state was an array, but from the information you've given it's an object (all of results is an object with ids as keys).
What you're probably looking for is something like this:
return {
...state,
...action.response.results
}
This will create a shallow copy of the state, preserving the object references for each result item (more efficient than doing a deep copy if you don't need to). It will then overwrite the previous id: 6 with whatever is in response.results.
Couple of things to remember about this:
It assumes you have object spread available. If not, Object.assign({}, state, action.response.results) will have the same effect but (IMHO) is not as clear.
Whatever is returned from the server will entirely replace whatever you already had in id: 6. That means if you were tracking various bits of state in the client for that id, or the server doesn't return everything you need, you might get some unexpected results.
const state = {
6: {
id: 6,
name: 'akkha madrasi mirchi1'
},
7: {
id: 7,
name: 'almonds'
},
8: {
id: 8,
name: 'alu1'
}
}
const response = {
results: {
6: {
id: 6,
name: 'Modified akkha madrasi mirchi1'
}
}
}
console.log({
...state,
...response.results
})
Just for completeness, here's how you'd reduce it if you had the nested structure as described in comment thread:
const state = {
results: {
6: {
id: 6,
name: 'akkha madrasi mirchi1'
},
7: {
id: 7,
name: 'almonds'
},
8: {
id: 8,
name: 'alu1'
}
}
}
const response = {
results: {
6: {
id: 6,
name: 'Modified akkha madrasi mirchi1'
}
}
}
console.log({
...state,
results: {
...state.results,
...response.results
}
})

Assuming that state is:
{
"6": {
"id": 6,
"name": "akkha madrasi mirchi1"
},
"7": {
"id": 7,
"name": "almonds"
},
"8": {
"id": 8,
"name": "alu1"
}
}
and you want to update with the following:
const obj = {
"6": {
"id": 6,
"name": "Modified akkha madrasi mirchi1"
}
};
Then the following will do it:
state = Object.assign( {}, state, obj);

I could do it using the immutability-helper
this is my action creator:
export function itemupdate(response) {
return {
type: 'ITEMS_UPDATE_DATA_SUCCESS',
idx: Object.keys(response.results)[0],
obj: response.results[Object.keys(response.results)[0]]
}
}
This is how i replace that particular id object in the results.
export function data(state = {}, action) {
case 'ITEMS_UPDATE_DATA_SUCCESS':
return update(state,{results: {[action.idx]: {$set: action.obj} }})
default:
return state;
}

Related

Normalizr with Redux with nested array of objects

I'm just getting started with using normalizr with Redux, and I can't make it work. Even though I can do it with plain JavaScript.
I have an array of objects
const data = [
{
data_detail: [
{
category: 'newCategory',
_id: '123',
},
],
_id: 'abc_id',
customer: {
_id: '456',
email: 'hello#gmail.com',
name: 'Bob',
},
date: '2021-01-10T01:51:24.387Z',
},
];
And I need to transform it to
const normalizedResponse = {
customers: {
'456': {
_id: '456',
email: 'hello#gmail.com',
name: 'Bob',
},
},
details: {
'123': {
category: 'newCategory',
_id: '123',
},
},
orders: {
'abc_id: {
order_detail: [123],
_id: 'abc_id',
customer: '456',
date: '2021-01-10T01:51:24.387Z',
},
},
};
Step 1: Display just orders
What I do:
const userSchema = new schema.Entity(
'orders',
);
const userListSchema = new schema.Array(userSchema);
const normalizedData = normalize(data, userListSchema);
What I get
{
"entities": {
"orders": {
"abc_id": {
"data_detail": [
{
"category": "newCategory",
"id": "123"
}
],
"id": "abc_id",
"customer": {
"id": "456",
"email": "hello#gmail.com",
"name": "Bob"
},
"date": "2021-01-10T01:51:24.387Z"
},
"abc_id-02": {
"data_detail": [
{
"category": "newCategory1",
"id": "123-02"
}
],
"id": "abc_id-02",
"customer": {
"id": "456-02",
"email": "hello#gmail.com",
"name": "Bob"
},
"date": "2001-01-10T01:51:24.387Z"
}
}
},
"result": [
"abc_id",
"abc_id-02"
]
}
What I'm trying to get:
orders: {
'abc_id: {
order_detail: [123],
_id: 'abc_id',
customer: '456',
date: '2021-01-10T01:51:24.387Z',
},
},
The question: How to remove some fields from orders and add new ones?
You have 3 different entity types in your data object. First draft out a schema for each of them:
const detail = new schema.Entity('details');
const customer = new schema.Entity('customers');
const order = new schema.Entity('orders');
Then go back and fill in the relationships. It looks like order is the outermost entity. An order contains an array of details/categories and a single customer.
const order = new schema.Entity('orders', {
data_detail: [detail],
customer,
});
All of your entities use _id instead of id, so you need to set the idAttribute.
Your data is an array of order. You can use new schema.Array(order) but you can also just use [order].
Here's your final code:
const customer = new schema.Entity("customers", {}, { idAttribute: "_id" });
const detail = new schema.Entity("details", {}, { idAttribute: "_id" });
const order = new schema.Entity(
"orders",
{
data_detail: [detail],
customer
},
{ idAttribute: "_id" }
);
const normalizedData = normalize(data, [order]);
That gives you:
{
"entities": {
"details": {
"123": {
"category": "newCategory",
"_id": "123"
}
},
"customers": {
"456": {
"_id": "456",
"email": "hello#gmail.com",
"name": "Bob"
}
},
"orders": {
"abc_id": {
"data_detail": ["123"],
"_id": "abc_id",
"customer": "456",
"date": "2021-01-10T01:51:24.387Z"
}
}
},
"result": ["abc_id"]
}

Hasura - query for tags - null array supposed to return all results, but only returns items with tags

I have a query:
query SearchProductData($tagId: [Int!]) {
product(where: { productTags: {_or: {tagId: {_in: $tagId}}}}) {
id
name
productTags {
tag {
id
name
}
}
}
}
and I pass in a variable of
{"tagId": null}
I would like to get back all product, regardless of if they have tags applied or not. What happens though is it retrieves only items with tags applied, not including the items with no tags.
the DB schema is
|- product
|
|-- productTags (one to many linking table of productIDs and tagIDs)
|
|--- tags
Any ideas how to write a query for this use case?
This is expected because of how the where clause is translating to a join (you can see the Generated SQL and Execution Plan query analysis by clicking Analyze in GraphiQL console.)
I don't think it's possible without injecting a bool expression. See below:
E.g.
query Test($expression: product_bool_exp) {
product(where: $expression) {
id
name
product_tags {
tag {
id
name
}
}
}
}
with argument
{
"expression": {"product_tags": {"tag_id": {"_in": [2]}}}
}
returns back:
{
"data": {
"product": [
{
"id": 1,
"name": "product_A",
"product_tags": [
{
"tag": {
"id": 1,
"name": "tag_1"
}
},
{
"tag": {
"id": 2,
"name": "tag_2"
}
}
]
}
]
}
}
And for the other case using the same query (where there are no tags passed in, and therefore all products we can use this variable value:
{
"expression": null
}
And we get back...
{
"data": {
"product": [
{
"id": 1,
"name": "product_A",
"product_tags": [
{
"tag": {
"id": 1,
"name": "tag_1"
}
},
{
"tag": {
"id": 2,
"name": "tag_2"
}
}
]
},
{
"id": 4,
"name": "product_D",
"product_tags": []
}
]
}
}
So you can dynamically construct that where expressions and pass that as an argument to the query.

Treesort dart - reorder List based on parent/children

I have a question: I have a List like this:
List flat = [
{ id: 1, parentId: 3 },
{ id: 3, parentId: 8 },
{ id: 4, parentId: 6 },
{ id: 6, parentId: 3 },
{ id: 7, parentId: 6 },
{ id: 8, parentId: null },
{ id: 10, parentId: 8 },
{ id: 13, parentId: 14 },
{ id: 14, parentId: 10 }
]
and I want to dynamically structure this List to the following:
[
{
id: 8,
children: [
{
id: 3,
children: [
{
id: 1,
children: []
},
{
id: 6,
children: [
{ id: 4, children: [] },
{ id: 7, children: [] }
]
}
]
},
{
id: 10,
children: [
{
id: 14,
children: [
{ id: 13, children: [] }
]
}
]
}
]
}
]
I have found mutliple examples with javascript, but how can I achieve this with dart?
How about:
/// Tree-order nodes by their "parentId".
///
/// Returns the root. Assumes there is exactly one root.
Map<String, dynamic> treeOrder(List<Map<String, dynamic>> nodes) {
// Each node has a key and a parent.
// Change them all to have no parent and a children list.
var map = <int, Map<String, dynamic>>{};
for (var node in nodes) {
map[node["id"]] = node;
node["children"] = [];
}
Map<String, dynamic> result;
for (var node in nodes) {
var parentId = node.remove("parentId");
if (parentId == null) {
result = node;
} else {
map[parentId]["children"].add(node);
}
}
return result;
}
Nothing particularly clever, just keeping a map from id to value to speed up lookup.

API Platform filter entity data

I just start to use Api platform and immediately stuck with problem how to filter data.
I have entity User and i want to filter data that are present in response ( JSON API format)
{
"links": {
"self": "/api/users"
},
"meta": {
"totalItems": 2,
"itemsPerPage": 30,
"currentPage": 1
},
"data": [
{
"id": "/api/users/1",
"type": "User",
"attributes": {
"_id": 1,
"username": "jonhdoe",
"isActive": true,
"address": null
}
},
{
"id": "/api/users/3",
"type": "User",
"attributes": {
"_id": 3,
"username": "test",
"isActive": true,
"address": null
}
}
]
}
so I want to remove e.g. User with id 3, but not use filters sent via request. I just want to set filter that will be always run when someone go to /api/users.
I look to api-platform extensions but this will be applied on each request e.g. /api/trucks. So at end I just want to get something like
{
"links": {
"self": "/api/users"
},
"meta": {
"totalItems": 1,
"itemsPerPage": 30,
"currentPage": 1
},
"data": [
{
"id": "/api/users/1",
"type": "User",
"attributes": {
"_id": 1,
"username": "jonhdoe",
"isActive": true,
"address": null
}
}
]
}
As you pointed out, extensions are the way to go.
The applyToCollection method gets a $resourceClass parameter containing the current resource class.
So you can apply the WHERE clause only for a specific class in this method like:
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
{
if (User::class === $resourceClass) {
$queryBuilder->doSomething();
}
// Do nothing for other classes
}

How to add array of ids to entity that doesn't have them

I have my schema set up almost exactly how I want it in redux state, except I want to add an array of form ids to the formTemplate object.
It would look like this:
// Normalized Form Templates
{
1: {
id: '1',
isGlobal: true,
name: 'Form Template Name',
forms: [1, 2], // This is the line I want to add...but how?
},
}
// Normalized Forms
{
1: {
id: '1',
createdAt: '2016-12-28T23:30:13.547Z',
name: 'Form 1',
parentTemplate: '1',
pdfs: [1, 2],
},
2: {
id: '2',
createdAt: '2016-12-28T23:30:13.547Z',
name: 'Form 2',
parentTemplate: '1',
pdfs: [],
},
}
Here is my schema
import { schema } from 'normalizr'
const formTemplate = new schema.Entity('formTemplates', {}, {
processStrategy: value => ({
id: value.id,
name: value.attributes.title,
isGlobal: value.attributes.is_global,
}),
})
const form = new schema.Entity('forms', {
pdfs: [pdf],
}, {
processStrategy: value => ({
id: value.id,
createdAt: value.attributes.created_at,
name: value.attributes.title,
parentTemplate: value.attributes.form_template_id,
pdfs: [...value.relationships.documents.data],
}),
})
const pdf = new schema.Entity('pdfs')
export default {
data: [form],
included: [formTemplate],
}
This is an example of the API response that I'm normalizing
{
"data": [
{
"id": "5",
"type": "provider_forms",
"attributes": {
"title": "Form 1",
"created_at": "2017-01-02T06:00:42.518Z",
"form_template_id": 1
},
"relationships": {
"form_template": {
"data": {
"id": "1",
"type": "form_templates"
}
},
"documents": {
"data": [ // some pdf data here ]
}
}
}
],
"included": [
{
"id": "1",
"type": "form_templates",
"attributes": {
"title": "Form Template",
"created_at": "2016-12-29T22:24:36.201Z",
"updated_at": "2017-01-02T06:00:20.205Z",
"is_global": true
},
}
]
}
You won't be able to do this with Normalizr because the form template entity has no context back to the forms entities. Your actions/reducer need to handle this.
Okay I figured out a way to do this. I changed my formTemplate entity to map them in manually like so:
const formTemplate = new schema.Entity('formTemplates', {}, {
processStrategy: (value, parent) => {
// eslint-disable-next-line eqeqeq
const childrenForms = parent.data.filter(form => form.attributes.form_template_id == value.id)
return {
id: value.id,
name: value.attributes.title,
isGlobal: value.attributes.is_global,
forms: childrenForms.map(form => form.id),
}
},
})

Resources