0

I might be experiencing either a bug, or I misunderstand something about general javascript syntax. Using ServiceNow UI Builder, I'm trying to refresh the datasource of a specific data visualization component. Which requires me to use setState and send in an entire JSON blob.

The following works as expected:

api.setState('intAssignedDonut', {
        "header": "Interactions assigned to one of my groups",
        "datasource": [{
            "isDatabaseView": false,
            "allowRealTime": true,
            "sourceType": "table",
            "label": {
                "message": "Interaction"
            },
            "tableOrViewName": "interaction",
            "filterQuery": "active=true^assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744",
            "id": "intAssigned",
            "elm": {}
        }],
        "metric": [{
            "dataSource": "intAssigned",
            "id": "intAssignedMetric",
            "aggregateFunction": "COUNT",
            "numberFormat": {
                "customFormat": false
            },
            "axisId": "primary"
        }],
        "groupBy": [{
            "maxNumberOfGroups": "ALL",
            "numberOfGroupsBasedOn": "NO_OF_GROUP_BASED_ON_PER_METRIC",
            "showOthers": false,
            "groupBy": [{
                "dataSource": "intAssigned",
                "groupByField": "state",
                "isRange": false,
                "isPaBucket": false
            }]
        }]
    });

However, I only need to alter a few properties, not the whole thing. So I thought I'd just clone the thing into a temp object, change what I need, then pass the cloned object back.

let clientState_intAssignedDonut = api.state.intAssignedDonut;
clientState_intAssignedDonut.header = 'Interactions assigned to one of my groups';
clientState_intAssignedDonut.datasource[0].filterQuery = 'active=true^assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744';
    
api.setState("intAssignedDonut", clientState_intAssignedDonut);

This seems to update the header properly, but the component doesn't refresh the datasource. Even if I console.log api.state.intAssignedDonut it looks identical to the whole JSON blob.

EDIT: I also tried using spread operators, but I can't figure out how to target the datasource[0]

api.setState("intAssignedDonut", {
        ...api.state.intAssignedDonut,
        header: "Interactions assigned to one of my groups",
        datasource[0].filterQuery: "active=true^assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744"
    });
Kenny Bones
  • 5,017
  • 36
  • 111
  • 174

1 Answers1

2

Javascript objects are passed by reference values, and react state is immutable:

let clientState_intAssignedDonut = api.state.intAssignedDonut;
api.setState("intAssignedDonut", clientState_intAssignedDonut);

This is mutating the state directly, and React will ignore your update if the next state is equal to the previous state, which is determined by an Object.is comparison to check if both objects are of the same value, see docs

Your second attempt is heading to the right direction using spread operator:

Update method one: first copy the nested object using: JSON.parse(JSON.stringify(object)), or you can find other method in this question: What is the most efficient way to deep clone an object in JavaScript?

let copied = JSON.parse(JSON.stringify(api.state.intAssignedDonut)); // copy a nested object
copied.header = "Interactions assigned to one of my groups";
copied.datasource[0].filterQuery = "active=true^assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744";
setState("intAssignedDonut",copied);

Update method two:

setState("intAssignedDonut",{
  ...api.state.intAssignedDonut,
  header: "Interactions assigned to one of my groups",
  datasource: [{ ...state.datasource[0], filterQuery: "active=true^assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744" }]
});

Check out sandbox

Enfield Li
  • 1,952
  • 1
  • 8
  • 23
  • They are not passed **by** reference. They are *reference values*, i.e. when created a reference to the object is assigned to the variable/passed to the function. But that is not the same as pass *by* reference. https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference https://stackoverflow.com/q/518000/218196 – Felix Kling Aug 09 '22 at 10:19
  • @Felix Kling You're correct, I've updated the answer. – Enfield Li Aug 09 '22 at 10:28
  • @Felix Kling Thank you for the correction and resource. – Enfield Li Aug 09 '22 at 11:35
  • Thanx! I'm trying update method two. But I still get an "unexpected token [0]" on the first datasource[0]. The first update method gives me the same results as previousy, the component doesn't update, only the heading – Kenny Bones Aug 09 '22 at 12:03
  • @Kenny Bones Hi, I've updated the answer with a sandbox attached in it. Yeah, copying nested object wasn't as easy as I originally thought, and after trying out in a sandbox, I've successfully managed to make it work, feel free to check out the sandbox, and let me know if you have any confusion left. – Enfield Li Aug 09 '22 at 12:35
  • Aha, your updated code using JSON.parse works as intended. Could it be because the original JSON object has properties wrapped in quotes? While the data set in your sandbox does not. – Kenny Bones Aug 09 '22 at 12:45
  • You can take a look at [this answer](https://stackoverflow.com/a/8294127/16648127) comparing JSON and JS object. I think, JSON data should be parsed after being fetched in order to use object notation(using dot notation or bracket notation) to update. – Enfield Li Aug 09 '22 at 13:05
  • I see, the initial client state parameter is actually a JSON blob. I'd assume the spread operator would copy everything exactly, but something must be failing along the way. I'd just get used to parsing the thing then :) Thanx a lot! – Kenny Bones Aug 09 '22 at 13:24
  • @Kenny Bones [This answer](https://stackoverflow.com/a/61422332/16648127) details the use of spread operator copy mechnism, I'd recommend take a look. – Enfield Li Aug 11 '22 at 08:20