React App Breaks on Browser Refresh

I have been trawling through React specific posts on trying to fix the issue where refreshing a page breaks the routing. However, I am struggling to apply any fixes to the my Meteor React app. It seems like such a common thing that requires fixing, I assume every Meteor React app developer would have to overcome it.

Here are my routes set up in my client side app.jsx:

<Switch>
          <Route path="/god/:godName" component={God} />
          <Route path="/gods" component={Gods} />
          <Route path="/" component={Home} />
        </Switch>

If I navigate to a specific god page, it works fine.

http://localhost:3000/god/Bruelin

The Bruelin parameter is picked up and the page renders perfectly.

export const God = () => {
  const { godName } = useParams();
  const god = useTracker(() => {
    Meteor.subscribe('gods');

    const godsCollection = GodsCollection.findOne({ name: godName });

    return godsCollection;
  });
  return (
    <div>
      <h2>
        Name:
        &nbsp;
        { god.name }
      </h2>
      <div>
        { Parse(god.description)}
      </div>
      <div>
        Avatar:&nbsp;
        { god.avatar }
      </div>
      <div>
        <h2>The Champions</h2>
        <ul>
          {god.champions.map((champion) => <li key={champion}>{champion}</li>)}
        </ul>
      </div>
    </div>
  );
};

However, if I refresh this page, or navigate to it directly, the page breaks as the useParams() returns null.

There are various React specific topics on this recommended using Browser History and setting up routes both on the client and server side. However, I am struggling trying to implement the solutions in the context of a Meteor app.

Any help would be appreciated.

Does useParams() return null all the time or it just return null on first render?

Maybe modify the tracker dependency could help:

const god = useTracker(() => {
    Meteor.subscribe('gods');

    const godsCollection = GodsCollection.findOne({ name: godName });

    return godsCollection;
  }, [godName]);

[/quote]

It works whenever I navigate to the page, however, anytime I refresh the page, or the the URL directly, it is null.

I will try your suggestion and report back.

The addition of storing [godname] in useTracker did not help, unfortunately.

Have you put a breakpoint or a console.log() call in the router yet to see exactly what route the Router thinks it is seeing?

Ok, so, it seems that useParams() is actually always returning the value, however, the GodsCollection becomes null whenever I refresh the page. If I actually navigate to the page from navigation, it works.

export const God = () => {
  const { godName } = useParams();
  console.log('godName=' + godName);
  const god = useTracker(() => {
    Meteor.subscribe('gods');

    const godsCollection = GodsCollection.findOne({ name: godName });

    return godsCollection;
  });

I don’t use MiniMongo – I’m using Apollo – but is there a Meteor or MiniMongo Chrome plug-in you can use to get visibility into what’s going on with the MiniMongo cache?

I use this one: Meteor DevTools Evolved - Chrome Web Store
and this: Meteor MiniMongo Explorer - Chrome Web Store

Maybe you have other pages which subscribes to ‘gods’ collection already.
The god variable returned by useTracker could be null at first. Because the subscription needs time to finish. So your component should check if it’s ready or not.
If you tried to access the god.name immediately, it could lead to error. You can check if god available or try to use god?.name instead.

1 Like

I have the Chrome plugin and just checked it. The collection is zero when I refresh (has 6 records previously), so, that is definitely the issue.

This is definitely the issue. Do you know how I can make the page wait for the collection before trying to reference it?

Try this:

const { god, loading } = useTracker(() => {
    const sub = Meteor.subscribe('gods');
    return {
     god: GodsCollection.findOne({ name: godName }),
     loading: !sub.ready(),
    };
  });

If the loading is true then it’s not ready.

1 Like

Apollo has a loading state also. I handle that like this:

React has a Suspense function that may be better than this by now. :slight_smile:

1 Like

Thank you very much. This worked!

I really appreciate your time and input.

2 Likes

For completeness, this is what the completed (working) code looks like:

export const God = () => {
  const { godName } = useParams();
  const { god, loading } = useTracker(() => {
    const sub = Meteor.subscribe('gods');
    return {
      god: GodsCollection.findOne({ name: godName }),
      loading: !sub.ready(),
    };
  });
  return (
    <div>
      { loading ? (
        <div>loading...</div>
      ) : (
        <div>
          <h2>
            Name:
            &nbsp;
            { god.name }
          </h2>
          <div>
            { Parse(god.description) }
          </div>
          <div>
            Avatar:&nbsp;
            { god.avatar }
          </div>
          <div>
            <h2>The Champions</h2>
            <ul>
              { god.champions.map((champion) => <li key={champion}>{champion}</li>) }
            </ul>
          </div>
        </div>
      )}
    </div>
  );
};