Issue:
I have a lot of small helper functions that don't necessarily need to live in a component(or maybe they can but they will make that component bloated with a lot of code).My lazy side just wants to just let those all just be some sort of global functions that the components can call.I really want to make good ReactJs code.
Question:
What are the best practices in terms of global helper functions in Reactjs? Should I force them into some sort of component or just shove them into the other components?
Basic Example:
function helperfunction1(a, b) {
//does some work
return someValue;
}
function helperfunction2(c, d) {
//does some work
return someOtherValue;
}
function helperfunction3(e, f) {
//does some work
return anotherValue;
}
function helperfunction4(a, c) {
//does some work
return someValueAgain;
}
var SomeComponent =
React.createClass({
//Has bunch of methods
//Uses some helper functions
render: function () {
}
});
var SomeOtherComponent =
React.createClass({
//Has bunch of methods
//Uses some helper functions
render: function () {
}
});
You can export multiple functions from a file, no React needed per se:
Helpers.js:
export function plus(a, b) {
return a + b;
}
export function minus(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export function divide(a, b) {
return a / b;
}
You can then import the functions you need:
import { multiply, divide } from './Helpers'
You can use a module-bundling tool like Webpack or Browserify for that.
Put your reusable functions in a CommonJS module.
Do not use Mixins, they will probably be deprecated in next versions of React as there's no standard way to declare mixins in React with ES6 syntax and they prefer to wait for ES7 that will probably standardize mixins. And there's no point coupling your reusable code to React unless it uses React lifecycle's methods.
You can use modulejs.
or you can use mixins (https://facebook.github.io/react/docs/reusable-components.html#mixins)
Sample for mixins: https://jsfiddle.net/q88yzups/1/
var MyCommonFunc = {
helperFunction1: function() {
alert('herper function1');
},
doSomething: function(){
alert('dosomething');
}
}
var Hello = React.createClass({
mixins: [MyCommonFunc],
render: function() {
this.doSomething();
return <div onClick={this.helperFunction1}>Hello {this.props.name} </div>;
}
});
React.render(<Hello name="World" />, document.getElementById('container'));
Just another option, if you don't want to split into a separate module, you could create a private method in your parent component like below and use freely within this component or pass to the child components via props..
var YourComponent = React.createClass({
globalConfig: function() {
return {
testFunc: function () {
console.log('testing...');
},
};
}(),
......
render: function() {
this.globalConfig.testFunc(); // use directly
<ChildComponent testFunc={this.globalConfig.testFunc} /> // pass to child
.....
All untested, but that's the idea...
Use a React context to do something like this. It's built for this exact use case;
Doc: https://reactjs.org/docs/context.html
react can be avoided altogether, like Michiel says
though a slight improvement would be to put all those pure js functions in a single fle then connect it to your html start page and the functions will be available everywhere:
just with a
<script src='./global-func.js' ></script>
its a dirty hack but it works ;)
you wont have to import the file into every component class you make
Related
I have a simple question.
I want to run the function I created in App.js in a different screen.
Each screen is connected to an AppContainer in context.
I'm going to use state on another screen, but I do not know how to do it.
Can you give me a simple example?
_setDefaultLocation = locationKey => {
console.log("call _setDefaultLocation function");
this.setState({ defaultLocation: locationKey });
console.log(this.state.defaultLocation);
this._getCurrent(locationKey);
};
The above function is called this.props._setDefaultLocation (); I tried to do this and it did not work.
import React from "react";
const WeatherContext = React.createContext();
function withWeather(WrappedComponent) {
return function withContext(props) {
return (
<WeatherContext.Consumer>
{value => <WrappedComponent value={value} {...props} />}
</WeatherContext.Consumer>
);
};
}
export { WeatherContext, withWeather };
Context is used to make it like that.
You can use global keyword to make your function global. Then you can use it in any screen.
For example, assume that you have a function is called 'myFunction'. You can implement your function in App.js as following.
global.myFunction = () => {
console.warn('I am a global function...')
// your function body
};
Then you can call this function in any screen just like other function calling..
In your case your function can implement in this format.
global._myFunction = locationKey => {
console.warn('I am a global function...')
// your function body
};
And when you are calling the function call it as _myFunction(locationKey).
NOTE: 'locationKey' is the parameter that you should pass for your function.
Think this solution will help you.
In our polymer1.0 component, we had this:
attached: function ()
{
var something=this.getComputedStyleValue("--flxs-" + prop)
this.async(function()
{
// do some work here.
});
}
In 2.0, both this.getComputedStyleValue this.async is not defined.
Any idea what is the alternative?
Polymer.Async seems like the alternate to this.async.
For getComputedStyleValue you can try StyleGather, but i doubt it'll work.
Otherwise, LegacyMixin contains all the old methods.
This question is specific to vuejs router, however may simply be a misunderstanding of importing js objects and assigning to the window object.
I am watching for url changes on a page which works fine with the watcher code in the component file. I need to use the same watcher code for multiple components so I extracted it to its own file, assigned it to the global scope, and cannot get it to work. Here are the details:
Working code in with the watcher in the component:
watch:{
$route () {
console.log('route changed')
//was it a reset?
console.log( this.$route.query.sort)
if(this.$route.query.sort === undefined){
if(this.$route.meta.reset){
//reset was pressed... actually do nothing here
this.$route.meta['reset'] = false;
}
else{
this.loading = true;
this.searchableTable.removeResultsTable();
this.searchableTable.options.search_query = this.$route.fullPath;
this.searchableTable.updateSearchPage();
}
}
else
{
//sort change just update the table view
}
}
}
So then I extracted the watch to a file routeWatcher.js:
export default {
$route () {
console.log('route changed')
//was it a reset?
console.log(this.$route.query.sort)
if (this.$route.query.sort === undefined) {
if (this.$route.meta.reset) {
//reset was pressed... actually do nothing here
this.$route.meta['reset'] = false;
}
else {
this.loading = true;
this.searchableTable.removeResultsTable();
this.searchableTable.options.search_query = this.$route.fullPath;
this.searchableTable.updateSearchPage();
}
}
else {
//sort change just update the table view
}
}
}
then I import and use, which works fine....
import searchableTableRouteWatcher from '../../controllers/routeWatcher'
...
watch:searchableTableRouteWatcher
again works fine.
Now the problem - I want to avoid the import in multiple files, so I thought I could put it on the window as a global
in my main.js file:
import searchableTableRouteWatcher from './controllers/routeWatcher'
window.searchableTableRouteWatcher = searchableTableRouteWatcher;
Then in my component:
watch:searchableTableRouteWatcher
results in searchableTableRouteWatcher is not defined
watch:window.searchableTableRouteWatcher
results in no errors, but the code is not being called
I have a feeling it has to do with this and there is confusion on $route()
For your purpose there are 'Mixins' in Vue.js: documentation
What you can do:
create a file, say mixins/index.js:
export const routeWatcher = {
watch: {... your watcher code pasted here ... }
};
import into your component:
import { routeWatcher } from 'path/to/mixins/index';
add mixin to your component properties and methods:
<script>
export default {
mixins: [routeWatcher];
data () ...... all your original component's script content
}
Mixin's content will be merged with component's original properties and act if it was hardcoded there.
Addition after your comment:
You can also declare Mixin globally, like this:
above 'new Vue' declaration put this code:
Vue.mixin({
watch: {....}
});
This mixin will appear in every component.
How can I use behaviors in Polymer element build with ES6 class ? I was trying to use existing ES5 behavior with newly defiend ES6 element but I'm getting 'error TyperError: this.log is not a function' when I'm calling function defined in behavior.
<!DOCTYPE html>
<html>
<head>
<base href="https://polygit.org">
<script src="/components/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="/components/polymer/polymer.html">
</head>
<body>
<my-element id="myElement" verbose></my-element>
</body>
<script>
(function() {
'use strict';
// Behavior (ES5)
var myBehaviors = myBehaviors || {};
myBehaviors.VerboseBehavior = {
properties: {
verbose: {
type: Boolean,
value: false
}
},
log: function(msg) {
if (!this.verbose) {
return;
}
if (this.id) {
console.log(this.localName + ' (#' + this.id + '): ' + msg);
} else {
console.log(this.localName + ': ' + msg);
}
}
};
// Element (ES6)
class myElement {
beforeRegister() {
this.is = 'my-element';
this.behaviors = [
myBehaviors.VerboseBehavior
];
this.properties = {
hello: {
type: String,
value: 'world'
}
};
}
ready() {
this.log('ready'); // error TypeError: this.log is not a function
}
attached() {
this.say();
}
say() {
console.log(this.hello);
}
};
Polymer(myElement);
})();
</script>
</html>
Define the behaviors with a getter.
(function() {
'use strict';
let SkeletonPolymerBehavior = {
// Behavior definition
};
class SkeletonPolymer {
// Define behaviors with a getter
get behaviors() {
return [SkeletonPolymerBehavior];
}
// Element setup goes here instead of created() callback
beforeRegister() {}
// Define other lifecycle methods as you need
registered() {}
created() {}
ready() {}
factoryImpl() {}
attached() {}
detached() {}
attributChanged(){}
}
Polymer(SkeletonPolymer);
)();
Note that the answer given by leodido will not work if you use extended behaviors. Extended behaviors in Polymer are arrays of behaviors themselves. I don't know what's going on exactly but apparently Polymer is parsing the protoype of your class and flattening the behavior array, writing the flattened version back to your prototype. The fixed getter function will ignore the flattened version and always return the deep array. That will not do. So you have two options.
You can flatten the array yourself:
get behaviors() {
return [
MyFlatBehavior, MyExtendedBehavior
].reduce(function(a, b) {return a.concat(b);});
}
However, since Polymer may call get behaviors a lot (it does in my case), this will introduce a minor performance penalty. The alternative is letting it set the behaviors:
let SkeletonPolymerBehavior = [MyFlatBehavior, MyExtendedBehavior];
class SkeletonPolymer {
get behaviors( ) {return SkeletonPolymerBehavior;}
set behaviors(b) {SkeletonPolymerBehavior = b;}
...
}
Finally you can flatten the array right away:
let SkeletonPolymerBehavior = [
MyFlatBehavior, MyExtendedBehavior
].reduce(function(a, b) {return a.concat(b);});
class SkeletonPolymer {
get behaviors( ) {return SkeletonPolymerBehavior;}
...
}
Note that my flatten function is not safe! It worked in my case, but it will fail when extending extended behaviors and possibly in other cases. You definitely need something more sophisticated there. If you don't want to bother (and I see no compelling reason to) you should definitely go with the second version, letting Polymer set the behaviors.
Final note: I tried abstracting that away into an es2015 Behaviors mixin. However, Polymer's prototype magic does not catch stuff inherited from base classes and the getters and setters defined there are never called.
However, it is possible to mess with the prototype the es5 way. This utility function will ease the addition of extended behaviors:
function addBehaviors(clas, ...behaviors) {
let bhv = behaviors;
Object.defineProperty(clas.prototype, "behaviors", {
get:function( ) {return bhv;},
set:function(b) {return bhv = b;}
});
}
Usage:
class SkeletonPolymer {...}
addBehaviors(SkeletonPolymer, MyFlatBehavior, MyExtendedBehavior);
I want to do something like this (but obviously not this exactly, because this function doesn't work this way)
angular.bootstrap( $("#myelement"), ['myModule'], {foo: bar} );
I want to pass in a configuration object, since we may want to have more than one instance of the app on a page, with different settings, etc. All I can think of are ugly workarounds. I'm thinking the best thing would be to override an "Options" service of my own making, but I still can't figure out the proper way to do that (tersely).
Thanks in advance!
How about you try something like this:
angular.module('configFoo', []).run(function() {});
angular.module('configBar', []).run(function() {});
angular.bootstrap(myEl, ['myModule', 'configFoo']);
angular.bootstrap(myOtherEl, ['myModule', 'configBar']);
http://docs.angularjs.org/api/angular.Module for all available module methods (you're probably only interested in .run() and .config())
Here is a working code:
http://jsfiddle.net/x060aph7/
angular.module('myModule', [])
.controller('myController', function($scope,myConfig) {
$scope.name = 'inst '+myConfig.foo;
})
;
var aConfig = [{foo:1},{foo:2},{foo:3}];
aConfig.forEach(function(config){
angular.module('fooConfig',[]).value('myConfig', config);
angular.bootstrap(getDiv(), ['myModule','fooConfig']);
});
function getDiv(){
var mDiv = document.createElement('div');
mDiv.setAttribute('ng-controller','myController');
mDiv.innerHTML = '<span>{{name}}</span>';
document.body.appendChild(mDiv);
return mDiv;
}
The following example helped us out bootstrapping a widget to a page. First a div is made - with a bit of jQuery - for the widget to load a template with an ng-include, it is controlled by WidgetLogoController. Next a module WidgetConfig is created that holds the widget's configuration.
$('#pageWidget').html(`<ng-include src="'/dist/templates/widgetLogo.html'"></ng-include>`)
.attr('ng-controller','WidgetLogoController');
var widgetConfig = {
'widgetId': data.pageWidgetId,
'areaId': data.area,
'pageId': data.pageId
};
angular.module('WidgetConfig', []).value('WidgetConfig', widgetConfig);
angular.bootstrap(document.getElementById('pageWidget'), ['Widget', 'WidgetConfig']);
Widget module includes the WidgetConfig configuration but also has a spot for it own in CONFIG:
(function (window, angular) {
'use strict';
window.app = angular.module('Widget', ['ngFileUpload', 'WidgetConfig'])
.constant('CONFIG', {
BASE_URL: 'http://osage.brandportal.com/'
});
})(window, angular);
WidgetController can access CONFIG and WidgetConfig.
(function (app) {
'use strict';
app.controller('WidgetLogoController', ['CONFIG', 'WidgetConfig',
function(CONFIG, WidgetConfig){
console.log('---WidgetLogoController');
console.log('CONFIG', CONFIG);
console.log('WidgetConfig', WidgetConfig);
}]);
}(app));
What about:
Load config and than load angular:
angular.element(document).ready(() => {
$.get('config', // url to my configuration
{},
function (data) {
window.config = data;
angular.bootstrap(document, ['myApp']);
}
);
});
Access the config:
angular.module('myApp').run(myAppRun);
function myAppRun($window) {
$window.config; // here I have config
}