meteor / react-packages

Meteor packages for a great React developer experience
http://guide.meteor.com/react.html
Other
573 stars 158 forks source link

Fix issue with useTracker and Subscriptions when using deps #306

Closed CaptainN closed 3 years ago

CaptainN commented 3 years ago

The runtime performance characteristics of the hook changed enough to warrant a minor version bump.

CaptainN commented 3 years ago

@rijk @yched I actually think this is a bit important to get out, because I think the issues I uncovered are probably effecting some apps in ways that have gone either unnoticed, or have been transitory enough to create mysterious blips in folks apps.

dburles commented 3 years ago

Hey @CaptainN this looks great. Did you intend to bump the version in this PR too?

CaptainN commented 3 years ago

@dburles I typically leave that for the release engineer. The minor version number should at least be bumped. A bunch of internals changed. It's more than a bug fix release.

rj-david commented 3 years ago

Getting a console error when running a page with withTracker

Warning: useTracker expected an array in it's second argument (dependency), but got type of undefined.

Not a problem with react-meteor-data 2.1.0

Jerry360-bit commented 3 years ago

Using useTracker single subscription. Running fine on Meteor@2.0-rc.3 with react-meteor-data 2.1.4. After updating to Meteor 2.0 with react-meteor-data 2.2.1, find and findOne methods mostly return undefined with :

Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.

filipenevola commented 3 years ago

Hi @Jerry360-bit could you open an issue with a reproduction? Read more here

Jerry360-bit commented 3 years ago

Hi @filipenevola,

Thanks for your reply. Meteor and React newbie here trying to get back into coding after being gone for about 15 years.... Still getting used to a changed development world.....

I was using useTracker(fn ,[]). This worked with react-meteor-data 2.1.4 but not in 2.2.1. Switched to useTracker(fn) and all is fine again.

Understand react-meteor-data is undergoing changes. I will just try to keep up and learn more about it.....

msgfxeg commented 3 years ago

