getkirby / kirby

Kirby's core application folder
https://getkirby.com
Other
1.27k stars 167 forks source link

Vue props do not pass as expected in 3.6.x instead result in content disappearing. #4125

Closed iandbanks closed 2 years ago

iandbanks commented 2 years ago

Description

As of 3.6 and the introduction of Fiber, props being passed from a plugins index.php file into a vue component no longer behave as expected and result in content disappearing. Initially upon page load a component will receive the prop data but then after a few seconds a Fetch/XHR request occurs on the page and overwrites previous prop data.

In testing, the content disappeared with custom sections but did not for the new Fiber Views

Expected behavior
The expected behavior would be that props would persist for section and other vue component types as they did in 3.5.

Screenshots
example-screenshot

To reproduce

I created a plugin that demonstrates the issue as well as walks through the testing steps. If you use the plugin, testing steps are in the readme. Otherwise, you can do a basic test by following the steps below.

  1. Create a new plugin.

  2. Create a new section within the plugins index.php file.

  3. Return props in the new section. index.php

    
    'sections' => [
        'todos' => [
            'props' => [
                'title' => 'Todos',
                'todos' => [
                    'Buy milk',
                    'Try Kirby 3.6'
                ]
            ]
        ],
  4. Create a new index.js file in the plugin. index.js

    
    import todosection from './components/sections/todo';
    panel.plugin("iandbanks/imagerandomizer", {
    sections:
        {
            "todos": todosection
        }
    });
  5. Create a todo.vue file in components/sections components/sections/todo.vue

    
    <template>
        <section>
          <header>
            <h1 class="test-headline">Test: Todos</h1>
          </header>
          <div>
            <k-header>{{ title }}</k-header>
            <ul>
              <li v-for="(todo, index) in todos" :key="index">
                {{ todo }}
              </li>
            </ul>
          </div>
        </section>
    </template>
    <script>
        export default {
            name: "k-todo-section",
            props: {
                title: String,
                todos: Array
            },
            created() {
                this.load().then(response => {
                this.title = response.title;
                this.todos = response.todos;
            });
            }
        }
    </script>
  6. Compile

  7. Add the section to any blueprint area. Site.yml is probably easiest.

    
    columns:
      - width: full
        sections:
          todos: sections/todos
  8. Navigate to {site-root.tld}/panel/site

  9. Observe how the Todo list initially appears, but then after a few seconds disappears.

Your setup

Kirby Version
3.6.2

Your system (please complete the following information)

bastianallgeier commented 2 years ago

Sorry, it took me quite a while to figure out what's going on. The way you are using the props in sections is not correct.

The misleading part here is our fault. Sections don't work like views or fields. They are lazy-loaded. This means that they don't get prop values from the backend when they are created. Instead you have to use the load method to lazy-load all backend data after the section is created.

This means that your imagerandomizer section never receives an initial value for the src prop from the backend. It basically gets created like this:

<imagerandomizer name="my-name" />

You still define a src prop, but it will always be undefined first. What you are doing afterwards is to fill in the value for that prop in your load method. This is technically not allowed in Vue.js because you are mutating a property. This breaks the flow in Vue that values must always be passed down, but never sent back up again that way.

The correct way is to store everything that comes from the load method in the data array. Like you already do with src2.

In 3.5.x this technically worked, although it was also incorrect. It worked because we never refreshed the entire view state on those 10 second lock check interval. But now we do refresh the entire state and every component that receives new props after the check will automatically be updated. This has so many advantages because it's now incredibly easy to react properly to such changes in all components without weird Vuex gymnastics.

But in your case your section component gets updated and the src property is wiped out again. The backend still doesn't send it and so it will still be marked as undefined.

I hope I explained it well enough. The fix is easy though. Everything that should be persisted in sections needs to go into the data object. As soon as you move src there as well, everything will start working again.

iandbanks commented 2 years ago

@bastianallgeier this is super helpful. Thanks for explaining the issue. Like you said the fix is easy enough to make. Especially thanks for clarifying the difference between sections and views/fields!