Store refactor: use non-global stores in components (#9293)

* Add Stores and StoresContext and use it in MatrixChat and RoomView

Added a new kind of class:
- Add God object `Stores` which will hold refs to all known stores and the `MatrixClient`. This object is NOT a singleton.
- Add `StoresContext` to hold onto a ref of `Stores` for use inside components.

`StoresContext` is created via:
- Create `Stores` in `MatrixChat`, assigning the `MatrixClient` when we have one set. Currently sets the RVS to `RoomViewStore.instance`.
- Wrap `MatrixChat`s `render()` function in a `StoresContext.Provider` so it can be used anywhere.

`StoresContext` is currently only used in `RoomView` via the following changes:
- Remove the HOC, which redundantly set `mxClient` as a prop. We don't need this as `RoomView` was using the client from `this.context`.
- Change the type of context accepted from `MatrixClientContext` to `StoresContext`.
- Modify alllll the places where `this.context` is used to interact with the client and suffix `.client`.
- Modify places where we use `RoomViewStore.instance` and replace them with `this.context.roomViewStore`.

This makes `RoomView` use a non-global instance of RVS.

* Linting

* SDKContext and make client an optional constructor arg

* Move SDKContext to /src/contexts

* Inject all RVS deps

* Linting

* Remove reset calls; deep copy the INITIAL_STATE to avoid test pollution

* DI singletons used in RoomView; DI them in RoomView-test too

* Initial RoomViewStore.instance after all files are imported to avoid cyclical deps

* Lazily init stores to allow for circular dependencies

Rather than stores accepting a list of other stores in their constructors,
which doesn't work when A needs B and B needs A, make new-style stores simply
accept Stores. When a store needs another store, they access it via `Stores`
which then lazily constructs that store if it needs it. This breaks the
circular dependency at constructor time, without needing to introduce
wiring diagrams or any complex DI framework.

* Delete RoomViewStore.instance

Replaced with Stores.instance.roomViewStore

* Linting

* Move OverridableStores to test/TestStores

* Rejig how eager stores get made; don't automatically do it else tests break

* Linting

* Linting and review comments

* Fix new code to use Stores.instance

* s/Stores/SdkContextClass/g

* Update docs

* Remove unused imports

* Update src/stores/RoomViewStore.tsx

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove empty c'tor to make sonar happy

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
kegsay 2022-10-19 13:07:03 +01:00 committed by GitHub
parent 84f2974b57
commit e946674df3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 467 additions and 275 deletions

View file

@ -21,7 +21,6 @@ import classNames from 'classnames';
import { Room } from "matrix-js-sdk/src/models/room";
import LegacyCallView from "./LegacyCallView";
import { RoomViewStore } from '../../../stores/RoomViewStore';
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler';
import PersistentApp from "../elements/PersistentApp";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -34,6 +33,7 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/Activ
import WidgetStore, { IApp } from "../../../stores/WidgetStore";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { SdkContextClass } from '../../../contexts/SDKContext';
import { CallStore } from "../../../stores/CallStore";
import {
VoiceBroadcastRecording,
@ -129,7 +129,7 @@ class PipView extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
const roomId = RoomViewStore.instance.getRoomId();
const roomId = SdkContextClass.instance.roomViewStore.getRoomId();
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(roomId);
@ -147,7 +147,7 @@ class PipView extends React.Component<IProps, IState> {
public componentDidMount() {
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
RoomViewStore.instance.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
const room = MatrixClientPeg.get()?.getRoom(this.state.viewedRoomId);
if (room) {
@ -164,7 +164,7 @@ class PipView extends React.Component<IProps, IState> {
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
const cli = MatrixClientPeg.get();
cli?.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
RoomViewStore.instance.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
const room = cli?.getRoom(this.state.viewedRoomId);
if (room) {
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
@ -186,7 +186,7 @@ class PipView extends React.Component<IProps, IState> {
private onMove = () => this.movePersistedElement.current?.();
private onRoomViewStoreUpdate = () => {
const newRoomId = RoomViewStore.instance.getRoomId();
const newRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
const oldRoomId = this.state.viewedRoomId;
if (newRoomId === oldRoomId) return;
// The WidgetLayoutStore observer always tracks the currently viewed Room,