Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to activate nested components with ids from the entire chain of nested routes. #136

Open
adrian-moisa opened this issue Oct 20, 2017 · 3 comments

Comments

@adrian-moisa
Copy link

adrian-moisa commented Oct 20, 2017

After implementing the demo example I have reached a roadblock. In the intro example the routes seem to have a 1 to 1 relation between route and action.

const routes = [
    {
        path: '', // optional
        action: () => `<h1>Home</h1>`,
    },
    {
        path: '/posts',
        action: () => console.log('checking child routes for /posts'),
        children: [
            {
                path: '', // optional, matches both "/posts" and "/posts/"
                action: () => `<h1>Posts</h1>`,
            },
            {
                path: '/:id',
                action: (context) => `<h1>Post #${context.params.id}</h1>`,
            },
        ],
    },
];

I am used to building nested components that have a router outlet (Angular 2) or match components (React). For example I would have a nesting like this:

 <chapter-cmp>
    <h1>Chapter title</h1>
    ...

    <lesson>
        <h1>Lesson title</h1>
        ...
    </lesson>

</chapter-cmp>
<sidebar>
    <p>Open almost all the time except for a few routes</p>
</sidebar>

How do I feed the corresponding data for each layer of the app? Currently I am not aware how can I get the ids from parents of nested routes without having to create monolitic routes like these path: '/:course/:chapter/:lesson'. I thought that I can just fire actions in the state store that will be received by the components. But than what if I move to another section of the app where I need to tear-down some components and instantiate others. '/blog/:category/:posts'`.

@adrian-moisa adrian-moisa changed the title How to How to activate nested components with ids from the entire chain of nested routes. Oct 20, 2017
@frenzzy
Copy link
Member

frenzzy commented Oct 20, 2017

There are plenty ways how you can go.
I believe that the right approach is to fetch the whole page data with a single http request (say hey to graphql) and use object composition approach for components, for example:

import UniversalRouter from 'universal-router';

const CourseLayout = (props, content) => `
  <section class="course">
    <h1>${props.title}</h1>
    ${content}
  </section>
  <aside class="sidebar">${props.details}</aside>
`;

const ChapterLayout = (props, content) => CourseLayout(props, `
  <section class="chapter">
    <h2>${props.title}</h2>
    ${content}
  </section>
`);

const LessonLayout = (props, content) => ChapterLayout(props, `
  <section class="lesson">
    <h3>${props.title}</h3>
    ${content}
  </section>
`);

const router = new UniversalRouter([
  {
    path: '/:course',
    async action({ params }) {
      const data = await fetch(`/api/course/${params.course}`);
      return CourseLayout(data, `<p>${data.description}</p>`);
    }
  },
  {
    path: '/:course/:chapter',
    async action({ params }) {
      const data = await fetch(`/api/course/${params.course}` +
        `?include-chapter=${params.chapter}`);
      return ChapterLayout(data, `<p>${data.chapter.description}</p>`);
    }
  },
  {
    path: '/:course/:chapter/:lesson',
    async action({ params }) {
      const data = await fetch(`/api/course/${params.course}` +
        `?include-chapter=${params.chapter}` +
        `&include-lesson=${params.lesson}`);
      return LessonLayout(data, `<p>${data.lesson.description}</p>`);
    }
  },
]);

router.resolve(location.pathname).then(html => {
  document.body.innerHTML = html;
});

this also will work with nested routes:

const routes = {
  path: '/:course',
  children: [
    { path: '', action(context, params) {/* course */} },
    { path: '/:chapter', children: [
      { path: '', action(context, params) {/* chapter */} },
      { path: '/:lesson', action(context, params) {/* lesson */} },
    ] },
  ],
};

@adrian-moisa
Copy link
Author

adrian-moisa commented Oct 20, 2017

Looks like I completely misunderstood the router architecture. I experimented a bit and I found that actually routes give plenty of control over what happens in the application. Here's a sample I used for experiments.

export const appRoutes = [

    {
        path: '',
        action: (context:any) => {
            console.log('App') 
            return {
                title: `Home`,
                component: `<app-cmp></app-cmp>`
            }
        },
    },
    {
        path: '/:course',
        action: (context:any) => {
            console.log(`GET course "${context.params.course}"`)
        },
        children: [
            {
                path: '',
                action: (context:any) => {
                    console.log('Course')
                    return {
                        title: `Course ${context.params.course}`,
                        component: `<course-cmp></course-cmp>`
                    }
                },
            },
            {
                path: '/:chapter',
                action: (context:any) => {
                    console.log(`GET chapter "${context.params.chapter}"`)
                },
                children: [
                    {
                        path: '',
                        action: (context:any) => {
                            console.log('Chapter') 
                            return {
                                title: `Chapter ${context.params.chapter}`,
                                component: `<chapter-cmp></chapter-cmp>`
                            }
                        },
                    },
                    {
                        path: '/:lesson',
                        action: (context:any) => {
                            console.log(`GET lesson "${context.params.lesson}"`)
                        },
                        children: [
                            {
                                path: '',
                                action: (context:any) => {
                                    console.log('Lesson') 
                                    return {
                                        title: `Lesson ${context.params.lesson}`,
                                        component: `<lesson-cmp></lesson-cmp>`
                                    }
                                },
                            }
                        ]
                    },
                ]
            },
        ],
    }
]

Accessing http://localhost:3000/apps/javascript/variables renders in console the following output. Basically instead of console.log I will have store.dispatch(getLesson(lessonId)).

GET course "apps"
GET chapter "javascript"
GET lesson "variables"
Lesson

An improvement will be to also prevent new GETs after first load for the parents that don't change ids.
For example if I execute router.resolve('/apps/javascript/variables/primitives') I would like to see only the GET codeBlock request. Although it's debatable if this is a good approach. Data synchronization issues could creep in. Any advice on this point?

GET course "apps"
GET chapter "javascript"
GET lesson "variables"
GET codeBlock "primitives"
Code block component

Now I have to figure out how to emulate the <router-outlet> from angular. I'll post back when I have that part working properly.

@frenzzy
Copy link
Member

frenzzy commented Oct 22, 2017

getLesson may skip http request in case of fresh data, for example based on time of the latest update. Or you can fetch data only once (if not exist in store) and use sockets to be notified about updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants