Parent / Child components VUE.JS 2.1.6 data passing - javascript

I'm very confused about how to properly tie components together.
I have two components registered globally:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
showModal = false;
}
});
Vue.component('product-create-modal-component', {
template: '#create-modal-template'
});
In the parent's template I included the child component like this:
<template id="product-welcome-template">
<div class="welcome-wrapper">
<div class="purpose-title"><h1 class="welcome-text">Welcome to Product Hub</h1></div>
<div class="purpose-create-btn"><button ##click="showModal = true" class="btn btn-primary btn-success create-btn">Create New Product</button></div>
<product-create-modal-component v-if="showModal"></product-create-modal-component>
</div>
</template>
The problem is (one of them) is that my create-modal-component is always showing, regardless of the value of showModal, in fact i can put in v-if="1 === 2" it would still show.
I'm sure this is not the right way of registering parent / child components but I can't seem to find a proper example. Mostly what i see that the parent is the app instance and it has a child of 'child' component and then they can communicate.
I have a feeling that including the child component in the parent's template is bad practice as it makes the parent strongly coupled.
Any help would be appreciated, thank you!

You are having showModal as props to product-welcome-component, but you are trying to set it false in created, but you have to use this in created to access showModal, like following:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
this.showModal = false;
}
});
However you are saying product-create-modal-component shows even you do v-if="1 === 2", which should not be the case Can you create a fiddle of your case.

Related

Sharing property data between sister components in vue2

I have two components Header.vue and Sidebar.vue
On Header.vue i have a button which onclick i need to change the value of property in Sidebar.vue
On Header.vue i have
template:
<a v-on:click="toggleSidebar">Toggle</a>
method:
toggleNavbar: function() {
this.toggleSidebar(this.showSidebar)
}
On Sidebar.vue
template:
<div class="sidebar sidebar_display_none" :class="showSidebar?'show':''">
Method:
created() {
this.showSidebar= this.toggleSidebar(this.showSidebar)
console.log(this.showSidebar)
}
On Mixin
toggleSidebar: function(currentState) {
return !currentState
}
Onclick Toggle button i need to show/hide Sidebar (showSidebar property is boolean)
How can i do it? Thanks
Psidom's comment above of studying vuex is spot on. In addition, you should look into props and events as another resource/skill for sharing and managing data between components.
Note I would've loved to have left this as a comment as well but I don't have the reputation for comments yet...

Knockoutjs - With bindings and state

I'm having an issue with component binding and state.
This is my html template:
<div class="ts-panel content">
<!--ko with: states.createState-->
<div data-bind="component: 'customer-create'">Testing CreateState</div>
<!--/ko-->
<!--ko with: states.lookupState-->
<div data-bind="component: 'customer-search'">Testing LookupState</div>
<!--/ko-->
</div>
This is my javascript
var myDataModel = function () {
var self = this;
self.states = {};
self.states.createState = ko.observable(true);
self.states.lookupState = ko.observable(false);
self.states.currentState = ko.observable(self.states.createState);
self.states.changeState = function (state) {
var currentState = self.states.currentState();
currentState(false);
self.states.currentState(state);
state(true);
}
};
return myDataModel;
I'm using another script to control which state I'm in by binding click events to certain buttons.
The problem I'm running into is that when I change the current state, the component bindings reset the state of the component. Eg. on the customer-create component, I fill out a form, then change to the lookupState, then change back to the createState, the form values are gone.
I think this is happening because the components are getting wiped out and recreated every time.
I also think that one solution to this is to store everything at the root level (i.e. the component that stores the states) and pass that all down when required to the individual components. However, I'd really like to keep the component-specific information inside those components.
Is there a way to store the state of the components or maybe store the components in a variable and bind to it that way?
From the documentations:
If the expression you supply involves any observable values, the expression will be re-evaluated whenever any of those observables change. The descendant elements will be cleared out, and a new copy of the markup will be added to your document and bound in the context of the new value.
The behaviour is same for the if binding as well. You could use the visible binding for this. This just hides and shows the div without actually removing it from the DOM. There is no containerless control flow syntax for visible. So, you'd have to add it to the div
<div data-bind="component:'customer-create', visible: states.createState">Testing CreateState</div>

