decred / politeiagui

ISC License
62 stars 56 forks source link

feat(core): add politeiagui toolkit #2869

Closed victorgcramos closed 1 year ago

victorgcramos commented 1 year ago

This PR adds the new politeiagui core toolkit, which exports the createSliceService util.

Motivation

After defining some plugins services and their regarded setups for app routes, I noticed that it's pretty hard to work with our previous setup flow due to the following reasons:

  1. There are lots of params property names definitions to keep in mind, and it's easy to forget some name. Our error handler already gives us some good dev feedback, but it's still hard to remember the configuration flow.

  2. If we don't use any util to create slices services, it becomes hard to maintain our standards between services configuration for plugins, and services consumers.

  3. Setting up a service requires devs to know all services ids in order to consume them on app routes. Passing an invalid id as param throws an error, but it's still hard to tell which service id is valid or not without looking at each service configuration.

  4. Lack of services slice configuration docs.

Solution

The createSliceService util formats our services to include on our plugin and to use them externally by setting up their services listeners.

Each service is composed by an id, used to identify the service among our plugins and apps, an onSetup function to be executed when our service is setup, and an effect that will be executed for each Service Listener.

Example:

Configure slice services:

// myplugin/myslice/services.js
import { createSliceServices } from "@politeiagui/core/toolkit";
import { createAction } from "@reduxjs/toolkit";

const services = createSliceServices({
  name: "my/test",
  services: {
    // `foo` is the service id
    foo: {
      effect: async (state, dispatch, payload) => {
        // Do something here to be executed on listener match
      },
      onSetup: () => {
        // Do something here to be executed when listener is initialized
      },
    },
  },
});

// Action to listen to.
const myAction = createAction("myAction")

export const { pluginServices, setup } = services;

Use on Plugin:

// myplugin/plugin.js
import { pluginServices } from "./myslice/services"

const plugin = pluginSetup({
  name: "playground",
  reducers: [],
  services: pluginServices,
});

Consume services on app route:

// myapp/routes.js
import { setup } from "@politeiagui/myplugin/myslice"
import App from "./app"

const { foo } = setup;

// setup foo without listeners. This return the serviceId, and once it's
// connected to our app, the `onSetup` action will be executed.
const fooWithoutListeners = foo;

// setup foo without any effect customization. This will execute the effect
// using the listened action payload as argument. Works for any Redux Toolkit
// listener action matcher.
const fooWithoutEffectCustomizationType = foo.listenTo({ type: "myAction" });
// or
const fooWithoutEffectCustomizationActionCreator = foo.listenTo({
  actionCreator: myAction
});

// setup foo with custom effect. This allows service's effect parameters
// customization, as well as the addition of a new listener handler.
const fooCustomized = foo
  .listenTo({ type: "myAction" })
  .customizeEffect((effect, { payload }, { dispatch, getState }) => {
    effect(getState(), dispatch, { token: payload });
  });

// Add on route
App.createRoute({
  path: "/",
  setupServices: [
    fooWithoutListeners,
    fooWithoutEffectCustomizationType,
    fooWithoutEffectCustomizationActionCreator,
    fooCustomized
  ]
})
victorgcramos commented 1 year ago

@tiagoalvesdulce Nice, I'll remove toolkit playground app. I used it as an example for this PR. Once I add the toolkit on pi app-shell, I'll update the description with the motivation. Thanks for the review 😉