Skip to content

Commit 57018df

Browse files
authored
Merge pull request #1006 from emberjs/deprecate-action-template-helper
Deprecate the (action) template helper and modifier
2 parents d6d0090 + 4a5a73e commit 57018df

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
---
2+
stage: accepted
3+
start-date: 2024-02-13T00:00:00.000Z
4+
release-date:
5+
release-versions:
6+
teams: # delete teams that aren't relevant
7+
- cli
8+
- data
9+
- framework
10+
- learning
11+
- steering
12+
- typescript
13+
prs:
14+
accepted: https://github.com/emberjs/rfcs/pull/1006
15+
project-link:
16+
---
17+
18+
<!---
19+
Directions for above:
20+
21+
stage: Leave as is
22+
start-date: Fill in with today's date, 2032-12-01T00:00:00.000Z
23+
release-date: Leave as is
24+
release-versions: Leave as is
25+
teams: Include only the [team(s)](README.md#relevant-teams) for which this RFC applies
26+
prs:
27+
accepted: Fill this in with the URL for the Proposal RFC PR
28+
project-link: Leave as is
29+
-->
30+
31+
# Deprecate `(action)` template helper and `{{action}}` modifier.
32+
33+
## Summary
34+
35+
The `(action)` template helper and `{{action}}` modifier was common pre-Octane. Now that we have native classes and the `{{on}}` modifier, we no longer need to use `(action)` or `{{action}}`
36+
37+
## Motivation
38+
39+
Remove legacy code with confusing semantics.
40+
41+
This is a part of _[Deprecating Ember Classic (pre-Octane)](https://github.com/emberjs/rfcs/issues/832)_.
42+
43+
## Transition Path
44+
45+
This was written in the [Octave vs Classic cheatsheet](https://ember-learn.github.io/ember-octane-vs-classic-cheat-sheet/#component-properties__ddau)
46+
47+
<details><summary>that content here</summary>
48+
49+
### Before (pre-Octane)
50+
51+
```js
52+
// parent-component.js
53+
import Component from '@ember/component';
54+
55+
export default Component.extend({
56+
count: 0
57+
});
58+
59+
```
60+
```hbs
61+
{{!-- parent-component.hbs --}}
62+
{{child-component count=count}}
63+
Count: {{this.count}}
64+
65+
```
66+
```js
67+
// child-component.js
68+
import Component from '@ember/component';
69+
70+
export default Component.extend({
71+
actions: {
72+
plusOne() {
73+
this.set('count', this.get('count') + 1);
74+
}
75+
}
76+
});
77+
```
78+
```hbs
79+
{{!-- child-component.hbs --}}
80+
<button type="button" {{action "plusOne"}}>
81+
Click Me
82+
</button>
83+
```
84+
85+
### After (post-Octane)
86+
```js
87+
// parent-component.js
88+
import Component from '@glimmer/component';
89+
import { tracked } from '@glimmer/tracking';
90+
import { action } from '@ember/object';
91+
92+
export default class ParentComponent extends Component {
93+
@tracked count = 0;
94+
95+
@action plusOne() {
96+
this.count++;
97+
}
98+
}
99+
100+
```
101+
```hbs
102+
{{!-- parent-component.hbs --}}
103+
<ChildComponent @plusOne={{this.plusOne}} />
104+
Count: {{this.count}}
105+
106+
```
107+
```hbs
108+
{{!-- child-component.hbs --}}
109+
<button type="button" {{on "click" @plusOne}}>
110+
Click Me
111+
</button>
112+
113+
```
114+
115+
</details>
116+
117+
But what we could put in the deprecation app:
118+
119+
### Scenario: `action` is passed a string
120+
121+
Before:
122+
```hbs
123+
<button type="button" {{action "plusOne"}}>
124+
Click Me
125+
</button>
126+
```
127+
128+
After
129+
130+
```hbs
131+
<button type="button" {{on 'click' this.plusOne}}>
132+
Click Me
133+
</button>
134+
```
135+
or, if `plusOne` is passed in as an argument
136+
```hbs
137+
<button type="button" {{on 'click' @plusOne}}>
138+
Click Me
139+
</button>
140+
```
141+
142+
If the `plusOne` action is in an actions object, it needs to move out:
143+
144+
Before:
145+
```js
146+
import Component from '@glimmer/component';
147+
148+
export default class Demo extends Component {
149+
actions = {
150+
plusOne() {
151+
/* ... */
152+
}
153+
}
154+
}
155+
```
156+
or
157+
```js
158+
import Component from '@ember/component';
159+
160+
export default class Demo extends Component {
161+
actions = {
162+
plusOne() {
163+
/* ... */
164+
}
165+
}
166+
}
167+
```
168+
or
169+
```js
170+
import Component from '@ember/component';
171+
172+
export default Component.extend({
173+
actions: {
174+
plusOne() {
175+
/* ... */
176+
}
177+
}
178+
})
179+
```
180+
181+
After:
182+
```js
183+
import Component from '@glimmer/component';
184+
import { action } from '@ember/object';
185+
186+
export default class Demo extends Component {
187+
@action
188+
plusOne() {
189+
/* ... */
190+
}
191+
}
192+
```
193+
194+
Note that `@action` is completely different from `(action)` or `{{action}}` (and is partly a motivator for deprecating `(action)` and `{{action}}`, to reduce ambiguity).
195+
196+
`@action` is binds the `this` on the method to the instance of the class.
197+
198+
### Scenario: `action` is passed a function reference
199+
200+
Before:
201+
```hbs
202+
<SomeComponent @update={{action this.plusOne}} />
203+
```
204+
205+
After
206+
207+
```hbs
208+
<SomeComponent @update={{this.plusOne}} />
209+
```
210+
211+
### Scenario: `action` is passed parameters
212+
213+
Before:
214+
```hbs
215+
<SomeComponent @update={{action this.plus 1}} />
216+
```
217+
218+
After:
219+
```hbs
220+
<SomeComponent @update={{fn this.plus 1}} />
221+
```
222+
223+
### Scenario: `action` is used with `mut`
224+
225+
Before:
226+
```hbs
227+
<SomeComponent @update={{action (mut @value.property)}} />
228+
```
229+
After:
230+
```js
231+
// parent.js
232+
import Component from '@glimmer/component';
233+
import { action } from '@ember/object';
234+
235+
export default class SomeComponent extends Component {
236+
@action
237+
handleUpdate(value) {
238+
this.args.property = value;
239+
}
240+
}
241+
```
242+
```hbs
243+
{{! parent.hbs }}
244+
<SomeComponent @update={{this.handleUpdate}} />
245+
```
246+
247+
Related, [Combining function arguments with action functions](https://guides.emberjs.com/release/components/component-state-and-actions/#toc_combining-arguments-and-actions)
248+
249+
### Related: `send`
250+
251+
When removing `(action)` or `{{action}}` with a _string_ name, you'll also need to verify that there are no [`send`](https://api.emberjs.com/ember/5.6/classes/Component/methods/send?anchor=send) calls with that same string.
252+
253+
## How We Teach This
254+
255+
The guides already cover how to invoke functions in the modern way.
256+
257+
Remove: https://api.emberjs.com/ember/5.6/classes/Ember.Templates.helpers/methods/action?anchor=action
258+
259+
## Drawbacks
260+
261+
Older code will stop working once the deprecated code is removed.
262+
263+
## Alternatives
264+
265+
- adding an import so folks can keep using action in gjs.
266+
I don't think we should do this because we want to clean up antiquated patterns, rather than encourage their continued existence.
267+
268+
## Unresolved questions
269+
270+
- Could there be a codemod?
271+
_Potentially_ for action usage that references `this.properties`. For string actions, it's impossible.
272+

0 commit comments

Comments
 (0)