Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

abordolo's avatar

Laravel + Inertia + Vue3 :: Reloading page after back button

Hi Everyone,

I am working on an app using Laravel, Inertia, and Vue3.

The app's home page displays a list of Todo items.

Route:

Route::get('/', [TodoController::class, 'index'])->name('todos.index');

Controller Method:

public function index()
{
    $todos = Todo::latest()->get();

    return inertia('Todos/Index', [
        'todos' => $todos,
    ]);
}

On the home page, I have a Create Todo button, that takes the user to the create page.

Route:

Route::get('/todos/create', [TodoController::class, 'create'])->name('todos.create');

Controller Method:

public function create()
{
    return Inertia::render('Todos/Create');
}

The store route and methods are as below:

Route::post('/todos', [TodoController::class, 'store'])->name('todos.store');
public function store(Request $request)
{
    $attributes = $request->validate([
        'title' => [
            'required',
            'min:6',
            'max:255',
        ],
        'content' => [
            'required',
            'min:6',
            'max:255'
        ],
    ]);

    $todo = Todo::create($attributes);
}

In the controller method, I do not want to redirect to a new page, instead I take care of it on the Vue component as below:

const submitForm = () => {
  form.post('/todos', {
    preserveScroll: true,
    onSuccess: () => {
      form.reset();
      router.get('/', {}, { replace: true});
    },

    onError: () => {},
  });
};

After successfully creating the Todo Item, when Inertia visits the home page, I see the new Todo Item. But, if I press the back button, I do not see the new Todo Item (previous home page, before navigating to the create page). I need to refresh to page to view the newly created Todo Item.

I could replace the current page, when navigating from the Home page to the Create page. But I want to able to click the back button of the browser to cancel creating the new Todo Item (instead of clicking the cancel button on the Create page).

Similar scenario arises when I try to update any state of the Todo Item (eg. done state) in the show page and click the back button. I do not see the updated state on the previous home page.

I tried to use Pinia store to resolve it:

  1. When I visit the home page, I populate the Pinia store with the Todo Items
  2. When I go to the Create page and successfully add the Todo Item, I update the Pinia store
  3. But when I click on the back button, the Pinia store is re-populated because todosStore.populate(props.todos) is in the setup method of the Vue component.
// imports
///////////////////////////////////////
import { ref } from 'vue';
import TodoCard from './Partials/TodoCard.vue';
import { Switch } from '@headlessui/vue';
import { useTodosStore } from '@/Stores/TodosStore.js';
import { router } from '@inertiajs/vue3';
///////////////////////////////////////

// props
///////////////////////////////////////
const props = defineProps({
  todos: {
    type: Array,
    required: true,
  },
});
///////////////////////////////////////

// refs
///////////////////////////////////////
const debug = ref(false);
///////////////////////////////////////

// showOnlyIncompleteTodos
///////////////////////////////////////
const showOnlyIncompleteTodos = ref(true);
///////////////////////////////////////

// store
///////////////////////////////////////
const todosStore = useTodosStore();
todosStore.populateStore(props.todos);
///////////////////////////////////////

// toggle todos to display
///////////////////////////////////////
let toggleTodosToDisplay = () => {
  let value = todosStore.showOnlyIncompleteTodos;
  todosStore.setShowOnlyIncompleteTodos(!value);
};
///////////////////////////////////////
  1. I tried putting todosStore.populateStore(props.todos); in beforeMount as well. But it does not work
  2. Then, I put an if statement in the Pinia store's populateStore method to check if the todos are already populated, if populated do not re-populate. Because, after pressing the back button, it is only the cached version of props.todos will be passed to it.
