Skip to content

Dynamic components

The most suitable demonstration is an example with a Layout component that changes the app layout and overall design depending on the current page.

When using reactive approaches, there are three main ways to work with dynamic components:

  1. Outside the Router component, using router.activeName
tsx
import { LayoutLogin } from 'layouts/LayoutLogin'
import { LayoutAuthZone } from 'layouts/LayoutAuthZone'

function App() {
  const { router } = useRouter();
  
  const Layout = ['login', 'restore'].includes(router.activeName!) 
    ? LayoutLogin 
    : LayoutAuthZone;
  
  return (
    <Layout>
      <Router router={router} />
    </Layout>
  );
}
tsx
import { LayoutLogin } from 'layouts/LayoutLogin'
import { LayoutAuthZone } from 'layouts/LayoutAuthZone'

function App() {
  const { router } = useRouter();

  const Layout = ['login', 'restore'].includes(router.activeName!)
    ? LayoutLogin
    : LayoutAuthZone;
  
  return (
    <Layout>
      <Router router={router} />
    </Layout>
  );
}
tsx
import { LayoutLogin } from 'layouts/LayoutLogin'
import { LayoutAuthZone } from 'layouts/LayoutAuthZone'

function App() {
  const { router } = useRouter();

  return (
    <Dynamic 
      component={['login', 'restore'].includes(router.activeName!) 
        ? LayoutLogin 
        : LayoutAuthZone
      }
    >
      <Router router={router} />
    </Dynamic>
  );
}
vue
<script lang="ts" setup>
  import { computed } from 'vue';
  import { useRouter } from '../../router';
  
  import LayoutLogin from 'layouts/LayoutLogin.vue'
  import LayoutAuthZone from 'layouts/LayoutAuthZone.vue'

  const { router } = useRouter();

  const Layout = computed(() => 
    ['login', 'restore'].includes(router.activeName!) 
      ? LayoutLogin 
      : LayoutAuthZone
  );
</script>

<template>
  <component :is="Layout">
    <Router :router="router" />
  </component>
</template>
  1. Inside the page component, reacting to dynamic parameters
ts
createConfigs({
  dashboard: {
    path: '/dashboard/:tab',
    params: {
      tab: (value) => ['table', 'widgets', 'charts'].includes(value)
    },
    query: {
      editMode: (value) => ['0', '1', '2'].includes(value)
    },
    loader: () => import('./pages/dashboard'),
  }

  // other Configs
});
tsx
export default function PageDashboard() {
  const { router } = useContext(RouterContext);

  const pageState = router.state.dashboard!;
  
  return (
    <Layout editMode={pageState.query.editMode || '0'}>
      {pageState.params.tab === 'table' && <Table />}
      {pageState.params.tab === 'widgets' && <Widgets />}
      {pageState.params.tab === 'charts' && <Charts />}
    </Layout>
  );
}
tsx
export default function PageDashboard() {
  const { router } = useContext(RouterContext);

  const pageState = router.state.dashboard!;
  
  return (
    <Layout editMode={pageState.query.editMode || '0'}>
      {pageState.params.tab === 'table' && <Table />}
      {pageState.params.tab === 'widgets' && <Widgets />}
      {pageState.params.tab === 'charts' && <Charts />}
    </Layout>
  );
}
tsx
export default function PageDashboard() {
  const { router } = useContext(RouterContext);
  
  const pageState = () => router.state.dashboard!;

  return (
    <Layout editMode={pageState().query.editMode || '0'}>
      <Switch>
        <Match when={pageState().params.tab === 'table'}><Table /></Match>
        <Match when={pageState().params.tab === 'widgets'}><Widgets /></Match>
        <Match when={pageState().params.tab === 'charts'}><Charts /></Match>
      </Switch>
    </Layout>
  );
}
vue
<script lang="ts" setup>
import { useRouterStore } from './router';

const { router } = useRouterStore();

const pageState = router.state.dashboard!;
</script>

<template>
  <Layout :edit-mode="pageState.query.editMode || '0'">
    <Table v-if="pageState.params.tab === 'table'" />
    <Widgets v-else-if="pageState.params.tab === 'widgets'" />
    <Charts v-else-if="pageState.params.tab === 'charts'" />
  </Layout>
</template>
  1. By assigning the same loader to several Config. In this case, beforeComponentChange will not be called, but router.activeName will still change. This example could have looked like the previous ones, but let us choose an approach with a reactive function:
ts
createConfigs({
  dashboard: {
    path: '/dashboard',
    loader: () => import('./pages/dashboard'),
  },
  dashboardEdit: {
    path: '/dashboard/edit',
    loader: () => import('./pages/dashboard'),
  },
  dashboardAggregate: {
    path: '/dashboard/aggregate',
    loader: () => import('./pages/dashboard'),
  }
});
tsx
// with mobx adapter

import { autorun } from 'mobx'

function PageUser() {
  const { router } = useContext(RouterContext);
  
  const [ Layout, setLayout ] = useState();
  
  useEffect(() => autorun(() => {
    if (router.activeName === 'dashboard') {
      setLayout(View)
    } else if (router.activeName === 'dashboardEdit') {
      setLayout(Edit)
    } else if (router.activeName === 'dashboardAggregate') {
      setLayout(Aggregate)
    }
  }), []);
  
  if (!Layout) return null;
  
  return <Layout>Content</Layout>;
}
tsx
// with kr-observable adapter

import { autorun } from 'kr-observable'

function PageUser() {
  const { router } = useContext(RouterContext);
  
  const [ Layout, setLayout ] = useState();
  
  useEffect(() => autorun(() => {
    if (router.activeName === 'dashboard') {
      setLayout(View)
    } else if (router.activeName === 'dashboardEdit') {
      setLayout(Edit)
    } else if (router.activeName === 'dashboardAggregate') {
      setLayout(Aggregate)
    }
  }), []);
  
  if (!Layout) return null;
  
  return <Layout>Content</Layout>;
}
tsx
// with solid adapter

import { createMemo } from 'solid-js'

function PageUser() {
  const { router } = useContext(RouterContext);

  const Layout = createMemo(() => {
    if (router.activeName === 'dashboard') return View;
    if (router.activeName === 'dashboardEdit') return Edit;
    if (router.activeName === 'dashboardAggregate') return Aggregate;
    
    return null;
  });

  return (
    <Show when={Layout()}>
      {(Component) => <Component>Content</Component>}
    </Show>
  );
}
vue
<script lang="ts" setup>
// with vue adapter
  
import { computed } from 'vue';
import { useRouterStore } from './router';

const { router } = useRouterStore();

const Layout = computed(() => {
  if (router.activeName === 'dashboard') return View;
  if (router.activeName === 'dashboardEdit') return Edit;
  if (router.activeName === 'dashboardAggregate') return Aggregate;
  
  return null;
});
</script>

<template>
  <component :is="Layout" v-if="Layout">
    Content
  </component>
</template>

These approaches can be combined depending on the task.

No AI participated in the development. MIT License