Data Fetching
Explanation
Standard way to fetch data in Child App is to use tramvai
HTTP Clients and Actions with Stores.
HTTP Client
You have a two main options to use tramvai
HTTP clients:
- Get already created clients from Root App
- Create your own HTTP client in Child App
We recommend to use first approach with already created clients from Root App, because it opens a lot of optimizations:
- Child App bundle is smaller because doesn't contain HTTP client code
- Cache for same requests will be shared between all Child Apps
- The same Child App requests will be deduplicated
HTTP_CLIENT
and all your custom HTTP clients will be available in Child App DI automatically, and you need only to use appropriate tokens to get it.
@tramvai/module-http-client
need to be connected in Root App, if we want to use HTTP_CLIENT
token in Child App
Actions
Actions is the standard way to perform any side-effects in the Child App, and works exactly the same as in Root App.
All declared in createChildApp
actions will be executed in parallel on the different stages of Child App lifecycle.
Usage
Installation
First, you need to install @tramvai/module-common
module and @tramvai/tokens-http-client
in your Child App:
npx tramvai add @tramvai/module-common
npx tramvai add @tramvai/tokens-http-client
Then, connect CommonChildAppModule
from this module in your createChildApp
function:
import { createChildApp } from '@tramvai/child-app-core';
import { CommonChildAppModule } from '@tramvai/module-common';
import { RootCmp } from './components/root';
// eslint-disable-next-line import/no-default-export
export default createChildApp({
name: 'fancy-child',
render: RootCmp,
modules: [CommonChildAppModule],
providers: [],
});
Make HTTP request
Perfect alternative to Action and State combination is awesome React Query library
⌛ At first, we need to create a new Store for request data and execution state, for example with data
, error
and loading
states:
import { createReducer, createEvent } from '@tramvai/state';
export const dataRequested = createEvent('requested');
export const dataLoaded = createEvent<Data>('loaded');
export const dataFailed = createEvent<Error>('failed');
type State = {
data: Data | null;
error: Error | null;
loading: boolean;
};
const initialState: State = { data: null, error: null, loading: false };
export const SomeDataStore = createReducer('some-data', initialState)
.on(dataRequested, (state) => ({ data: null, error: null, loading: true }))
.on(dataLoaded, (state, data) => ({ data, error: null, loading: false }))
.on(dataFailed, (state, error) => ({ data: null, error, loading: false }));
⌛ Then, we need to create a new Action with HTTP call, and save all information to our new store:
import { declareAction } from '@tramvai/core';
import { HTTP_CLIENT } from '@tramvai/tokens-http-client';
import { dataRequested, dataLoaded, dataFailed } from '../stores/some-data';
export const fetchSomeDataAction = declareAction({
name: 'fetch-some-data',
async fn() {
this.dispatch(dataRequested());
try {
const { payload } = this.deps.httpClient.get<Data>('https://api.get/some-data/');
this.dispatch(dataLoaded(payload));
} catch (error) {
this.dispatch(dataFailed(error));
}
},
deps: {
// this dependency will be resolved from Root App
httpClient: HTTP_CLIENT,
},
});
⌛ Then, action need to be declared in createChildApp
function:
import { createChildApp } from '@tramvai/child-app-core';
import { CommonChildAppModule } from '@tramvai/module-common';
import { RootCmp } from './components/root';
import { fetchSomeDataAction } from './actions/some-data';
// eslint-disable-next-line import/no-default-export
export default createChildApp({
name: 'fancy-child',
render: RootCmp,
modules: [CommonChildAppModule],
providers: [],
actions: [fetchSomeDataAction],
});
⌛ At last, we can subscribe to this data in our UI component:
import { useStore } from '@tramvai/state';
import { SomeDataStore } from '../stores/some-data';
export const RootCmp = () => {
const { data, error, loading } = useStore(SomeDataStore);
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return <div>Result: {data}</div>;
};
This action will be executed before Root App will be render our Child App. If Child App will be preloaded, in success case, this action will be executed on the server side, and user never see loading state in the browser.