actions: {
    populateStore(todos) {
      if (this.todos.length > 0) return;

      this.todos = todos;
      this.incompleteTodos = this.todos.filter((todo) => !todo.done);
      this.todosToDisplay = this.showOnlyIncompleteTodos
        ? this.incompleteTodos
        : this.todos;
    },
...
...
...
  1. This works fine, but, I then faced the issue when I create a Todo Item by directly visiting the Create page (by typing the url on the browser) and create the Todo Item. Then, when I visit the home page, store is not populated with props.todos
  2. I could resolve it by modifying the if condition as
if (this.todos.length > todos.length) return;

But, I think, I am not doing it right.

Could anyone please guide me on the right way to handle this? I do not want to replace the current page while navigating from the Home page to the Create page as below:

<Link :href="navItem.href" replace>
  <div
    class="text-sm font-bold tracking-wide uppercase transition-colors duration-300 hover:text-blue-700"
  >
    Create Todo
  </div>
</Link>              								

I also tried using popstate event listener, but failed to achieve reloading home page.

Sorry for the long post, but could someone please help?

0 likes
5 replies
LaryAI's avatar
Level 58

To address the issue of the back button not reflecting the updated state of your Todo items, you can leverage Inertia's preserveState option and Vue's reactivity system. Here’s a step-by-step solution:

  1. Update the Store Method to Redirect Back to the Home Page: Modify your store method in the TodoController to redirect back to the home page after creating a new Todo item.

    public function store(Request $request)
    {
        $attributes = $request->validate([
            'title' => [
                'required',
                'min:6',
                'max:255',
            ],
            'content' => [
                'required',
                'min:6',
                'max:255'
            ],
        ]);
    
        $todo = Todo::create($attributes);
    
        return redirect()->route('todos.index');
    }
    
  2. Update the Vue Component to Handle State Preservation: Use Inertia's preserveState option to ensure the state is preserved when navigating back.

    const submitForm = () => {
      form.post('/todos', {
        preserveScroll: true,
        onSuccess: () => {
          form.reset();
          router.get('/', {}, { replace: true, preserveState: true });
        },
        onError: () => {},
      });
    };
    
  3. Ensure the Home Page Component Reactively Updates: Make sure your home page component is set up to reactively update when the todos prop changes. This can be done using Vue's watch function.

    import { ref, watch } from 'vue';
    import { useTodosStore } from '@/Stores/TodosStore.js';
    
    export default {
      props: {
        todos: {
          type: Array,
          required: true,
        },
      },
      setup(props) {
        const todosStore = useTodosStore();
        todosStore.populateStore(props.todos);
    
        watch(() => props.todos, (newTodos) => {
          todosStore.populateStore(newTodos);
        });
    
        return {
          todosStore,
        };
      },
    };
    
  4. Pinia Store Setup: Ensure your Pinia store is set up to handle the population of todos correctly.

    import { defineStore } from 'pinia';
    
    export const useTodosStore = defineStore('todos', {
      state: () => ({
        todos: [],
        incompleteTodos: [],
        todosToDisplay: [],
        showOnlyIncompleteTodos: true,
      }),
      actions: {
        populateStore(todos) {
          this.todos = todos;
          this.incompleteTodos = this.todos.filter((todo) => !todo.done);
          this.todosToDisplay = this.showOnlyIncompleteTodos
            ? this.incompleteTodos
            : this.todos;
        },
        setShowOnlyIncompleteTodos(value) {
          this.showOnlyIncompleteTodos = value;
          this.todosToDisplay = value ? this.incompleteTodos : this.todos;
        },
      },
    });
    
  5. Handling Direct URL Access: Ensure that the home page component correctly populates the store when accessed directly.

    import { onMounted } from 'vue';
    import { useTodosStore } from '@/Stores/TodosStore.js';
    
    export default {
      props: {
        todos: {
          type: Array,
          required: true,
        },
      },
      setup(props) {
        const todosStore = useTodosStore();
    
        onMounted(() => {
          todosStore.populateStore(props.todos);
        });
    
        return {
          todosStore,
        };
      },
    };
    

By following these steps, you ensure that the state of your Todo items is preserved and updated correctly when navigating back and forth between pages. This approach leverages Inertia's preserveState option and Vue's reactivity system to keep the UI in sync with the application's state.

gych's avatar

I think you could use popstate event listener for this but you mentioned that you've already tried it and it didn't gave you the expected results. Can you share the code of how you implemented the popstate event listener?

abordolo's avatar

hey @gych thanks for the reply.

I resorted to using a Pinia Store as below:

export const usePopstateStore = defineStore({
  id: 'popstateStore',
  state: () => ({
    isBackButtonPressed: false,
  }),
  actions: {
    set() {
      this.isBackButtonPressed = true;
    },

    unset() {
      this.isBackButtonPressed = false;
    },
  },
});

In app.js file I added the event listener as below:

window.addEventListener('popstate', () => {
  usePopstateStore().set();
});

Finally in the components I added the following piece of code:

onBeforeMount(() => {
  if (popStateStore.isBackButtonPressed) {
    popStateStore.unset();
    router.reload({
      only: ['todos'],
    });
    return;
  } else {
    todosStore.populateStore(props.todos);
  }
});

That solved the issue, however I am exploring better ways to do it, maybe by not using Pinia store.

gych's avatar
gych
Best Answer
Level 29

@abordolo Try to add this at the top under the imports in your app.js file

import { router } from '@inertiajs/vue3'

let stale = false;
window.addEventListener('popstate', () => stale = true);
router.on('navigate', (event) => {
    if (stale) router.get(event.detail.page.url, {}, { replace: true, preserveState: false });
    stale = false;
});
2 likes
abordolo's avatar

Hey @gych, thank you very much. This works like charm.

I have used the code in the application following your instructions. It re-fetches data from the server if I press the back button on the browser.

Thank again :)

1 like

Please or to participate in this conversation.