React Hooks - state and lifecycle methods without a class (React 16.7.0-alpha)

@rjdavid: https://reactjs.org/blog/2018/03/29/react-v-16-3.html#component-lifecycle-changes

@ralpheiligan: not sure I undestrand your question ?

@yched, nowhere in the blog post that mentioned that componentDidMount will be deprecated. Are you referring to other lifecycle methods?

Doh ! Sorry, I meant componentWillMount, it’s componentWillMount / WillUpdate that get deprecated.
I edited my post above with the correct method names… Sorry for the confusion :-/

i like it, but i see a potential concurrency problem on SSR with meteor, see https://github.com/meteor/meteor/issues/10279

how i understand the hooks is, that they store some caller–>hook dependency. So on every render call of a component, they set some global state to the current calling component, so that every hook/effect call knows which component it belongs to.

this work fine, with normal javascript environments, as long as you don’t use asynchronous callbacks (like setTimeout)

on meteor servers, however, you have fibers. and fibers can stop/yield execution of code on certain points (like on a Collection.findOne() call).

This could potentioally break hooks/effects in a similar way like it breaks React.createContext (see link above) when doing server side rendering.

its probably not a big problem with more recent apps that use apollo, where the whole <App /> has no fiber-yields, but it can be, if you use pub/sub.

I don’t know the exact implementation, so this is only guessing. Maybe there will be also a fix involving Fiber.current like i did for the context api (https://github.com/panter/meteor-fiber-save-react-context)

1 Like

@macrozone

At this stage of the alpha hooks are not supported on the server yet, so that’s hard to say for sure.

For the specific case of “provide reactive Meteor data to React components through a hook”, though, I’d say it’s likely not going to be a problem : the current implementation of the withTracker(reactiveFun) HOC basically just does if (Meteor.isServer) return reactiveFun(props), and a useTrackerHook(reactiveFun) will probably do the same (that’s what my PR linked above does, for example). So if you had no problem running the withTracker HOC on the server, I don’t think a useTracker hook will cause more issues.

Not sure how the other hooks used by your components will behave on SSR, since that’s not implemented yet in React.

Tracker is not the problem, but yielding functions are.

renderToString(<App />) is no longer synchronous if you use e.g. Collection.find() in one of your components, so another call to renderToString(<App />) might happen before the first one has completed.

if React relies on some globals to keep references for hooks (like they use globals to keep the current value in React.createContext, we will get into troubles, because these references might change because of a concurrent rendering

2 Likes

Sure, what I’m saying is : There are actually no hooks involved on the server. The current implementation of the useTracker hook does nothing more than the withTracker HOC when Meteor.isServer is true : it just runs the callback you give it, once, non-reactively, and not involving any react hooks.

So if your code has no problem currently with withTracker(callback_that_yields) on the server (and I assume that’s the case ?), then it shouldn’t have problems with useTracker(callback_that_yields) either.

Maybe the issue you mention will appear with components using other hooks (useState, useEffect…) directly, but not with useTracker. Definitely worth keeping an eye on, but not really related to the existence of a useTracker hook :slight_smile:

aaah, yes. i referred to the topic here, to react-hooks :wink:

@macrozone right, sorry, I forgot that this thread was initially about “react hooks” in general, and that I later kind of hijacked it with “hey, we can probably have a hook for Tracker data” :slight_smile:

Apparently React 16.6.3 contains a couple fixes around context and SSR. Dunno if they affect the issues your seeing with yields and Fibers ? (or your fix package, for that matter…)

yes, i saw that as well and did a first attemp to upgrade to react 16.6.3. First result is, that SSR is now even more broken then before with meteor :wink:

Yawn. I’m not against incremental improvements. At the same time, this is another one of those changes that only the react maintainers and language purists care about. In the grand scheme of building digital products, whether your declare you component with the word class or not isn’t going to move the needle.

Hooks is more than not needing class components. Hooks provide a smaller building block than a component, as its been. Meaning, as React developers, we can now share and npm install more code than ever before. Think about the state of the community when there is a hook for everything.

Need list filtering/selecting?

 const {toggleOne, isSelected, selectAll } = useList(items)

etc…

2 Likes

Exactly, you can fine some hooks on npm already and the platform is an interesting one.

So essentially it provides a more systematic API to share/re-use more granular code, which is cool.

1 Like

I didn’t see that aspect highlighted yet. Sounds powerful now.

@yched Can you please give an example using useTracker with Meteor.subscribe code?

@a.com something like:

function MyComp(props) {
  const doc = useTracker(props => {
    Meteor.subscribe('my_subscription', props.var);
    return MyCollection.findOne(props.id);
  }, [props.var, props.id]);
  return <div>{doc ? doc.title : 'No document found'}</div>;
}

You can also return an object with several values (like you currently do with the withTracker HOC), for instance if you also want the ‘ready’ status of the subscription:

function MyComp(props) {
 const { ready, doc } = useTracker(props => {
   const sub = Meteor.subscribe('my_subscription', props.var);
   return { 
     ready: sub.ready(), 
     doc: MyCollection.findOne(props.id)
   };
 }, [props.var, props.id]);
 return ready ? <div>{doc ? doc.title : 'No document found'}</div> : <Spinner />;  
}

I’d tend to think a better practice would be to split independant results each into their own useTracker calls, since each reactive func then gets recomputed on its own only when necessary:

function MyComp(props) {
 const ready = useTracker(props => {
   const sub = Meteor.subscribe('my_subscription', props.var);
   return sub.ready();
 }, [props.var]);
 const doc = useTracker(props => MyCollection.findOne(props.id), [props.id]);
 return ready ? <div>{doc ? doc.title : 'No document found'}</div> : <Spinner />;  
}
1 Like

I have just added this to local package directory in a medium sized app using withTracker extensively as a package override for react-meteor-data, and in my not too extensive testing I can confirm, it works pretty well! I’m going to actually deploy this to our testing server and see how it goes.

Is there a Pull Request for this yet? (Er, nm, I found it.)

I’m kind of digging hooks. I made this quick useSubscription hook on top of this:

// bad code, doesnt work
// export const useSubscription = (name, ...rest) => {
//   const [isReady, setIsReady] = useState(false)
//   useTracker(() => {
//     const handler = Meteor.subscribe(name, ...rest)
//     setIsReady(handler.ready())
//     return () => { handler.stop() }
//   }, [name, ...rest])
//   return isReady
// }

/// elsewhere
// const isReady = useSubscription('my-pub', 'arg1', 'arg2')

Pretty spiffy. This is actually untested, but I’ve found it fairly easy to express various hooks, and to do so with much less code than using the old container method. Now I just have to figure out how to make a useMeteorState wrapped around ReactiveVar, so that we can have persistent local state, which survives hot code push!

UPDATE: This code was incorrect. I mixed up the syntax (useTracker’s API is a mix of useSession and useEffect), and made it work like it would if useTracker was like useEffect - but it’s not. Here is a (simpler) corrected version:

export const useSubscription = (name, ...rest) => useTracker(
  () => Meteor.subscribe(name, ...rest).ready(),
  [name, ...rest]
)

/// elsewhere
const isReady = useSubscription('my-pub', 'arg1', 'arg2')
4 Likes

looking forward to it