Anders Kitson
Anders Kitson

Reputation: 1545

Meteor, useTracker mongo.collection.findOne returns undefined, then correct value

I am getting multiple return values for a Mongo.collection.findOne query by ID, in Meteor JS and React. I think it has to do with the fact that it is rerendering, I have tried to use useEffect but it does not work the way I implemented it, which is just wrapping the lead variable in the useEffect.

Here is my code

import React, { useState, useEffect } from "react";
import Dasboard from "./Dashboard";
import { Container } from "../styles/Main";
import { LeadsCollection } from "../../api/LeadsCollection";
import { Lead } from "../leads/Lead";
import { useTracker } from "meteor/react-meteor-data";

const Walkin = ({ params }) => {
  const [email, setEmail] = useState("");
  const [testIdA, setTestId] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
  };

  const lead = useTracker(() => LeadsCollection.findOne({ _id: params._id }));

  console.log(lead);

  console.log(params._id);

  const deleteLead = ({ _id }) => LeadsCollection.remove(_id);

  return (
    <Container>
      <Dasboard />
      <main className="split">
        <div>
          <h1>Add a lead below</h1>
          <form className="lead-form" onSubmit={handleSubmit}>
            {/* <input
              type="text"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="Type to add new lead"
            /> */}

            <button type="submit">Edit Lead</button>
          </form>
        </div>
        <p>{params._id}</p>
        {/* <Lead key={params._id} lead={lead} onDeleteClick={deleteLead} /> */}
      </main>
    </Container>
  );
};

export default Walkin;

There are three rerenders and this is the result

undefined
Walkin.jsx:20 sLg7dk3KdS7boZuHB
Walkin.jsx:18 undefined
Walkin.jsx:20 sLg7dk3KdS7boZuHB
Walkin.jsx:18 {_id: "sLg7dk3KdS7boZuHB", email: "fasfdas", createdAt: Wed Nov 11 2020 21:40:39 GMT-0700 (Mountain Standard Time)}
Walkin.jsx:20 sLg7dk3KdS7boZuHB

Thanks ahead of time

Upvotes: 1

Views: 558

Answers (2)

Mikkel
Mikkel

Reputation: 7777

The behaviour you are seeing is normal for Meteor subscriptions

https://dweldon.silvrback.com/common-mistakes

Subscribe works like a garden hose - you turn it on and, after a while, things come out the other end. Activating the subscription does not block execution of the browser, therefore an immediate find on the collection won't return any data.

This is a bit annoying to code, because your component needs to allow for no data (first time around). This means any propTypes rules on your components will fail. The technique I use is to insert a "Loading" component in between the data and the component itself.

Below is the code for the container component.

const Loading = (props) => {
  if (props.loading) return <div>Loading...</div>
  return <Launch {...props}></Launch>
}

const Launcher = withTracker((props) => {
  const subsHandle = Meteor.subscribe('list.workflows')
  const methods = { launchJob }
  return {
    workflows: Workflows.find({}).fetch(),
    methods,
    loading: !subsHandle.ready(),
  }
})(Loading)

export default Launcher

The container (<Launcher>) is responsible for fetching data and doing any Meteor interactions like method calls. The <Launch> component is responsible for rendering only. It can have strict propType rules to help diagnose missing data, and can also be rendered by Storybook and any testing tools (as there is no Meteor dependency in it). This also makes the component more portable (ie it could be used in a non Meteor project)

Upvotes: 1

Christian Fritz
Christian Fritz

Reputation: 21364

As JanK. already pointed out, you will want to do the subscription manually. When you do that you will get a .ready function you can use to tell whether the data is available yet on the client, and show a loading page until then.

Here is an example of how that typically looks in practice:

const Walkin = ({ params }) => {

  const {lead, ready} = useTracker(() => {
    const subscription = Meteor.subscribe('leads', params._id);
    return {
      lead: LeadsCollection.findOne({ _id: params._id })),
      ready: subscription.ready()
    };
  }, [params._id]);

  if (!ready) {
    return <div>loading</div>
  }

  return <div>
    The actual page where lead is used.
  </div>;
}

Upvotes: 3

Related Questions