diff --git a/docs/extensions/guides/overview.md b/docs/extensions/guides/overview.md new file mode 100644 index 0000000000..819c992725 --- /dev/null +++ b/docs/extensions/guides/overview.md @@ -0,0 +1,3 @@ +## How to use mobx within Lens Extensions + +A short overview of mobx with examples and links to its offical documentation. Read it [here](working-with-mobx.md) diff --git a/docs/extensions/guides/working-with-mobx.md b/docs/extensions/guides/working-with-mobx.md new file mode 100644 index 0000000000..561da31a90 --- /dev/null +++ b/docs/extensions/guides/working-with-mobx.md @@ -0,0 +1,78 @@ +# Working with mobx + +## Introduction + +Lens uses `mobx` as its state manager on top of React's state management system. +This helps with having a more declarative style of managing state, as opposed to `React`'s native `setState` mechanism. +You should already have a basic understanding of how `React` handles state ([read here](https://reactjs.org/docs/faq-state.html) for more information). +However, if you do not, here is a quick overview. + +A `React.Component` is generic over both `Props` and `State` (with default empty object types). +`Props` should be considered read-only from the point of view of the component and is the mechanism for passing in "arguments" to a component. +`State` is a component's internal state and can be read by accessing the parent field `state`. +`State` **must** be updated using the `setState` parent method which merges the new data with the old state. +`React` does do some optimizations around re-rendering components after quick successions of `setState` calls. + +## How mobx works: + +`mobx` is a package that provides an abstraction over `React`'s state management. The three main concepts are: +- `observable`: data stored in the component's `state` +- `action`: a function that modifies any `observable` data +- `computed`: data that is derived from `observable` data but is not actually stored. Think of this as computing `isEmpty` vs an `observable` field called `count`. + +Further reading is available from `mobx`'s [website](https://mobx.js.org/the-gist-of-mobx.html). + +## Basic usage of mobx: + +When using `Lens`'s extension's API, some of the provided types are marked as `observable` (or others) and are documented as such. +These can be used as normal types and the combination of `mobx` and `react` work to determine when a rerender should occur. + +--- + +## Example: + +Imagine that your extension wants to conditionally add an app menu if some other observable value is ever `true` (and remove it when it is `false`). +That could be achieved using roughly the following code: + +```typescript +import { LensMainExtension, MenuRegistration } from "@k8slens/extensions"; +import observables from "./observables" // a collection of observable data + +interface MenuRegistrationWithId extends MenuRegistration { + id?: string; +} + +export default class ExtensionMain extends LensMainExtension { + appMenus: MenuRegistrationWithId[] = [ + { + parentId: "file", + label: strings.extension.appMenu["label"](), + click() { + console.log("you clicked the label menu") + }, + }, + ]; + + constructor() { + super() + + reaction( + () => observables.clusterIsInState, + clusterIsInState => { + if (clusterIsInState) { + this.appMenus.push({ + parentId: "file", + label: "Remove from current state", + click() { + console.log("currently does nothing, removing...") + }, + id: "clusterIsInState", + }) + } else { + this.appMenus = this.appMenus.filter(m => x.id !== "clusterIsInState") + } + } + ) + } +} +``` diff --git a/src/extensions/cluster-feature.ts b/src/extensions/cluster-feature.ts index 6381d267cc..3f0105c51e 100644 --- a/src/extensions/cluster-feature.ts +++ b/src/extensions/cluster-feature.ts @@ -21,6 +21,9 @@ export abstract class ClusterFeature { latestVersion: string; config: any; + /** + * @observable + */ @observable status: ClusterFeatureStatus = { currentVersion: null, installed: false, diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index af0e9d6f86..bfb1fbf615 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -26,6 +26,9 @@ export class ExtensionLoader { protected instances = observable.map(); protected readonly requestExtensionsChannel = "extensions:loaded"; + /** + * @observable + */ @observable isLoaded = false; whenLoaded = when(() => this.isLoaded);