How to dynamically set mixins for Reactjs Element - javascript

Is there any way to add mixins programmatically for an ReactElement?
For example
var mymixins = {
showLayout1(){
return "Layout1"
}
}
var comp = React.createClass({
onComponentWillMount(){
// dynamically add mixins "mymixins" to this element
addMixins(this, mymixins);
},
render(){
return this.showLayout1()
}
});
The reason I'm looking for this kind of function is because Reactjs Component does not accept 2 mixins with the same method name.

Related

What do you call this React Component pattern?

I am having a hard time finding some documentation on this pattern.
Does it have a name?
TextBase is a styled component. so I can extend it as following:
Text.H1 = withComponent('h1') however I want html attributes to be passed as well. Hence the function component. However when I extend my Text component the props are being overridden, resulting with all components being h1's.
const Text = (props) => {
const { children, testid, ...rest } = props;
return <TextBase data-testid={testid} {...rest}>{children}</TextBase>
}
Text.defaultProps = {color: 'red'}
Text.H1 = Text
Text.H1.defaultProps = { as: 'h1'}
Text.H2 = Text
Text.H2.defaultProps = { as: 'h2'}
Text.H3 = Text
Text.H3.defaultProps = { as: 'h3'}
🙏🏽
Try binding the function call using this or use arrow function or manually bind the function using bind. I am sure it will work. Reference fault is all this is

How to add class to Vue component via $refs

