Im changing a child component state from its parent using this method:
From parent:
this.changeBottomToolbar(newTool)
Method declared on Child's class
changeBottomToolbar(newToolbarName){
this.setState({selectedToolbar: newToolbarName})
}
It's state change and it re-renders as I check with the console.
The render it's a conditional render that uses a function to get what it should render
getBottomToolbar(){
switch(this.state.selectedToolbar){
case "SelectorAndText":
console.log("Devolviendo el selector")
return <TextBottomToolbar
ref={this.bottomToolbarRef}
fontSizeUpdater = {this.fontSizeUpdater}
fontColorUpdater = {this.fontColorUpdater}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
fontFamilyUpdater = {this.fontFamilyUpdater}
alignmentUpdater = {this.alignmentUpdater}
/>
break
case "FreeLine":
console.log("Devolviendo el Line")
return <LineBottomToolbar
ref={this.bottomToolbarRef}
strokeColorUpdater = {this.strokeColorUpdater}
strokeSizeUpdater = {this.strokeSizeUpdater}
shadowColorUpdater={this.shadowColorUpdater}
shadowSizeUpdater={this.shadowSizeUpdater}
/>
break
}
}
And i call it on the render:
render(){
const { classes } = this.props
console.log(this.state.selectedToolbar)
return (
<React.Fragment>
{
this.getBottomToolbar()
}
</React.Fragment>
);
}
As you can see in this image, the code it's been executed correctly and it returns the other component when the state it's changed
But the component ITS NOT CHANGING even tho the render it's been called again and it's state it's changing, im completely shocked, I have no clue on why this happens, please help!!
So the problem was that i wrongly copy-pasted the import (facepalm)
This was the import I was doing so I thought it wasn't chaning
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/TextBottomToolbar'
This corrected the problem:
import TextBottomToolbar from './BottomToolbars/TextBottomToolbar'
import LineBottomToolbar from './BottomToolbars/LineBottomToolbar '
I'm creating my first React app, so apologies in advance. A newbie must learn as he goes.
But I'm a few hours into debugging, having gotten nowhere, and I'm hoping someone can clarify why this attempt to pass data into a React component, use prototype.map, and render a final component just isn't cutting it.
var imagedata = [{"id":"1"},{"id":"2"},{"id":"3"}];
var portraitPhoto = React.createClass({
render: function() {
return (
<div className="test">
<img src={"./build/assets/images/photos/square_raw/" + this.props.imagepath + ".jpg"}
className="full-width-portrait" />
</div>
);
}
});
var portrait = React.createClass({
getDefaultProps: function(){
return {
data: imagedata
}
},
render: function() {
var portraitEach = this.props.data.map(function (imaged,i) {
return (
<div className="portrait2">
<portraitPhoto imagepath={imaged.id}/>
</div>
);
});
return (
<div className="portrait-container">
{portraitEach}
</div>
);
}
});
React.render(
<portrait/>,
document.getElementById('portraits')
);
You need your React components to have uppercase names. React's help page says:
React's JSX uses the upper vs. lower case convention to distinguish
between local component classes and HTML tags
Simply having this should be enough:
var PortraitPhoto = React.createClass({
...
...
});
A demo on jsfiddle is here.
Background
I am trying to learn how to use the React Shallow Rendering TestUtil and had the tests passing until I added an onClick event handler to both; It seems that there must be some difference with the Accordion.toggle function I am trying to use in Accordion.test.js vs this.toggle in Accordian.js...but I can't figure it out.
Question
How can I get the two highlighted tests in Accordian.test.js to pass?
Steps to reproduce
Clone https://github.com/trevordmiller/shallow-rendering-testing-playground
npm install
npm run dev - see that component is working when you click "Lorem Ipsum"
npm run test:watch - see that tests are failing
There are a number of issues preventing your tests from passing.
Looking at the test "should be inactive by default":
Accordion.toggle in your test is a property of the Accordion class, and this.toggle in your code is a property of a instance of the Accordion class - so in this case you are comparing two different things. To access the 'instance' method in your test you could replace Accordion.toggle with Accordion.prototype.toggle. Which would work if it were not for this.toggle = this.toggle.bind(this); in your constructor. Which leads us to the second point.
When you call .bind() on a function it creates a new function at runtime - so you can't compare it to the original Accordion.prototype.toggle. The only way to work around this is to pull the "bound" function out of the result from render:
let toggle = result.props.children[0].props.onClick;
assert.deepEqual(result.props.children, [
<a onClick={toggle}>This is a summary</a>,
<p style={{display: 'none'}}>This is some details</p>
]);
As for your second failing test "should become active when clicked":
You try calling result.props.onClick() which does not exist. You meant to call result.props.children[0].props.onClick();
There is a bug in React that requires a global "document" variable to be declared when calling setState with shallow rendering - how to work around this in every circumstance is beyond the scope of this question, but a quick work around to get your tests passing is to add global.document = {}; right before you call the onClick method. In other words where your original test had:
result.props.onClick();
Should now say:
global.document = {};
result.props.children[0].props.onClick();
See the section "Fixing Broken setState()" on this page and this react issue.
Marcin Grzywaczewski wrote a great article with a workaround for testing a click handler that works with shallow rendering.
Given a nested element with an onClick prop and a handler with context bound to the component:
render() {
return (
<div>
<a className="link" href="#" onClick={this.handleClick}>
{this.state.linkText}
</a>
<div>extra child to make props.children an array</div>
</div>
);
}
handleClick(e) {
e.preventDefault();
this.setState({ linkText: 'clicked' });
}
You can manually invoke the function value of the onClick prop, stubbing in the event object:
it('updates link text on click', () => {
let tree, link, linkText;
const renderer = TestUtils.createRenderer();
renderer.render(<MyComponent />);
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
// initial state set in constructor
expect(linkText).to.equal('Click Me');
// manually invoke onClick handler via props
link.props.onClick({ preventDefault: () => {} });
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
expect(linkText).to.equal('Clicked');
});
For testing user events like onClick you would have to use TestUtils.Simulate.click. Sadly:
Right now it is not possible to use ReactTestUtils.Simulate with Shallow rendering and i think the issue to follow should be: https://github.com/facebook/react/issues/1445
I have successfully tested my click in my stateless component. Here is how:
My component:
import './ButtonIcon.scss';
import React from 'react';
import classnames from 'classnames';
const ButtonIcon = props => {
const {icon, onClick, color, text, showText} = props,
buttonIconContainerClass = classnames('button-icon-container', {
active: showText
});
return (
<div
className={buttonIconContainerClass}
onClick={onClick}
style={{borderColor: color}}>
<div className={`icon-container ${icon}`}></div>
<div
className="text-container"
style={{display: showText ? '' : 'none'}}>{text}</div>
</div>
);
}
ButtonIcon.propTypes = {
icon: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired,
color: React.PropTypes.string,
text: React.PropTypes.string,
showText: React.PropTypes.bool
}
export default ButtonIcon;
My test:
it('should call onClick prop when clicked', () => {
const iconMock = 'test',
clickSpy = jasmine.createSpy(),
wrapper = ReactTestUtils.renderIntoDocument(<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>);
const component = findDOMNode(wrapper).children[0];
ReactTestUtils.Simulate.click(component);
expect(clickSpy).toHaveBeenCalled();
expect(component).toBeDefined();
});
The important thing is to wrap the component:
<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>
Hope it help!
I have a set of buttons created from an array, however I'm unsure how to set individual classNames for them. Is there an easy way to this?
var ButtonContainer = React.createClass({
render: function(){
var answerList = this.props.answerList.map(function(input, i){
return <SingleButton key={'button'+i} singleAnswer={input}/>
}.bind(this));
return <div> {answerList} </div>
}
})
var SingleButton = React.createClass({
render: function(){
return (
<div>
<button className='quiz-button'>{this.props.singleAnswer}</button>
</div>
)
}
});
I've tried className={this.props.key} but that doesn't seem to work. Thanks for any help!
Since React v0.12 key and ref are removed from props:
You can no longer access this.props.ref and this.props.key from inside
the Component instance itself. So you need to use a different name for
those props.
That is why setting className={this.props.key} wont work. But you can try this:
return <SingleButton key={'button'+i} className={'button'+i} singleAnswer={input}/>
and then
<button className={this.props.className}>{this.props.singleAnswer}</button>
Related question: This.key in React.js 0.12
How can I find the vue.js component corresponding to a DOM element?
If I have
element = document.getElementById(id);
Is there a vue method equivalent to the jQuery
$(element)
Just by this (in your method in "methods"):
element = this.$el;
:)
The proper way to do with would be to use the v-el directive to give it a reference. Then you can do this.$$[reference].
Update for vue 2
In Vue 2 refs are used for both elements and components: http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced
In Vue.js 2 Inside a Vue Instance or Component:
Use this.$el to get the HTMLElement the instance/component was mounted to
From an HTMLElement:
Use .__vue__ from the HTMLElement
E.g. var vueInstance = document.getElementById('app').__vue__;
Having a VNode in a variable called vnode you can:
use vnode.elm to get the element that VNode was rendered to
use vnode.context to get the VueComponent instance that VNode's component was declared (this usually returns the parent component, but may surprise you when using slots.
use vnode.componentInstance to get the Actual VueComponent instance that VNode is about
Source, literally: vue/flow/vnode.js.
Runnable Demo:
Vue.config.productionTip = false; // disable developer version warning
console.log('-------------------')
Vue.component('my-component', {
template: `<input>`,
mounted: function() {
console.log('[my-component] is mounted at element:', this.$el);
}
});
Vue.directive('customdirective', {
bind: function (el, binding, vnode) {
console.log('[DIRECTIVE] My Element is:', vnode.elm);
console.log('[DIRECTIVE] My componentInstance is:', vnode.componentInstance);
console.log('[DIRECTIVE] My context is:', vnode.context);
// some properties, such as $el, may take an extra tick to be set, thus you need to...
Vue.nextTick(() => console.log('[DIRECTIVE][AFTER TICK] My context is:', vnode.context.$el))
}
})
new Vue({
el: '#app',
mounted: function() {
console.log('[ROOT] This Vue instance is mounted at element:', this.$el);
console.log('[ROOT] From the element to the Vue instance:', document.getElementById('app').__vue__);
console.log('[ROOT] Vue component instance of my-component:', document.querySelector('input').__vue__);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<h1>Open the browser's console</h1>
<div id="app">
<my-component v-customdirective=""></my-component>
</div>
If you're starting with a DOM element, check for a __vue__ property on that element. Any Vue View Models (components, VMs created by v-repeat usage) will have this property.
You can use the "Inspect Element" feature in your browsers developer console (at least in Firefox and Chrome) to view the DOM properties.
Hope that helps!
this.$el - points to the root element of the component
this.$refs.<ref name> + <div ref="<ref name>" ... - points to nested element
💡 use $el/$refs only after mounted() step of vue lifecycle
<template>
<div>
root element
<div ref="childElement">child element</div>
</div>
</template>
<script>
export default {
mounted() {
let rootElement = this.$el;
let childElement = this.$refs.childElement;
console.log(rootElement);
console.log(childElement);
}
}
</script>
<style scoped>
</style>
So I figured $0.__vue__ doesn't work very well with HOCs (high order components).
// ListItem.vue
<template>
<vm-product-item/>
<template>
From the template above, if you have ListItem component, that has ProductItem as it's root, and you try $0.__vue__ in console the result unexpectedly would be the ListItem instance.
Here I got a solution to select the lowest level component (ProductItem in this case).
Plugin
// DomNodeToComponent.js
export default {
install: (Vue, options) => {
Vue.mixin({
mounted () {
this.$el.__vueComponent__ = this
},
})
},
}
Install
import DomNodeToComponent from'./plugins/DomNodeToComponent/DomNodeToComponent'
Vue.use(DomNodeToComponent)
Use
In browser console click on dom element.
Type $0.__vueComponent__.
Do whatever you want with component. Access data. Do changes. Run exposed methods from e2e.
Bonus feature
If you want more, you can just use $0.__vue__.$parent. Meaning if 3 components share the same dom node, you'll have to write $0.__vue__.$parent.$parent to get the main component. This approach is less laconic, but gives better control.
Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for.
For example:
<ul>
<li v-for="(item, key) in items" v-on:click="play(item,$event)">
<a v-bind:ref="'key' + item.id" v-bind:href="item.url">
<!-- content -->
</a>
</li>
</ul>
and in Vue component you can use
var recordingModel = new Vue({
el:'#rec-container',
data:{
items:[]
},
methods:{
play:function(item,e){
// it contains the bound reference
console.log(this.$refs['key'+item.id]);
}
}
});
I found this snippet here. The idea is to go up the DOM node hierarchy until a __vue__ property is found.
function getVueFromElement(el) {
while (el) {
if (el.__vue__) {
return el.__vue__
} else {
el = el.parentNode
}
}
}
In Chrome:
Solution for Vue 3
I needed to create a navbar and collapse the menu item when clicked outside. I created a click listener on windows in mounted life cycle hook as follows
mounted() {
window.addEventListener('click', (e)=>{
if(e.target !== this.$el)
this.showChild = false;
})
}
You can also check if the element is child of this.$el. However, in my case the children were all links and this didn't matter much.
If you want listen an event (i.e OnClick) on an input with "demo" id, you can use:
new Vue({
el: '#demo',
data: {
n: 0
},
methods: {
onClick: function (e) {
console.log(e.target.tagName) // "A"
console.log(e.targetVM === this) // true
}
}
})
Exactly what Kamil said,
element = this.$el
But make sure you don't have fragment instances.
Since in Vue 2.0, no solution seems available, a clean solution that I found is to create a vue-id attribute, and also set it on the template. Then on created and beforeDestroy lifecycle these instances are updated on the global object.
Basically:
created: function() {
this._id = generateUid();
globalRepo[this._id] = this;
},
beforeDestroy: function() {
delete globalRepo[this._id]
},
data: function() {
return {
vueId: this._id
}
}