agile-ts / agile

🌌 Global State and Logic Library for JavaScript/Typescript applications

Home Page:https://agile-ts.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Computed value is not recomputed after dependencies change

leopf opened this issue Β· comments

commented

πŸ› Bug report

I have a problem with not updating computed values. I am using a demo API to download and insert users into a collection. I want to use a computed value to get a user from this collection based on a user id, which is defined in a state. The first compute works with the default user id, but after updating the user id the value is not recomputed. I took a look at the deps prop and found all the necessary observers with the correct values, though the value of the computed user was incorrect.

The first compute only works if I define the computed value after inserting the users into the collection, otherwise this computed value is stuck on undefined.

πŸ€– Current Behavior

const data: any[] = await fetch('https://jsonplaceholder.typicode.com/users').then(res => res.json());

const userCollection = createCollection();
const selectedUserId = createState(1);
selectedUserId.watch(() => console.log("---- new selected user id!"));

for (const item of data) {
    userCollection.collect(item);
}

const selectedUser = createComputed(() => {
    return userCollection.getItemValue(selectedUserId.value);
});

console.log("auto:", selectedUser.value);
selectedUserId.set(2);
console.log("manual:", userCollection.getItemValue(selectedUserId.value));
console.log("auto:", selectedUser.value);
console.log(selectedUser.deps);

output:

auto: {
  id: 1,
  name: 'Leanne Graham',
  username: 'Bret',
  email: 'Sincere@april.biz',
  address: {
    street: 'Kulas Light',
    suite: 'Apt. 556',
    city: 'Gwenborough',
    zipcode: '92998-3874',
    geo: { lat: '-37.3159', lng: '81.1496' }
  },
  phone: '1-770-736-8031 x56442',
  website: 'hildegard.org',
  company: {
    name: 'Romaguera-Crona',
    catchPhrase: 'Multi-layered client-server neural-net',
    bs: 'harness real-time e-markets'
  }
}
---- new selected user id!
manual: {
  id: 2,
  name: 'Ervin Howell',
  username: 'Antonette',
  email: 'Shanna@melissa.tv',
  address: {
    street: 'Victor Plains',
    suite: 'Suite 879',
    city: 'Wisokyburgh',
    zipcode: '90566-7771',
    geo: { lat: '-43.9509', lng: '-34.4618' }
  },
  phone: '010-692-6593 x09125',
  website: 'anastasia.net',
  company: {
    name: 'Deckow-Crist',
    catchPhrase: 'Proactive didactic contingency',
    bs: 'synergize scalable supply-chains'
  }
}
auto: {
  id: 1,
  name: 'Leanne Graham',
  username: 'Bret',
  email: 'Sincere@april.biz',
  address: {
    street: 'Kulas Light',
    suite: 'Apt. 556',
    city: 'Gwenborough',
    zipcode: '92998-3874',
    geo: { lat: '-37.3159', lng: '81.1496' }
  },
  phone: '1-770-736-8031 x56442',
  website: 'hildegard.org',
  company: {
    name: 'Romaguera-Crona',
    catchPhrase: 'Multi-layered client-server neural-net',
    bs: 'harness real-time e-markets'
  }
}
Set(2) {
  StateObserver {
    agileInstance: [Function (anonymous)],
    key: undefined,
    dependents: Set(1) { [StateObserver] },
    subscribedTo: Set(0) {},
    value: 2,
    previousValue: 1,
    state: [Function (anonymous)],
    nextStateValue: 2
  },
  StateObserver {
    agileInstance: [Function (anonymous)],
    key: 2,
    dependents: Set(1) { [StateObserver] },
    subscribedTo: Set(0) {},
    value: {
      id: 2,
      name: 'Ervin Howell',
      username: 'Antonette',
      email: 'Shanna@melissa.tv',
      address: [Object],
      phone: '010-692-6593 x09125',
      website: 'anastasia.net',
      company: [Object]
    },
    previousValue: null,
    state: [Function (anonymous)],
    nextStateValue: {
      id: 2,
      name: 'Ervin Howell',
      username: 'Antonette',
      email: 'Shanna@melissa.tv',
      address: [Object],
      phone: '010-692-6593 x09125',
      website: 'anastasia.net',
      company: [Object]
    }
  }
}