Ref added programmatically not applied

I would like to attach a ref attribute to an HTML element that has a custom directive.
Let's say I have a directive named v-custom and whenever it is used on an element, I would like ref="elem" to be added to the element.
I have tried using directives like so:
Vue.directive('custom', {
inserted(el) {
el.setAttribute("ref", "elem");
}
});
And in one of my components I have this:
<span v-custom>Testing</span>
And when I view it in a web page I can inspect that span element and see that it has the ref attribute but when I inspect the refs of the component it belongs to it says that it contains no "elem" ref.
However, If I add the ref tag myself like so:
<span v-custom ref="elem">Testing</span>
Then it works as intended and I can see the "elem" ref in the console.
Is there any way to get my use case working or is this intended behavior?
As #skirtle noted, ref is written as a normal DOM attribute in the vue template code, but is handled differently when parsed. A vue component instance/view model/"vm" has an object vm.$refs which maps keys to DOM elements. We can modify this object ourself. The issue then is how to get the parent vm from within the directive (we already got the DOM element el).
Looking at the documentation for custom directives https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments, we can see that the third argument is a "vnode" reference, and looking at its documentation, we can see that vnode.context references the container vm; thus:
Vue.directive('my-directive', {
inserted (el, binding, vnode) {
console.log('directive inserted')
const refKey = "s2"
vnode.context.$refs[refKey] = el // set it
}
})
Vue.component('my-component', {
template: '#my-component-template',
replace: false,
props: {text: String},
mounted () {
console.log('component mounted')
this.$refs.s1.innerText = 's1 ref working'
this.$refs.s2.innerText = 's2 ref working' // use it
}
});
new Vue({
el: '#app',
data: {
status: "initialized",
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
hello
<my-component :text="status"></my-component>
</div>
<script type="text/x-template" id="my-component-template">
<div>
{{text}}, <!-- to illustrate props data passing working as usual -->
<span ref="s1"></span>, <!-- to illustrate a normal ref -->
<span class="nested"> <!-- to illustrate vnode.context doesn't just get the parent node -->
<span v-my-directive></span>
</span>
</div>
</script>
Running this example, we can see that the v-my-directive successfully modifies vm.$refs.s2 to reference the DOM-element with the directive, before the mounted function in the vm is run, where we can use the reference.
Beware that you probably would like some logic to not overwrite the ref if more that one elements contains the directive.
Happy coding!

How to remove UI components using React JS

I'm having trouble finding relevant documentation on how to remove UI components when using react. For example, there's a login form. The user clicks submit and now the form should be removed from the screen. How do I do this?
I've found unmountComponentAtNode, but that can only be invoked at the parent level. Am I supposed to have a parent node that is aware of all child state and loads them conditionally? Should all children have an "isHidden" attribute which renders the dom as hidden if true?
This must be basic but I don't see this in the react js tutorials. I found this stackoverflow post (react.js: removing a component) is this really the pattern? It kind of makes sense but it means that a large app will likely have an extremely complex Application parent class that manages maps of application state based on configuration.
It seems like i need to start defining application state as named maps. For example:
BaseApp: showHeader=true;showContent=true;
LoginState: showBaseApp=true;showLoginForm=true;
LoggedInState: showBaseApp=true;showFeed=true;
At any moment we would have to update all state maps and call the base class render method...
In my opinion your question isn't about removing component but about showing the right component. And yes - it can be done with a component state but with Flux/Redux store/reducer as well.
In your example with a login form after click on "Submit" we can change local state for the component and show another text like "The request was sent blah-blah-blah" or another component.
But you can do this by extracting component's local state to a store/reducer and it'll be work better in relatively big app. Nevertheless, it's really up to you where you want to store state.
I like to use a hide prop like so.
class AppCtrlRender extends React.Component {
render() {
let page = this.state.appState.currentPage;
let hideAbout = (page != 'about');
let hideHome = (page != 'home');
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
<div id='allPageSty' style={allPageSty}>
<AboutPage hide={hideAbout} />
<HomePage hide={hideHome} />
</div>
</div>
);
}
}
export default class AboutPage extends React.Component {
render() {
if (this.props.hide) return null;
let aTime = (new Cache()).time.toString();
return (
<div style={AboutPageSty}>
React 0.14 ReFlux used for app state. This is the About Page.
<NavMenu /><br/><br/>
{aTime}
</div>
);
}
}

Show/Hide ReactJS components without losing their internal state?

I've been hiding/showing react components by not rendering them, for example:
render: function() {
var partial;
if (this.state.currentPage === 'home') {
partial = <Home />;
} else if (this.state.currentPage === 'bio') {
partial = <Bio />;
} else {
partial = <h1>Not found</h1>
}
return (
<div>
<div>I am a menu that stays here</div>
Home Bio
{partial}
</div>
);
}
but just say that the <Bio/> component has lots of internal state. Everytime I recreate the component, it loses it's internal state, and resets to it's original state.
I know of course that I could store the data for it somewhere, and pass it in via props or just globally access it, but this data doesn't really need to live outside of the component. I could also hide/show components using CSS (display:none), but I'd prefer to hide/show them as above.
What's the best practice here?
EDIT: Maybe a better way to state the problem is to use an example:
Ignore React, and assume you were just using a desktop app that had a configuration dialog with a Tab component called A, which has 2 tabs, named 1 and 2.
Say that tab A.1 has an email text field and you fill in your email address. Then you click on Tab A.2 for a second, then click back to Tab A.1. What's happened? Your email address wouldn't be there anymore, it would've been reset to nothing because the internal state wasn't stored anywhere.
Internalizing the state works as suggested in one of the answers below, but only for the component and it's immediate children. If you had components arbitrarily nested in other components, say Tabs in Tabs in Tabs, the only way for them to keep their internal state around is to either externalize it somewhere, or use the display:none approach which actually keeps all the child components around at all times.
It just seems to me that this type of data isn't data you want dirtying up your app state... or even want to even have to think about. It seems like data you should be able to control at a parent component level, and choose to either keep or discard, without using the display:none approach and without concerning yourself with details on how it's stored.
One option would be to move the conditional inside the component itself:
Bio = React.createClass({
render: function() {
if(this.props.show) {
return <p>bio comp</p>
} else {
return null;
}
}
});
<Bio show={isBioPage} />
Whether this is "best practise" or not probably depends on the exact situation.
Unfortunately, style={{display: 'none'}} trick only works on normal DOM element, not React component. I have to wrap component inside a div. So I don't have to cascade the state to subcomponent.
<div className="content">
<div className={this.state.curTab == 'securities' ? 'active' : ''}>
<Securities />
</div>
<div className={this.state.curTab == 'plugins' ? 'active' : ''}>
<Plugins />
</div>
</div>
Looks like official documentation suggests hiding stateful children with style={{display: 'none'}}
The fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?
I propose the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale
Rationale
react/react-dom comes comes with 2 basic assumptions/ideas:
every UI is hierarchical naturally. This why we have the idea of components which wrap each other
react-dom mounts (physically) child component to its parent DOM node by default
The problem is that sometimes the second property isn't what you want
in your case. Sometimes you want to mount your component into
different physical DOM node and hold logical connection between
parent and child at the same time.
Canonical example is Tooltip-like component: at some point of
development process you could find that you need to add some
description for your UI element: it'll render in fixed layer and
should know its coordinates (which are that UI element coord or
mouse coords) and at the same time it needs information whether it
needs to be shown right now or not, its content and some context from
parent components. This example shows that sometimes logical hierarchy
isn't match with the physical DOM hierarchy.
Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example to see the concrete example which is answer to your question (take a look at the "use" property):
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...

Categories

Resources