I need to add class name to some Vue components using their ref names. The ref names are defined in a config file. I would like to do it dynamically, to avoid adding class manually on each Vue component.
I have tried to find each component using $refs and if found, add the class name to the element's class list. The class is added, but it is removed as soon as user interaction begins (e.g. the component is clicked, receives new value etc.)
Here is some sample code I've tried:
beforeCreate() {
let requiredFields = config.requiredFields
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
You can use this:
this.$refs[field].$el.classList.value = this.$refs[field].$el.classList.value + 'my-class'
the only thing that you need to make sure of is that your config.requiredFields must include the ref name as a string and nothing more or less ... you can achieve that with :
//for each ref you have
for (let ref in this.$refs) {
config.requiredFields.push(ref)
}
// so config.requiredFields will look like this : ['one','two]
here is an example of a working sample :
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('one', {
template: '<p>component number one</p>'
})
Vue.component('two', {
template: '<p>component number two</p>'
})
new Vue({
el: "#app",
beforeCreate() {
let requiredFields = ['one','two'] // config.requiredFields should be like this
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
})
.my-class {
color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<one ref="one" ></one>
<two ref="two" ></two>
</div>
I know this question was posted ages ago, but I was playing around with something similar and came across a much easier way to add a class to $refs.
When we reference this.$refs['some-ref'].$el.classList it becomes a DOMTokenList which has a bunch of methods and properties you can access.
In this instance, to add a class it is as simple as
this.$refs['some-ref'].$el.classList.add('some-class')
You've to make sure classList.value is an array. By default its a string.
methods: {
onClick(ref) {
const activeClass = 'active-submenu'
if (!this.$refs[ref].classList.length) {
this.$refs[ref].classList.value = [activeClass]
} else {
this.$refs[ref].classList.value = ''
}
},
},
this post helped me tremendously. I needed to target an element within a v-for loop and I ended up writing a little method for it (i'm using Quasar/Vue).
hopefully this will save someone else some time.
addStyleToRef: function(referEl, indexp, classToAdd) {
//will add a class to a $ref element (even within a v-for loop)
//supply $ref name (referEl - txt), index within list (indexp - int) & css class name (classToAdd txt)
if ( this.$refs[referEl][indexp].$el.classList.value.includes(classToAdd) ){
console.log('class already added')
} else {
this.$refs[referEl][indexp].$el.classList.value = this.$refs[referEl][indexp].$el.classList.value + ' ' + classToAdd
}
}
let tag = this.$refs[ref-key][0];
$(tag).addClass('d-none');
Simply get the tag with ref let tag = this.$refs[ref-key][0]; then put this tag into jquery object $(tag).addClass('d-none'); class will be added to required tag.

Better way of applying css-modules to AngularJS

I'm using css-modules with AngularJS 1.5 components now. The stack is webpack + css-loader?modules=true + angular 1.5 components + pug.
Currently I have to do the following steps to use css modules in my pug template.
// my-component.js
import template from 'my-component.pug';
import styles from 'my-component.css';
class MyComponent {
constructor($element) {
$element.addClass('myComponent'); // ------ (1)
this.styles = styles; // ------ (2)
}
}
angular.module(name, deps)
.component('my-component', {
controller: MyComponent,
template: template,
});
// my-component.pug
div(class={{ ::$ctrl.styles.fooBar }}) FooBar // ----- (3)
// my-component.css
.myComponent { background: green; }
.fooBar { color: red; }
There are two problems:
Every component has to inject $element and set its class name manually. The reason for doing this is, AngularJS component tag itself exists in the result HTML without any classes, which makes CSS difficult. For example, if I use MyComponent above like this:
<div>
<my-component></my-component>
</div>
it will generate the following HTML:
<div>
<my-component>
<div class="my-component__fooBar__3B2xz">FooBar</div>
</my-component>
</div>
Compared to ReactJS, <my-component> in above result HTML is an extra, sometimes it makes CSS difficult to write.
So my solution is (1), to add a class to it.
The class in template is too long (3). I know it is the correct way to reference $ctrl.styles.fooBar but this is way too long.
My ideal solution would be like this:
// my-component.js
angular.module(name, deps)
.component('my-component', {
controller: MyComponent,
template: template,
styles: styles,
});
// my-component.css
div(css-class="fooBar") FooBar
The idea is to:
make angular.module().component support an extra styles attribute, which will automatically do (2) this.styles = styles; in the controller, and apply (1) $element.addClass() as well.
directive css-class to apply $ctrl.styles to element.
My question is, I have no idea how to implement idea 1 above (2 is easy). I appreciate if anyone could share some light on this.
I came up with a solution which I don't quite satisfy with.
Angular component can accept a function as template and inject with $element.
doc
If template is a function, then it is injected with the following locals:
$element - Current element
$attrs - Current attributes object for the element
Therefore we could attach the main class for component (.myComponent) in the template function, then regex replace all the occurance of class names with compiled class names.
// utils.js
function decorateTemplate(template, styles, className) {
return ['$element', $element => {
$element.addClass(styles[className]);
return template.replace(/\$\{(\w+)\}/g, (match, p1) => styles[p1]);
}];
}
// my-component.js
import style from './my-component.css';
import template from './my-component.pug';
import { decorateTemplate } from 'shared/utils';
class MyComponent {
// NO NEED to inject $element in constructor
// constructor($element) { ...
}
angular.module(name, deps)
.component('myComponent', {
// decorate the template with styles
template: decorateTemplate(template, styles, 'myComponent'),
});
// my-component.pug, with special '${className}' notation
div(class="${fooBar}") FooBar
There are still one place to be improved that decorateTemplate uses regex replacement and template has to use a special notation ${className} to specify the css-modules class names.
Any suggestions are welcome.
UPDATE
I updated my decorateTemplate() function to leverage pug features, so that local class names can be written as ._localClassName.
// utils.js
function decorateTemplate(template, styles, className) {
return ['$element', ($element) => {
$element.addClass(styles[className]);
return template.replace(/\sclass="(.+?)"/g, (match, p1) => {
let classes = p1.split(/\s+/);
classes = classes.map(className => {
if (className.startsWith('_')) {
let localClassName = className.slice(1);
if (styles[localClassName]) {
return styles[localClassName];
} else {
console.warn(`Warning: local class name ${className} not found`);
return className;
}
} else {
return className;
}
});
return ' class="' + classes.join(' ') + '"';
});
}];
}
// my-component.pug
._fooBar FooBar
Although this is much easier it does not eliminate the quirky notation (begin with _ for local class names) and regex replace.
Any suggestions are welcome.

react - accessing DOM without breaking encapsulation

Is there a cannonical way of going about doing something like the following without breaking encapsulation?
import React, { Component, PropTypes } from 'react';
class Dashboard extends Component {
constructor(props, context) {
super(props, context);
this.setRef = ::this.setRef;
}
componentDidMount() {
const node = ReactDOM.findDOMNode(this.someRef);
const newHeight = window.innerHeight - node.offsetTop - 30;
node.style.maxHeight = `${newHeight}px`;
}
render() {
return (
<div id="some-element-id" ref={this.setRef}>
</div>
);
}
setRef(ref) {
this.someRef= ref;
}
}
ReactDOM.findDOMNode seems to be the suggested way of going about this, but this still breaks encapsulation and the documentation has a big red flag to this extent.
You should use the component "state" to set the style property of the react element, so you only access the "real" DOM node to calculate the height and then you update the state, re-rendering the component. The react component now has the same information as the real DOM node, so it shouldn't be breaking encapsulation.
Using vanilla JS to provide an example:
var Component = React.createClass({
getInitialState: function(){
return {
style: {},
}
},
componentDidMount: function(){
var node = ReactDOM.findDOMNode(this);
var newHeight = window.innerHeight - node.offsetTop - 30;
this.setState({style: {backgroundColor: '#bbb', height: newHeight.toString() + 'px' }});
},
render: function(){
return React.createElement('div', {style: this.state.style}, 'Height: ' + this.state.style.height);
},
});
You can see it running in this fiddle.
While this technically "breaks encapsulation" in the general sense, if there is no other way to do it (and in this case there is not), then using findDOMNode is your only choice and it is the correct one.
If you find yourself using it repeatedly, you should create a wrapper component to encapsulate that behavior.

How can I pass props/context to dynamic childrens in react?

I am using react, and I am trying to pass props/context to my dynamic childrens,
by dymamic childrens I mean childrens are render using
{this.props.children}
How can I pass to this children (In my code I know it's type) context/props?
In this jsbin there is an example that it dosen't work on dynamic childrens.
http://jsbin.com/puhilabike/1/edit?html,js,output
Though #WiredPrairie's answer is correct, the React.addons.cloneWithProps is deprecated as of React v0.13RC. The updated way to do this is to use React.cloneElement. An example:
renderedChildren = React.Children.map(this.props.children, function (child) {
return React.cloneElement(child, { parentValue: self.props.parentValue });
});
There's not a a great way to do this that is clear and passing all the properties of the parent isn't a great pattern and could lead to some very difficult to follow code if not done carefully (and with excellent documentation). If you have a subset of properties though, it's straightforward:
JsFiddle
Assuming you're using React with Addons, you can clone the children of a React component and set new property values on them. Here, the code just copies a property called parentValue into each child. It needs to create a clone of each element as the child element had already been created.
var Hello = React.createClass({
render: function() {
var self = this;
var renderedChildren = React.Children.map(this.props.children,
function(child) {
// create a copy that includes addtional property values
// as needed
return React.addons.cloneWithProps(child,
{ parentValue: self.props.parentValue } );
});
return (<div>
{ renderedChildren }
</div>)
;
}
});
var SimpleChild = React.createClass({
render: function() {
return <div>Simple { this.props.id }, from parent={ this.props.parentValue }</div>
}
});
React.render((<Hello parentValue="fromParent">
<SimpleChild id="1" />
<SimpleChild id="2" />
</Hello>), document.body);
Produces:
Simple 1, from parent=fromParent
Simple 2, from parent=fromParent
Spreading props on DOM elements
https://github.com/vasanthk/react-bits/blob/master/anti-patterns/07.spreading-props-dom.md
When we spread props we run into the risk of adding unknown HTML
attributes, which is a bad practice.
const Sample = () => (<Spread flag={true} domProps={{className: "content"}}/>);
const Spread = (props) => (<div {...props.domProps}>Test</div>);

Categories

Resources