Skip to main content

actions-conditions

For each action, you can specify the execution conditions, this mechanism is described in detail on the Action page. By default, a global action is executed once on the server for each user request, if the action does not have time to complete within a certain time, then its execution is transferred to the client.

Using preset limits

Let's say we want to execute one action only on the server, and one only on the client, for this there are onlyServer and onlyBrowser restrictions:

Create actions

import { declareAction } from '@tramvai/core';
import { set } from '../store';

export const innerAction = declareAction({
name: 'innerAction',
fn() {
console.log('execute innerAction');
return this.dispatch(set('innerAction'));
},
// conditions not set - the action will be executed by default:
// if the action is defined as global (in an application, bundle or page),
// then the action will first try to execute on the server - if it succeeds, then it will not be executed again on the client
// if the action did not manage to be executed within the limit, then it will be launched on the client,
// repeated launches when switching to the page with this action will not be executed, because the last successful execution is remembered
// if the action is called explicitly through the context, then such an action will always be executed (the execution limit still affects,
// and the data may not always be available with ssr, but the action will always try to execute)
});

export const innerServerAction = declareAction({
name: 'innerServerAction',
fn() {
console.log('execute innerServerAction');
return this.dispatch(set('innerServerAction'));
},
// this action can only be executed on the server
conditions: {
onlyServer: true,
},
});

export const innerBrowserAction = declareAction({
name: 'innerBrowserAction',
fn() {
console.log('execute innerBrowserAction');
return this.dispatch(set('innerBrowserAction'));
},
// this action can only be executed on the browser
conditions: {
onlyBrowser: true,
},
});

Use actions

import { declareAction } from '@tramvai/core';
import { set } from '../store';
import { innerAction, innerBrowserAction, innerServerAction } from './inner';

export const pageServerAction = declareAction({
name: 'pageServerAction',
async fn() {
console.log('execute pageServerAction');
await this.executeAction(innerAction);
await this.executeAction(innerServerAction);
await this.executeAction(innerBrowserAction);
return this.dispatch(set('pageServerAction'));
},
conditions: {
pageServer: true,
},
});

export const pageBrowserAction = declareAction({
name: 'pageBrowserAction',
async fn() {
console.log('execute pageBrowserAction');
await this.executeAction(innerAction);
await this.executeAction(innerServerAction);
await this.executeAction(innerBrowserAction);
return this.dispatch(set('pageBrowserAction'));
},
// this action can only be executed on the browser
conditions: {
onlyBrowser: true,
},
});

export const pageAlwaysAction = declareAction({
name: 'pageAlwaysAction',
async fn() {
console.log('execute pageAlwaysAction');
return this.dispatch(set('pageAlwaysAction'));
},
conditions: {
always: true,
},
});

// this action is only executed in the browser, on page load and on every SPA transition
export const pageBrowserAlwaysAction = declareAction({
name: 'pageBrowserAlwaysAction',
async fn() {
console.log('execute pageBrowserAlwaysAction');
return this.dispatch(set('pageBrowserAlwaysAction'));
},
conditions: {
always: true,
onlyBrowser: true,
},
});

Create your own restrictions

To do this, you need to implement the ActionCondition interface, and add a new limiter to the DI, via the ACTION_CONDITIONALS token:

Create a delimiter

import { ActionCondition } from '@tramvai/module-common';
import { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';

export const condition = ({
pageService,
}: {
pageService: typeof PAGE_SERVICE_TOKEN;
}): ActionCondition => {
return {
key: 'custom',
fn: (checker) => {
if (checker.conditions.custom) {
const { pathname } = pageService.getCurrentUrl();
console.log(pathname);
if (pathname !== '/custom/') {
checker.forbid();
}
}
},
};
};

Create an action with this constraint

import { declareAction } from '@tramvai/core';
import { set } from '../store';

export const customAction = declareAction({
name: 'customAction',
fn() {
console.log('execute customAction');
return this.dispatch(set('customAction'));
},
// you can set your own options, which are then used in their conditions checks
conditions: {
custom: true,
},
});

Execute actions on specific pages only

To do this, we use the static property actions for the component that is used on these pages:

Page component

import reduceObj from '@tinkoff/utils/object/reduce';
import React from 'react';
import { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
import { useSelector } from '@tramvai/state';
import { useDi } from '@tramvai/react';
import { store } from '../store';
import {
pageBrowserAction,
pageServerAction,
pageAlwaysAction,
pageBrowserAlwaysAction,
} from '../actions/page';
import { customAction } from '../actions/custom';

export function Page() {
const state = useSelector(store, (x) => x.actionTest);
const pageService = useDi(PAGE_SERVICE_TOKEN);

return (
<div>
<button type="button" onClick={() => pageService.navigate({ url: '/custom/' })}>
Navigate To custom
</button>
{reduceObj(
(acc, v, k) => {
return acc.concat(
<div>
{k} loaded from {v}
</div>
);
},
[],
state
)}
</div>
);
}

Page.actions = [
pageServerAction,
pageBrowserAction,
pageAlwaysAction,
pageBrowserAlwaysAction,
customAction,
];

Connecting actions and restrictions in the application

Let's create an application that connects the actions, constraints, and components from the previous examples:

Application entry point

import { createApp, createBundle, provide } from '@tramvai/core';
import { ACTION_CONDITIONALS } from '@tramvai/module-common';
import { PAGE_SERVICE_TOKEN, ROUTES_TOKEN } from '@tramvai/tokens-router';

import { store } from './store';
import { modules } from '../common';
import { condition } from './conditions/custom';
import { Page } from './components/Page';

const bundle = createBundle({
name: 'mainDefault',
components: {
pageDefault: Page,
},
reducers: [store],
});

createApp({
name: 'actions-conditions',
modules: [...modules],
providers: [
provide({
provide: ACTION_CONDITIONALS,
multi: true,
useFactory: condition,
deps: {
pageService: PAGE_SERVICE_TOKEN,
},
}),
provide({
provide: ROUTES_TOKEN,
multi: true,
useValue: {
name: 'custom',
path: '/custom/',
},
}),
],
bundles: {
mainDefault: () => Promise.resolve({ default: bundle }),
},
});