v2.0.0 Migration Guide

This update introduces significant improvements to the way models and views interact. We focused on automatically updating the view side whenever the model changes, simplifying the overall workflow. Additionally, new features have been added to enhance the developer experience, such as dynamically connecting to new sessions and accessing a list of connected views.

To see these changes in action, check out the following example repositories:

  1. Counter
  2. Music Box
  3. Mondrian

Updating existing code

Model side

  1. Your model should inherit from ReactModel

Update your existing models to inherit from the ReactModel class. The ReactModel class is a new base class introduced in this update, which allows the view side to be automatically updated whenever the model changes.

import { ReactModel } from "@croquet/react";

class CounterModel extends ReactModel {
  // Your model logic goes here
}
  1. No need to publish output events

If your model events were exclusively published to update the view side, you may drop them. The ReactModel class automatically handles the updates to the view side.

class CounterModel extends ReactModel {
  resetCounter() {
    this.count = 0;
    
    // Remove the line below:
    // this.publish(this.id, "count");
  }
}
  1. Override the handleViewJoin and handleViewExit methods

Subscribing directly to view-join and view-exit events is not supported in this version. Instead, you should override the handleViewJoin and handleViewExit methods.

class YourModel extends ReactModel {
  init(option) {
    super.init(option)

    // Remove the lines below:
    // this.subscribe(this.id, "view-join", this.handleViewJoin);
    // this.subscribe(this.id, "view-exit", this.handleViewExit);

  }

  // Override these methods instead
  handleViewJoin(viewId) { /* ... */ }
  handleViewExit(viewId) { /* ... */ }
}

View side

  1. Use the useReactModelRoot hook, instead of useModelRoot

In your view side, replace the useModelRoot hook with the new useReactModelRoot hook. This new hook returns a stateful object that will be automatically updated whenever the model changes.

import { useReactModelRoot } from "@croquet/react";

function CounterView() {
  const model = useReactModelRoot<CounterModel>();
}
  1. Remove state that was used to represent the model data

Since useReactModelRoot returns a stateful object, you don't need to create a React state to represent the model.

function CounterView() {
  // Remove the line below:
  // const [count, setCount] = useState(model.count);

  const count = model.count;
}
  1. You don't need to subscribe to model events anymore

The useReactModelRoot hook ensures that your view components always have access to the latest state of the model, without the need for explicit event subscriptions or manual updates. However, if your logic requires you to subscribe to specific events, this behavior is still supported.

function CounterView() {
  // Remove the line below
  // useSubscribe(model.id, "count", () => setCount(model.count), []);
}
  1. Call the model handlers instead of using usePublish

The object returned by useReactModelRoot has the same interface as the root model of your application, so you can call the model event handlers directly. Those functions publish the event that will be handled by the respective function, but it feels like you are calling the model methods directly, which is nice!

Nonetheless, using the usePublish hook is still supported.

function CounterView() {
    // Remove the line below
    // const publishReset = usePublish(() => [model.id, "reset"], []);

    // Make sure the DOM event is not passed to the method
    return (
      <div onClick={(_) => model.reset()}>
        {model.count}
      </div>
    );
}

New features

  1. Dynamically connecting to a new session

If you ever need to change the session your application is connected to, you can simply use the function returned by the useChangeSession hook. This will cause the <CroquetRoot/> component to connect to leave the current session and connect to a new one.

import { useChangeSession } from '@croquet/react'

export default function YourComponent() {
    const changeSession = useChangeSession()

    const handleConnectNewSession = (newSessionName, newSessionPassword) {
        changeSession({ name: s.name, password: s.password })
    }
}
  1. Access all the connected views with the useConnectedViews hook

We also introcuded a new hook useConnectedViews that gives you access to a list of views that are connected to the same session.

import { useConnectedViews } from '@croquet/react'

export default function YourComponent() {
    const { views, viewCount } = useConnectedViews()

    return (
        <>
            <p>Listing all {viewCount} views:</p>
            <ul>{ views.map((v) => <li>{v}</li>) }</ul>
        </>
    )
}

We made this feature opt-in, so you will need to configure it in your <CroquetRoot/> component

export default function Wrapper() {
    return (
        <CroquetRoot
            sessionParams={{
                // ... other configuration
                options: { trackViews: true }
            }}
        >
            <App/>
        </CroquetRoot>
    )
}