I am having a problem with that fix after updating from meteor 2. I always getting React: “Maximum update depth exceeded” and I am stuck and my system is down for 3 days now. even I do not use any useEffect() in my code! after debugging an researching for 3 very log days I found this 'fix' and I believe it is not a fix it has broken my code so please help here is my useTracker() code which was working fine before upgrading

  const ready = useTracker(() => {
    return (
      Meteor.subscribe('countriesPub').ready() &&
      Meteor.subscribe('citiesPub').ready() &&
      Meteor.subscribe('customersPub').ready() &&
      Meteor.subscribe('districtsPub').ready() &&
      Meteor.subscribe('naturesPub').ready() &&
      Meteor.subscribe('finishesPub').ready() &&
      Meteor.subscribe('typesPub').ready() &&
      Meteor.subscribe('facilitiesPub').ready() &&
      Meteor.subscribe('amenitiesPub').ready() &&
      Meteor.subscribe('tagsPub').ready() &&
      Meteor.subscribe('brokersPub').ready() &&
      Meteor.subscribe('projectsPub').ready() &&
      Meteor.subscribe('unitsPub').ready() &&
      Meteor.subscribe('settingsPub').ready() &&
      Meteor.subscribe('notilogsPub').ready &&
      Meteor.subscribe('imagesPub').ready() &&
      Meteor.subscribe('documentsPub').ready() &&
      Meteor.subscribe('documentsNamesPub').ready()
    );
  });

  const {
    countries,
    cities,
    districts,
    natures,
    finishes,
    types,
    facilities,
    amenities,
    tags,
    brokers,
    projects,
    units,
    serial,
    systemSettings,
    systemSettingsId,
    customers,
    admins,
  } = useTracker(() => {
    if (ready) {
      const units = Units.find({}, { sort: { deleted: 1, serial: 1 } }).fetch();

      units.map((unit) => {
        unit.investors.map((investor) => {
          investor.customer = Customers.findOne({ _id: investor.customer });
        });
      });

      const setting = Settings.findOne({ systemSettings: { $exists: true } });
      const systemSettings = setting.systemSettings;
      const systemSettingsId = setting._id;
      const serialPrefix = systemSettings.unitSerialPrefix;
      const serialLeadingZeros = '0'.repeat(Number(systemSettings.unitSerialLeadingZeros));
      const serialSeed = Number(systemSettings.unitSerialSeed);
      const serialCounter = Number(systemSettings.unitSerialCounter);
      const serialNewValue = Number(serialSeed + serialCounter + 1);
      const serial = `${serialPrefix}${serialLeadingZeros}${serialNewValue}`;
      const customers = Customers.find({ 'profile.isAdmin': false }).fetch();
      const countries = Countries.find({}, { sort: { name: 1 } }).fetch();
      const cities = Cities.find({}, { sort: { name: 1 } }).fetch();
      const districts = Districts.find({}, { sort: { name: 1 } }).fetch();
      // const states= States.find({}, { sort: { name: 1 } }).fetch();
      const natures = Natures.find({}, { sort: { name: 1 } }).fetch();
      const finishes = Finishes.find({}, { sort: { name: 1 } }).fetch();
      const types = Types.find({}, { sort: { name: 1 } }).fetch();
      const facilities = Facilities.find({}, { sort: { name: 1 } }).fetch();
      const amenities = Amenities.find({}, { sort: { name: 1 } }).fetch();
      const tags = Tags.find({}, { sort: { name: 1 } }).fetch();
      const brokers = Brokers.find({}, { sort: { name: 1 } }).fetch();
      const projects = Projects.find({}, { sort: { name: 1 } }).fetch();
      const admins = Customers.find({ 'profile.isAdmin': true }).fetch();

      return {
        countries,
        cities,
        districts,
        natures,
        finishes,
        types,
        facilities,
        amenities,
        tags,
        brokers,
        projects,
        units,
        serial,
        systemSettings,
        systemSettingsId,
        customers,
        admins,
      };
    }
    return {
      countries: null,
      cities: null,
      districts: null,
      natures: null,
      finishes: null,
      types: null,
      facilities: null,
      amenities: null,
      tags: null,
      brokers: null,
      projects: null,
      units: null,
      serial: null,
      systemSettings: null,
      systemSettingsId: null,
      customers: null,
      admins: null,
    };
  }, [ready]);
pinvooeg commented 3 years ago

@Jerry360-bit thank you for your comment. It helped me a lot and my system is working again

CaptainN commented 3 years ago

@msgfxeg What specifically fixed the issue? What wasn't working, vs. what changed to make it work?

msgfxeg commented 3 years ago

@CaptainN Thank you for your concern and for asking. What was not working after upgrading from v2.1.4 to the latest version is useTracker(fn, [arrayOfDependencies]) even after weaving out the dependencies array still not working! what I have changed to make it work again is downgrading to v2.1.4 of react-meteor-data by editing the meteor packages file i.e. .meteor/packages from react-meteor-data to be react-meteor-data@=2.1.4 after that my system is up and running again.

rijk commented 3 years ago

I downgraded to 2.1.4 too, in my case the new version seemed to triggered an immediate rerender where the previous version did not, and this interrupted a framer-motion transition. I haven't done any more specific debugging yet, as downgrading fixed the issue for me.

CaptainN commented 3 years ago

I think I have an idea as to what's going on here. I think the reactiveFn reference is going stale. I need to add a way to keep that reference fresh without creating a ton of new renders. Probably that'll mean storing it in a ref on every render, and then using that ref in autoRun. The old hook did that for different reasons, and probably explains why that one doesn't exhibit this problem.

I'll patch this later tonight.

CaptainN commented 3 years ago

@rijk That FramerMotion problem is not likely to be solved by the fix I described above - but you should also know, that under some situations, the old hook will do that too... (You may be able to workaround that by using useMemo on the results of useTracker - yes, that feels hacky...) That uncertainty is one of the reasons I changed this (also to support StrictMode and ConcurrentMode cleanly).

