[Bug]: live-share-react: "Cannot call changeState when liveState is undefined"
nmtoblum opened this issue · comments
Dear live-share-sdk team,
I've been using the library a lot in the last few weeks and wanted to say thank you for your work. With it you can build really great apps that are a lot of fun. I'm still struggling with some of the implementation details with React, so I wanted to try live-share-react, but I haven't had any success yet.
- I have reviewed the FAQ and known issues and did not find my topic
Describe the bug
I built a minimal example (based on vite+react+ts) based on your 04.live-share-react example. When I try to work with LiveState objects I get the error message: Cannot call changeState when liveState is undefined
This happens everytime I try to manipulate the LiveState.
Often but not always I get different error messages on initialization (happens before working with the shared objects).
provided user was not an "AzureUser"
- Warning:
Error in processing request : interactive.getClientInfo with error Unhandled message
- Or something like this:
To Reproduce
I have published the minimal code and manifest used here: https://github.com/toblum/spotalot/tree/live-share-react
Expected behavior
Should be able to change the LiveState
Screenshots
see above
Desktop(s) (please complete the following information):
- OS: Mac OS / Win 11
- Browser: Chrome
- Version: Latest
I would be great to get an idea what is going wrong here in my example.
Best regards
Tobias
One thing I wanted to add: To get a better understanding what happens here and to reduce complexity, I took another step back and tried to implement a similar minimal solution with live-share-turbo. It's in this branch: https://github.com/toblum/spotalot/tree/live-share-turbo
When running this code I always get this error message:
This happens every time when this line is executed: https://github.com/toblum/spotalot/blob/c95e8e6de9b6c2ce7a5382711f186703ee98e8d7/client/src/pages/SidePanel.tsx#L19
I think that could be related as I saw this error also when using live-share-react.
Greetings
Tobias
And I even went back one more step and tried the same as vanilla live-share:
https://github.com/toblum/spotalot/tree/minimal
Here I always get the error: Error: Registry does not contain entry for the package
Now I'm at the end of my rope. I have no idea what I did wrong here.
Would be great if you could give me a hint what's happening here.
Greetings
Tobias
Hey @nmtoblum, thanks for reporting this! Thanks for linking to your test project, I will see if I can repro on my machine and get back to you ASAP.
One thing I noticed, which we have done a poor job at documenting...@fluidframework/azure-client
version 1.1 or greater have an issue right now because of a breaking change they introduced that we only recently caught. If you are testing in Teams, that would probably line up with the AzureUser issue you were seeing.
We have a server-side patch coming next week, but in the meantime if you change the @fluidframework/azure-client
version to "~1.0.2" that should at least fix that issue.
As for this one:
And I even went back one more step and tried the same as vanilla live-share:
https://github.com/toblum/spotalot/tree/minimalHere I always get the error: Error: Registry does not contain entry for the package
This is probably because you were testing in the same meeting as before. This is something Live Share Turbo / React are meant to help. Because the container was created using the Live Share Turbo structure, that means the schema you defined for the base client mismatched that of what is used in Turbo/React. The base Live Share package doesn't yet support migrating schema, but to fix you can just create a new meeting and test from there.
Let me know if this helps!
Also this isn't the issue, but I noticed the following:
const { created } = useLiveShareContext();
const host = TestLiveShareHost.create();
return (
<div>
SidePanel
<LiveShareProvider joinOnLoad host={host}>
LSP
{created}
<GameSettings></GameSettings>
</LiveShareProvider>
</div>
);
First issue, useLiveShareContext();
should only be accessed from within a child component. I would nest another component within SidePanel.tsx
, because otherwise that will always be undefined. Also, created
is referring to whether the local user was the one to create the container.
The second is regarding the host
. Generally, you should either wrap those in a useState
or even define it outside of the component. This will ensure it doesn't re-create the object on every render.
I would recommend something closer to the following:
import { LiveShareProvider, useFluidObjectsContext } from "@microsoft/live-share-react";
import { LiveShareHost } from "@microsoft/live-share";
import { useState } from "react";
const SidePanel = () => {
// teams-js must be initialize before LiveShareHost.create() will work
const [host] = useState(LiveShareHost.create());
return (
<LiveShareProvider host={host} joinOnLoad>
<SidePanelRenderer />
</LiveShareProvider>
)
}
const SidePanelRenderer = () => {
const { container } = useFluidObjectsContext();
if (!container) return (<Spinner />);
return (<GameSettings />);
}
Hi Ryan,
thanks for your quick reply and for taking the time to investigate my problem.
The note about the version of @fluidframework/azure-client
was spot on. That solved a lot of things already:
- minimal: The error
Registry does not contain entry for the package
is gone and I think I could build a vanilla live-share solution now. I have not tested further yet. - live-share-turbo: This solution works now too.
- live-share-react: With your code example it works now too. I probably oversimplified the code a bit during my tests. :-)
At first, however, no variant worked and the error messages were confusing, but I drilled a little deeper and I think I understood why my tests over the last few days gave such confusing results:
I always tested in teams meetings. Today, I tested the "minimal" example first and tried it successfully (that registered an exampleMap in the Fluid container). Then I tried the live-share-turbo solution in the same meeting. That then failed with an error message that looked like this:
Then I tried live-share-react example and got this error message:
I made sure that the container exists before, so I couldn't explain why it's undefined. After logging the container I saw it:
container is available, initial objects: {exampleMap: SharedMap}
The container already existed in the meeting (with the exampleMap from the minimal example I started earlier). So the live-share-turbo client in live-share-react took this existing container, but could not create it's internal TURBO_STATE_MAP object, but assumes that it's already there then breaks with the error above.
So I asked myself how this situation could be handled in Teams where maybe more then one live-share-* app is started in the same meeting. The first will win? But how should the second app handle this?
It would be nice if you could give me some guidance on this. Many thanks in advance for your support.
Many greetings
Tobias
Hey @nmtoblum, yeah this is great feedback, thank you for being so thorough! We have some work to do in this area for sure. While we are formally releasing @microsoft/live-share, -media, and -canvas to v1.0.0 at Build in a couple weeks, the React & Turbo packages will stay in Preview. So we're still working some of this out, but I will make a note to ensure that the error messages be more consistent across these three paradigms.
What you are touching on here relates to how Fluid Framework works. Basically, anything that is in initialObjects
from your ContainerSchema
is created before the IFluidContainer
is created, and currently they don't support changing those initialObjects
after the container is created.
So I asked myself how this situation could be handled in Teams where maybe more then one live-share-* app is started in the same meeting. The first will win? But how should the second app handle this?
These containers are stored in our server for about 6 hours after you first install the app to a meeting, before we delete them. Each unique application in Teams gets one container per meeting, meaning that apps do not get access to any other container you have already created.
This is determined by two things, which we do primarily for security reasons and to avoid the issue which you mentioned above:
- Your application's unique
id
, as determined in your app manifest - The origin URL of your application
That means that even if an app has the same identifier as you, if your origin is different then you will have two separate containers for a single meeting. You won't have any conflicts with other Teams apps, just your own while you are testing a different app schema within the same 6 hour window.
This gives you two ways to "reset" your container while you're still flushing out your app & schema:
- Simply create a new meeting and sideload it there. Meet Now meetings are the easiest way for this, generally.
- Change one character of your "id" in your app manifest and reinstall. Obviously you want a static app ID for your production app, but there is nothing stopping you from changing it as many times as you'd like during development. Usually we just do a new meeting though, since zipping the manifest and uploading the new app package is a bit clunky.
RE: other stuff mentioned
Containers created by LiveShareTurboClient
(and consequentially live-share-react) are not compatible with that from the regular LiveShareClient
. This is because Live Share Turbo actually abstracts away the schema so you don't have this issue. LiveShareTurboClient
uses a single initialObjects
entry called DynamicObjectsManager
, which is what you're seeing in that first error message. It is the same as if you did this in the Live Share client:
import { LiveShareClient, TestLiveShareHost } from "@microsoft/live-share";
import { DynamicObjectsManager } from "@microsoft/live-share-turbo";
const schema = {
initialObjects: {
TURBO_STATE_MAP: DynamicObjectsManager,
},
dynamicObjectTypes: [...every DDS possible in your app],
}
We might change this in a future update -- we are talking with the Fluid Framework team about how we could create the TURBO_STATE_MAP
without depending on initialObjects
at all (effectively doing what they do under the hood w/ initialObjects, rather than adding an additional abstraction). However, it does mean that you can get some portability between live-share-react
and live-share-turbo
, as long as you are using the same keys for each DDS.
For example, if you use this in React:
import { useLiveState } from "@microsoft/live-share-react";
const FOO_KEY= "foo";
export const SomeComponent = () => {
const [foo, setFoo] = useLiveState<string>(FOO_KEY, "bar");
return (
<div>
{foo}
</div>
);
}
That is the same as this in live-share-turbo
:
import { LiveShareTurboClient } from "@microsoft/live-share-turbo";
import { LiveState, TestLiveShareHost } from "@microsoft/live-share";
const FOO_KEY= "foo";
const host = TestLiveShareHost.create();
const client = new LiveShareTurboClient(host);
await client.join();
const fooState = await client.getDDS(FOO_KEY, LiveState);
fooState.on("stateChanged", (latest) => {
// update app
});
await fooState.initialize("bar");
Technically you could make the LiveShareClient
work with Turbo client as well, if you were really motivated, though at that point you might as well just use LiveShareTurboClient
.
Hope this helps! Happy to answer more questions, and definitely keep sharing candid feedback, it's super helpful!
Hello Ryan,
thank you for all the information. It all helps me a lot to understand how it works. I'm also glad to hear that (vanilla) live-share will be 1.0 soon. Then with that we can build solutions that can be used productively later on.
Thanks also for the information on how new containers are created. If a new container is created per application ID and meeting, then no problems are to be expected and the observed irregularities can only be attributed to my unsuitable development setup. Now I simply create a new meeting more often. That makes it less complicated.
Otherwise, as soon as possible, I will just work a bit more with live-share(-react) and also onboard my colleagues. I have a few ideas for apps. If I run into any problems or questions along the way, I won't hesitate to post them again. Thanks again for the super helpful support.
Many greetings
Tobias