I am trying to define a custom action to my Ember component & want to communicate between parent/child.
In my-child.js, I have
actions: {
someCustomAction: function() {
let self = this;
self.sendAction('someCustomAction');
},
}
And I catch the same in my-parent.js as below;
actions: {
someCustomAction: function (){
console.log("Inside someCustomAction....");
}
}
Now, with the above code, control/action does not come to my-parent.js
I have to add "someCustomAction" to the template as below
In my-parent.hbs
{{my-child someCustomAction="someCustomAction"}}
I wanted to know the exact reason for the same. Why does just doing sendAction not automtically work ?
Here's how I would go about it while following the data down, actions up (DDAU) pattern.
First, you pass the action you want to fire in your child using the action helper.
{{my-child someActionName=(action 'parentAction')}}
Then, you retreive the action and fire it from the child component.
this.get('someActionName')(params);
Ember Twiddle Complete Example
This is because components are not contextually aware. So the actions need to be tied together manually. But this also gives you the ability to send different actions on the same component and even additional args as well.
Components are isolated from their surroundings, so any data that the
component needs has to be passed in.
https://guides.emberjs.com/v2.15.0/components/passing-properties-to-a-component/
If the component is truly isolated, actions are as well.
https://guides.emberjs.com/v2.15.0/components/triggering-changes-with-actions/#toc_passing-the-action-to-the-component
Related
This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0
Edit
Actually, I'm fairly certain that this is because my action creator doesn't return an action and the first property of an action is supposed to be a type and that is therefore undefined...
I have a React/Redux SPA that I want to register page views on with a custom analytics engine (ie, not Google Analytics). I'm trying to register page views.
So I have attempted to do this by setting lifecycle hooks in React to fire a Redux action:
class ConfirmationPage extends Component {
...
componentWillMount() {
this.props.registerPageVisited('confirmation');
}
}
However, I receive a Cannot read property 'type' of undefined error presumably because I am modifying the state via the props. Looking at the stack trace, it brings me to that hook. However, I've tried other hooks such as componentDidMount and even componentWillUnMount and I get the same error.
For context, my action creator is this:
export function registerPageVisited(page) {
DB.child('visits')
.child(store.getState().visit)
.update({ [page]: true });
}
where the DB is a firebase reference.
So, how should I keep track of page views?
Could you provide code where you instantiate ConfirmationPage component?
In the componentWillMount function what you are actually doing is calling registerPageVisited function which should be passed as props which should look something like: <ComponentPage registerPageVisited={registerPageVisited} />
As you are suspecting, since your registerPageVisited does not return action object it is not action creator. If you will make it an action creator, then you should use mapDispatchToProps function to use it like any other props as sen in your example.
An alternate to this is have a different object listen to notifications from the store and update the DB based on the changes it's seeing. That will isolate any metrics collection from your view hierarchy.
You'd have to replicate some logic that determines which React components are getting shown into that object, but you could pull that into a separate class and use it in both places.
If you're using react-router, there might be hooks that fire when the active route changes. However, I haven't investigated that.
I'm wondering if there is any way to pass data back from a dynamically loaded component in Angular2. I pass the component data using
this._dcl.loadIntoLocation(...).then(component => {
component.instance.variable = this.variable;
}
On completing the tasks inside the component, in my case a modal, can I pass data or variables back out in a similar manner? Thanks in advance.
You can add an Observable to the dynamically added component
(an EventEmitter might work as well but might break eventually. Then Angular2 team doesn't guarantee that an EventEmitter will keep behaving like an observable)
Then just subscribe to this observable
this._dcl.loadIntoLocation(...).then(component => {
component.instance.variable = this.variable;
component.instance.someObservable.subscribe(val => this.someVal = val);
}
DynamicComponentLoader is deprecated and AFAIK was already removed in master.
See also Angular 2 dynamic tabs with user-click chosen components
Say in my first file, open.jsx, I have:
// Default Import Statements here
var open = React.createClass({
render() {
return (
<div>
<Dialog
title="Test"
ref="openDialog">
</Dialog>
</div>
);
},
_handleTouchTap() {
this.refs.openDialog.setState({open:true});
}
});
module.exports = open;
And in my app.jsx file I have:
const testComponent = React.createClass({
render() {
return (
<FlatButton label="Test" onTouchTap={this._handleTouchTap}/>
);
},
_handleTouchTap: function() {
Open._handleTouchTap();
}
});
module.exports = testComponent;
The error I am getting currently is:
Uncaught TypeError: Open._handleTouchTap is not a function
Does anyone know how I can pass methods in between files for React?
I want to call open.jsx's _handleTouchTap() method when they press the button in app.jsx.
When you call Open._handleTouchTap, you are attempting to call the method as if it was static, on the Open class. This method, however, is only available once an Open component has been instantiated. You must attach a ref to the component and call the method via this.refs.someOpenComponent._handleTouchTap().
You may want to provide more of your code so better examples can be provided.
Also, methods with an underscore in front of their names typically denote "private" methods, and should not be called from a different class. You may want to consider renaming this function so it is more clear what its purpose is.
I'm assuming you want to render some page with a button, and show the dialog as soon as someone presses the FlatButton. I also notice you're using material-ui, so let's go with that.
When starting any React project, it's a good idea to think about your component hierarchy. Because you're using material-ui and the Dialog component's opening is controlled by passing props, it's easiest to use the following approach.
Simple case
Use a root component App (in app.jsx), which mounts a button and mounts a dialog, but the dialog is initially in a hidden state (the "open" prop on Dialog defaults to false) so doesn't visually show up yet (even though it is mounted). In this case, you will want the button to set the open prop on Dialog to true as soon as the button is pressed.
Please note I would recommend separating most of this rendering stuff into separate components; for illustration purposes, let's keep everything in App.jsx.
The way you want to organise in this case is as follows:
// App.jsx (render + handle click function only)
render: function() {
return (
<div>
<FlatButton label="Test" onTouchTap={this._handleTapTestButton}/>
<Dialog
title="Test"
open={this.state.dialogOpen}>
<div>My dialog contents</div>
</Dialog>
</div>
);
},
_handleTapTestButton: function() {
this.setState({dialogOpen: !this.state.dialogOpen}); // flip the state
}
See? No refs needed even (and that's good!). Now, this works fine if your Dialog component is located nice and close to your FlatButton.
More complex case: Dialog is far away from FlatButton
Your next question might be "how can I organise this when the Dialog component is nested somewhere deep inside a totally different component that is not a child or parent of the App.jsx component", but instead a sibling?
Well, this smells a little to me (just an opinion). It's not an anti-pattern per sé, but if you can avoid this, I would recommend you do. Ie: for your own convenience and for maintainability's sake, try to keep components that naturally interact with each other close (in terms of parent-child) to each other in the component hierarchy. This way, you can communicate pretty easily using props (see React's info on this. That's definitely not an absolute rule though, there are plenty of reasons to deviate from that.
Let's assume you have a valid case for not doing that, and even worse: the component are siblings, not direct or indirect grandparent/parent/child.
In that case, you have two options:
Use a store and associated events (or any other javascript code that communicates state) to communicate the state change to the other component (ie using Flux, Redux, or whatever you prefer). In this case, when the button is clicked, you fire an event somewhere that gets picked up by the other component. This event triggers a state change in the other component. Warning: this can get unmanageable pretty quickly, which is one of the reasons state-managing-frameworks like Flux and Redux exist.
OR, onTouchTap, have the FlatButton call a function that was passed down from a shared parent component. This function then flips the state at the shared parent component, and passes this state as props to the Dialog. Ie, when both components share a grandparent somewhere, you can define a function at the grandparent level and pass that function as a prop down to the FlatButton. The function's role is to change the state at the grandparent (dialogOpen). This state is then passed down one or more components as a prop all the way down the hierarchy until it ends up at the Dialog, which will auto show itself as the prop switches to true.
There are serious advantages/disadvantages to either approach. The first approach leaks your UI rendering logic into your stores (which is usually inevitable anyway, but can be managed using things like Flux), the second leaks it into the component hierarchy (tricky for maintainability) and tends to create tight coupling (yuck).
Here's the route:
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
closeModal: function () {
alert('asdf');
}
}
});
And the component js code:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
closeModal: function () {
this.sendAction('closeModal');
}
}
});
What I'd like to do is to (as the code may suggest ;) ) send an action from component to route, so that the route could act upon it. However the code above doesn't work - component properly handles the action, but the sendAction call inside it's method doesn't do anything.
EDIT:
I solved this problem using:
this._controller.send('closeModal'); inside component's action method however this solution doesn't satisfy me. Ember authors are saying that controllers will be removed in ember 2.0, so in my code I don't want to have any references to them. Suggestions?
A component has a isolated context. So it knows nothing about anything (route or controller) outside of the component. In order to send an action from your component to your route, you should pass the route's action to the component in your template like so:
// your template
{{your-component closeModal="closeModal"}}
Now when you call this.sendAction('closeModal') in your component, it will trigger the action given to the component within your template, which in this case is the closeModal action of your route.
For more information, see the docs (http://emberjs.com/api/classes/Ember.Component.html#method_sendAction)
UPDATE August 3 2016
For those who encountered closure actions in newer versions of Ember, you might also make use of such actions here by doing:
// your template
{{your-component closeModal=(action "closeModal")}}
This action helper will point to an action of your controller, in your component you can call this.attrs.closeModal() or this.get('closeModal')() to trigger the action instead of calling sendAction.
The benefit of these actions is that the action might return a value which can be used in the component. In case of a modal this can for example be used to determine if a modal may be closed or not if the closeAction is called, if it returns false for example you might decide to prevent the modal from closing.
As a side note, the closure actions always point to your controller, in order to let it point to a route action, you might take a look at this addon:
https://github.com/DockYard/ember-route-action-helper