Create button that refreshes route?

Hi I’m trying to create a button that just refreshes the current page/route. This is what I have so far.

in my main route:

home.js actions: { refresh: function() { this.refresh(); } },

home.hbs

{{refresh actionToCall="refresh"}}

In my button component:

refresh.js

import Ember from 'ember';

export default Ember.Component.extend({

    actions: {
    refresh() {
      this.get('home').send('refresh');
    }
  }
  
  
});

refresh.hbs

<button {{action "refresh"}} class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent">Search Again!</button>

But I think Im missing something. The button appears and the browser console throws this error when the button is clicked:

refresh.js:12 Uncaught TypeError: Cannot read property ‘send’ of null

A couple issues here:

  1. In your component, this.get('home').send('refresh') is never going to work: home isn’t a value you’ve passed to the component, and the component has no knowledge of the home route, so it’s always undefined.

  2. Here you just need to rely on the bubbling behavior of the {{action}} helper, described in the API docs for {{action}}:

    If the context of a template is a controller, actions used this way will bubble to routes when the controller does not implement the specified action. Once an action hits a route, it will bubble through the route hierarchy.

So if you have a route like this:

export default Route.extend({
  actions: {
    reloadModel() {
      this.refresh();
    }
  },
});

Then you don’t even need a full component, just a button, set up like this:

<button {{on "click" (action "reloadModel")}}>Reload!</button>

Here I’m using the {{on}} helper, first introduced in Ember 3.11, but back ported to earlier releases with the ember-on-modifier polyfill. This is the recommended way to handle this. If you’re on a version where modifiers aren’t supported, you can write it like this instead:

<button {{action "reloadModel" on="click"}}>Reload!</button>

If you wanted to make that a component instead, you could do it like this:

  • Ember component with backing class:

    import Component from '@ember/component';
    
    export default Component.extend({
      tagName: 'button',
      click() {
        this.onClick();
      }
    });
    
    Reload!
    
  • Glimmer component: no backing class, but this template:

    <button {{on "click" @onClick}}>Reload!</button>
    
  • Invocation with Angle Brackets (preferred!):

    <ReloadButton @onClick={{action "reloadModel"}} />
    
  • Invocation with curlies (old Ember, not preferred):

    {{reloadButton onClick=(action "reloadModel")}}
    
1 Like

Okay I think I got part of it to work using glimmer

I first installed glimmer to my app…

Then at the top of my search.js component I added:

import Component from '@glimmer/component';
import { action } from '@ember/object';

And at the bottom of my search component I have this for the action.

	  @action
  reload() {
	this.refresh();	
	}

And in my hbs for my search component I have

<button onclick={{action "reload"}}>Search Again?</button> 

The button is calling the function now…

But it doesn’t like the refresh command?

In the browser console:

search.js:173 Uncaught TypeError: this.refresh is not a function

I just got it to work…

I used this in my component:

	  @action
  reload() {
	window.location.reload(true);
  },

and this in the template:

<button onclick={{action "reload"}} style="background-color:#f2661B; color:#ffffff;">Search Again?</button>

Thank you very much for your help

1 Like

Putting it on a component isn’t going to help, because it’s still not connected in any way.

I was mistaken earlier, and you need to put an action on a controller and use this.send from there. With the same route I suggested earlier:

export default Controller.extend({
  actions: {
    reload() {
      this.send("reloadModel");
    }
  },
});

And in the template:

<button {{action "reload"}}>Reload!</button>

I’m not at a computer where I can test that at the moment (typing on a tablet!) but I think that should work.


Edit to add Octane-friendly example:

If you’re using a version of Ember from 3.11 or later, you could do this instead (and it’s strongly recommended):

import Controller from '@ember/controller';
import { action } from '@ember/object';

export default class MyController extends Controller {
  @action reload() {
    this.send("reloadModel");
  }
}
<button {{on "click" this.reload}}>Reload!</button>

But I got it to work using the method I described in my last comment. Is there issues with my method?

Yep, there are a couple issues with it!

  1. You’re actually just doing a full reload of the window that way, instead of just reloading the data for the route. That will be a lot slower, and it’ll give the user a much worse experience by giving them the white screen flash as the whole page reloads.

  2. A much smaller issue, but you shouldn’t use onclick=. This talk from EmberConf 2018 has a great explanation of the difference between native onclick and Ember’s built-in event handlers, and particularly on how mixing them causes all sorts of issues. You should prefer {{action "<action name>" on="click"}} in versions of Ember before 3.11, and {{on "click" <action>}} in versions 3.11 and higher.

Thank you… This is what I have now…

In the search.js I have this

	  @action
  reload() {
	this.send("reloadModel");
  },

and in the search.hbs I have this

<button {{on "click" this.reload}} style="background-color:#f2661B; color:#ffffff;">Search Again?</button>

But it works maybe once then throws this error

index.js:163 Uncaught Error: Assertion Failed: <emberjs@component:search::ember193> had no action handler for: reloadModel

Do I need to add the same action to index.js that I put in search.js ?

I changed it to this

	  @action
  reload() {
	this.send("reload");
  },

but gives this error now.

Uncaught RangeError: Maximum call stack size exceeded

Without knowing exactly what files you’re talking about, I’m not sure I can answer specific questions. Here’s what you need to have:

  • app/routes/search.js with an action named reloadModel
  • app/controllers/search.js with an action named reload, which does this.send('reloadModel')
  • app/templates/search.hbs with a button which does {{on "click" this.reload}}

Before you go any further, I suggest reading or rereading [the Ember Guides] and specifically their discussion of:

It’s really important to understand the differences between these before going any further!

This is because doing this.send will find the first action it can in the “bubbling” hierarchy, which includes first controllers then routes – and since you have renamed the action on the controller here to reload, it finds itself—it’s the same as doing this:

@action reload() {
  this.reload()
}

I have a index.js/index.hbs route that just goes to one search.js component.

in the index.hbs i have this

<Search @title="Strain Search" @results={{this.model}} class="index"/>  

Then the search.js / search.hbs component is the whole app containing a search form and a results form.

Ive gone through the guide quickly. I plan on going through it again when I have more time. Trying to get this simple search app functioning first. I really appreciate your help.