For that type use case, you may need the experimental useFind hook, and/or we may need to add a comparator function extension to useTracker, which has been a desired feature for a long while. I think it may have become necessary to implement that for certain types of workflows.

For the issue of the error we have been discussing, if you remove the deps array altogether, does that fix the problem? (if you do that, it should use the useTrackerNoDeps internal variant, which is a completely different implementation from the deps variant)

CaptainN commented 3 years ago

Can you guys confirm that when removing the deps array (not just using an empty array) the problem persists?

rijk commented 3 years ago

Not sure if you were asking me as well, but I cannot remove the array as there are actual deps in it.

Jerry360-bit commented 3 years ago

For me the problem clears when not using any deps array. Using an empty array the problem persists.

CaptainN commented 3 years ago

@rijk You actually should be able to. Deps is optional in useTracker and I actually think the no-deps path should become the recommended default path for most users. You'll still get your double render though...

make-github-pseudonymous-again commented 3 years ago

I am running react-meteor-data@2.2.1. I have problems with things not loading depending on timing artefacts, for instance a hook will work only if some entries are already present in minimongo. I don't remember witnessing these problems before upgrading to Meteor 2.0.

Two examples of what I am experiencing:

  1. In an attachments system: I can attach uploads to both patients and consultations. A consultation is linked to a patient. If I want to list all attachments that can be reached from a patient directly (attached to the patient) or indirectly (attached to a consultation linked to the patient) I end up with a hook waterfall. This leads to problems when trying to load these attachments from scratch but no problem if I first open a page that loads the patient's consultations then navigate to the page that loads these attachments.
  2. In a paginating system: if I want page 7 to be displayed reactively, I need to load pages 1 through 7 in minimongo if I want to use basic pub/sub without polluting other pub/sub results. This page loads consultations and each consultation loads the patient it is linked to. If I navigate forward to page 8, consultations get loaded but not their respective patients. If I navigate backwards however, both consultations and their patients are loaded properly. Note that navigating forward loads an additional page of results in minimongo while navigating backwards removes a page of results in minimongo.

In both cases the problem can be generalized to the following: On a fresh query, with a waterfall of hooks (X loading Y's loading Z's), I end up with the second hook waiting forever (Y's are loaded but not Z's).

Another problem I have with the pagination example is that, if multiple consultations of the same pages are linked to the same patient, sometimes, only a subset of the patients are loaded successfully, the others hang forever.

All these problem go away if I remove dependency arrays in useTracker calls. These dependency arrays are arrays of values that uniquely identify each query.

Are my problems related to what is being discussed here?

CaptainN commented 3 years ago

@aureooms Oh hey, you are here. PR #314 should fix these problems. I'd appreciate it if you could give that a test.

In case you haven't done that before, you can do test by downloading/checking out the bugfix branch, and copying the react-meteor-data folder to your local packages directory in your app.

make-github-pseudonymous-again commented 3 years ago

@CaptainN I confirm using deps in useTracker works as expected in my project with react-meteor-data@2.2.2.

make-github-pseudonymous-again commented 3 years ago

Could a hook waterfall be used as regression test for this fix?

CaptainN commented 3 years ago

@aureooms I'm not sure what you mean, but maybe. I think there were up to 2 problems:

Another thing we should write tests for is cases where deps are used incorrectly (like someone uses an object ref which will change on every render - easy to write a test for that one). This probably wouldn't have worked in 2.2.1 because of issue #2 above, but we should still not allow the hook to fail in these cases. It might even be possible to detect this in some way during development, and kick up a helpful warning, similar to that useEffect warning.

make-github-pseudonymous-again commented 3 years ago

I'm not sure what you mean

@CaptainN What utterance are you referring to?

CaptainN commented 3 years ago

I'm not sure what you mean

@CaptainN What utterance are you referring to?

I was referring to this:

Could a hook waterfall be used as regression test for this fix?

make-github-pseudonymous-again commented 3 years ago

@CaptainN Something like the following

const useStuff = (stuff) => {
    const things = useThings(stuff);
    const foo = useFoo(things);
    const bar = useBar(foo);
    // ...
};