🎯 Expected behavior

I would expect this to work, as defined in the docs.

const MY_COMPUTED = createComputed(() => {
    const user = USERS.getItemValue(CURRENT_USER_ID.value);
    return `My name is '${user?.name} and I am ${user?.age} years old.`;
});
MY_COMPUTED.value; // Returns "My name is 'hans' and I am 10 years old."
USERS.update(CURRENT_USER_ID.value, {name: 'jeff'})
MY_COMPUTED.value; // Returns "My name is 'jeff' and I am 10 years old." 

πŸ“„ Reproducible example

I created a simple main.ts file and ran it with ts-node.

import { createState, createCollection, createComputed } from "@agile-ts/core";
import fetch from 'cross-fetch';

async function init() {
    const data: any[] = await fetch('https://jsonplaceholder.typicode.com/users').then(res => res.json());

    const userCollection = createCollection();
    const selectedUserId = createState(1);
    selectedUserId.watch(() => console.log("---- new selected user id!"));

    for (const item of data) {
        userCollection.collect(item);
    }

    const selectedUser = createComputed(() => {
        return userCollection.getItemValue(selectedUserId.value);
    });

    console.log("auto:", selectedUser.value);
    selectedUserId.set(2);
    console.log("manual:", userCollection.getItemValue(selectedUserId.value));
    console.log("auto:", selectedUser.value);
    console.log(selectedUser.deps);
}

init();

πŸ’‘ Suggested solution(s)

βž• Additional notes

πŸ’» Your environment

Software Version(s)
TypeScript v4.5.4
npm/Yarn v8.3.2
NodeJs v14.18.2
@agile-ts/core v0.2.8
@agile-ts/react N/A
@agile-ts/api N/A
@agile-ts/multieditor N/A
ts-node v10.4.0
commented

Thanks for reporting this issue @leopf ;D
It looks like that the computed value isn't ingested into the runtime correctly.
The compute() method is called as expected, and returns the correct value (see red marked part).
However, this returned value doesn't seem to get applied to the State.

image
https://codesandbox.io/s/issue-219-ufcck?file=/src/main.js

I'll dig into your issue this evening and keep you updated.

Thanks ;D


  • You can also use Selectors to select a specific State in a Collection.
commented

Thanks for the quick response! I did some digging as well, and it seems like the computed value is updated asynchronously. I added a sideeffect to test this:

...
const selectedUser = createComputed(() => {
    return userCollection.getItemValue(selectedUserId.value);
});

selectedUser.addSideEffect("somerandomkey", (instance) => console.log("NEW SELECTED USER:", instance.value), { weight: 0 });
...

The new output is the same except after the already posted output it outputs the new value for the computed instance.

NEW SELECTED USER: {
  id: 2,
  name: 'Ervin Howell',
  username: 'Antonette',
  email: 'Shanna@melissa.tv',
  address: {
    street: 'Victor Plains',
    suite: 'Suite 879',
    city: 'Wisokyburgh',
    zipcode: '90566-7771',
    geo: { lat: '-43.9509', lng: '-34.4618' }
  },
  phone: '010-692-6593 x09125',
  website: 'anastasia.net',
  company: {
    name: 'Deckow-Crist',
    catchPhrase: 'Proactive didactic contingency',
    bs: 'synergize scalable supply-chains'
  }
}

This would make sense, since the compute function within the Computed instance is asynchronous. For this to work, it might be necessary to differentiate between async and sync compute functions under the hood.

If my suspicion proves correct, I will follow up with a PR.

commented

Yeah, you are right πŸ‘
The fact that the wrapper compute method itself is async seems to create this issue.
Adding a small timeout shortly after mutating the userId seems to give the compute() method
enough time to compute itself asynchronously, as the wrapper method is async.

See code sandbox: https://codesandbox.io/s/issue-219-ufcck?file=/src/main.js

commented

fixed in #222