I have a situation to convert several jQuery components to VueJs.
In general, I know what to do, but in some cases, I need to replace some functions calls.
For instance:
Component
const Component = (function () {
const initialize = () => {
return 'Tony Stark'
}
return {
initialize: initialize
}
})
export default Component
Random file, using exported function
$( document ).ready(function() {
Component.initialize()
});
What is the best solution to Component.initialize() still working?
Because I have this request in several files.
I got a solution:
import Component from './component'
// Call method
Component.methods.method()
You may import the component to every Vue component and use it like this:
import someComponent from './someComponent'
export default {
created () {
Component.initialize()
}
}
Or you could use instance properties, see https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html
Related
I am working on my first Vue project. I'm used to React and vanilla js, but just getting my head around a few concepts in Vue here.
In particular, importing state and action props from a Pinia store, and seemingly having to import those multiple times in a single Vue component (something I don't need to do in React).
In this example, I am importing a simple count value, and an increment function, and trying to use these in a few different places:
<script setup>
// I import everything initially in setup, which works fine,
// and these props (currentCount and incrementCount)
// can be used in my template:
import { storeToRefs } from 'pinia';
import { useStore } from '#/stores/store';
const { currentCount } = storeToRefs(useStore());
const { incrementCount } = useStore();
</script>
<template>
<main>
Current count: {{ currentCount }}
<button #click="incrementCount">Increment</button>
</main>
</template>
<script>
// I can't use store values from setup here.
// This doesn't work:
// console.log(currentCount);
// I also can't import store values here.
// I get the following error:
// "getActivePinia was called with no active Pinia"
// const { currentCount } = storeToRefs(useStore());
export default {
mounted() {
// I have to import store values here for them to work:
const { currentCount } = storeToRefs(useStore());
console.log(currentCount);
},
watch: {
// weirdly, this reference to watching "currentCount" works:
currentCount() {
// I also have to import store values here for them to work:
const { currentCount } = storeToRefs(useStore());
console.log(currentCount);
},
},
};
</script>
As you can see, if I want to use store values in my template, on mount, and in a watcher (whereby I'd use React's useEffect hook) I am having to import the store props 3 times in total.
Is this correct / normal? Is there a simpler way to achieve what I'm doing, where I only import props once? I want to be sure I haven't missed something and am not doing something in an unusual way.
Thanks for any help and advice!
Pinia was designed with Composition API in mind.
So its intended usage is inside setup() function, where you'd only import it once.
To use it outside of a setup() function, you have two main routes:
inside components, you can just return it from setup() and it becomes available in any hook/method/getter. Either as this.store or spread:
import { useStore } from '#/store'
import { toRefs } from 'vue'
// or from '#vue/composition-api' in Vue2
export default {
setup: () => ({ ...toRefs(useStore()) })
}
/* this makes every state prop, getter or action directly available
on current component instance. In your case, `this.currentCount`.
Obviously, you can also make the entire store available as `this.someStore`:
setup: () => ({ someStore: useSomeStore() })
// now you can use `this.someStore` anywhere
*/
a more general approach is to export the pinia instance (returned by createPinia()), from main.(js|ts), import it where you need the store and then call useStore() passing the pinia instance as an argument.
This can be done anywhere, even outside of components.
Generic example:
import { pinia } from 'main.js'
import { useSomeStore } from '#/store'
const someStore = useSomeStore(pinia);
I should probably also mention the mapState helper provided by pinia. It allows you to select only a few of the keys exposed to current instance. Example:
import { mapState } from 'pinia'
// ...
computed: {
...mapState(useSomeStore, [ 'currentCount'])
}
// Now `this.currentCount` is available
Note: mapState is weirdly named, as it allows you to access more than just state props (also getters and actions). It was named mapState to match the similar helper from vuex.
An even more general approach is to add your store as global, using the plugin registration API in Vue2:
import { useSomeStore } from '#/store';
import { createPinia } from 'pinia';
const pinia = createPinia();
const someStorePlugin = {
install(Vue, options) {
Vue.prototype.someStore = useSomeStore(options.pinia)
}
};
Vue.use(someStorePlugin, { pinia });
new Vue({ pinia });
After this, every single component of your Vue instance will have this.someStore available on it, without you needing to import it.
Note: I haven't tested adding a store in globals (and I definitely advise against it - you should avoid globals), but i expect it to work.
If you want to combine pinia stores with the options API, one way to do it is to use the setup() function inside the options to call useStore:
<script>
import { useStore } from '#/stores/store';
export default {
setup() {
const store = useStore();
return {store}
},
watch: {
store.currentBrightness(newVal, oldVal){
// your code
}
},
methods: {
// inside methods use this.store
},
mounted() {
console.log(this.store.currentCount);
}
}
</script>
Some might consider this as a unwanted mix of composition and options API, but in my view it is a quite good solution for pinia stores.
Nechoj, has the most straightforward answer. Also if you have multiple stores you can always import the stores as necessary into a parent component then use inject just add some parts. For example I have a route data that is called via an api, I don't need it everywhere all the time so i call it in a parent then use inject to use those routes in a drop down that might be a great grandchild component. I don't need that whole utils store just the routes.
index page:
import { useUtilsStore } from "src/stores/utilsStore";
const passengerRoutes = computed(() => utilsStore.getPassengerRoutes);
provide("passengerRoutes", passengerRoutes);
grandchild component:
const compRoutes = inject("passengerRoutes");
I have multiple components which all need to do the same thing. (A simple function which maps over their child components and does something to each one). At the moment I am defining this method in each of the components. But I only want to define it once.
I could define it in the top level component and then pass it down as a prop. But that doesn't feel quite right. It is more a library function than a prop. (It seems to me).
What is the correct way of doing this?
Utils.js with latest Javascript ES6 syntax
Create the Utils.js file like this with multiple functions, etc
const someCommonValues = ['common', 'values'];
export const doSomethingWithInput = (theInput) => {
//Do something with the input
return theInput;
};
export const justAnAlert = () => {
alert('hello');
};
Then in your components that you want to use the util functions, import the specific functions that are needed. You don't have to import everything
import {doSomethingWithInput, justAnAlert} from './path/to/Utils.js'
And then use these functions within the component like this:
justAnAlert();
<p>{doSomethingWithInput('hello')}</p>
If you use something like browserify then you can have an external file i.e util.js that exports some utility functions.
var doSomething = function(num) {
return num + 1;
}
exports.doSomething = doSomething;
Then require it as needed
var doSomething = require('./util.js').doSomething;
If you want to manipulate state in helper functions follow this:
Create a Helpers.js file:
export function myFunc(){ return this.state.name; //define it according to your needs }
Import helper function in your component file:
import {myFunc} from 'path-to/Helpers.js'
In your constructor add that helper function to the class
constructor(){ super() this.myFunc = myFunc.bind(this) }
In your render function use it:
`render(){
{this.myFunc()}
}`
Here are some examples on how you can reuse a function (FetchUtil.handleError) in a React component (App).
Solution 1: Using CommonJS module syntax
module.exports = {
handleError: function(response) {
if (!response.ok) throw new Error(response.statusText);
return response;
},
};
Solution 2: Using "createClass" (React v16)
util/FetchUtil.js
const createReactClass = require('create-react-class');
const FetchUtil = createReactClass({
statics: {
handleError: function(response) {
if (!response.ok) throw new Error(response.statusText);
return response;
},
},
render() {
},
});
export default FetchUtil;
Note: If you are using React v15.4 (or below) you need to import createClass as follows:
import React from 'react';
const FetchUtil = React.createClass({});
Source: https://reactjs.org/blog/2017/04/07/react-v15.5.0.html#migrating-from-reactcreateclass
Component (which reuses FetchUtil)
components/App.jsx
import Categories from './Categories.jsx';
import FetchUtil from '../utils/FetchUtil';
import Grid from 'material-ui/Grid';
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {categories: []};
}
componentWillMount() {
window
.fetch('/rest/service/v1/categories')
.then(FetchUtil.handleError)
.then(response => response.json())
.then(categories => this.setState({...this.state, categories}));
}
render() {
return (
<Grid container={true} spacing={16}>
<Grid item={true} xs={12}>
<Categories categories={this.state.categories} />
</Grid>
</Grid>
);
}
}
export default App;
I'll show two styles below, and you'll want to choose depending on how much the components' logic relate to each other.
Style 1 - Relatively related components can be created with callback references, like this, in ./components/App.js...
<SomeItem
ref={(instance) => {this.childA = instance}}
/>
<SomeOtherItem
ref={(instance) => {this.childB = instance}}
/>
And then you can use shared functions between them like this...
this.childA.investigateComponent(this.childB); // call childA function with childB as arg
this.childB.makeNotesOnComponent(this.childA); // call childB function with childA as arg
Style 2 - Util-type components can be created like this, in ./utils/time.js...
export const getTimeDifference = function (start, end) {
// return difference between start and end
}
And then they can be used like this, in ./components/App.js...
import React from 'react';
import {getTimeDifference} from './utils/time.js';
export default class App extends React.Component {
someFunction() {
console.log(getTimeDifference("19:00:00", "20:00:00"));
}
}
Which to use?
If the logic is relatively-related (they only get used together in the same app), then you should share states between components. But if your logic is distantly-related (i.e., math util, text-formatting util), then you should make and import util class functions.
Another solid option other than creating a util file would be to use a higher order component to create a withComponentMapper() wrapper. This component would take in a component as a parameter and return it back with the componentMapper() function passed down as a prop.
This is considered a good practice in React. You can find out how to do so in detail here.
Sounds like a utility function, in that case why not put it in a separate static utility module?
Otherwise if using a transpiler like Babel you can make use of es7's static methods:
class MyComponent extends React.Component {
static someMethod() { ...
Or else if you are using React.createClass you can use the statics object:
var MyComponent = React.createClass({
statics: {
customMethod: function(foo) {
return foo === 'bar';
}
}
However I don't advise those options, it doesn't make sense to include a component for a utility method.
Also you shouldn't be passing a method down through all your components as a prop it will tightly couple them and make refactoring more painful. I advise a plain old utility module.
The other option is to use a mixin to extend the class, but I don't recommend that as you can't do it in es6+ (and I don't see the benefit in this case).
Shouldn't you use a Mixin for this ? See https://facebook.github.io/react/docs/reusable-components.html
Although they are falling out of favour see https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750
Might be useful
I'm using React, and I have something like this in my code:
renderDetails.js:
export default renderDetails = (details) => {
// function logic removed for brevity
}
Then, in the same folder, I have another source file from where I want to import it, and I do something like this:
businessDetails.js:
import renderDetails from './renderDetails';
// rest removed for brevity
But, I get an error message pointing to my renderDetails.js file and says: "rederDetails is not defined". Any ideas what the problem might be and how to fix it?
The problem is that even though you are exporting the component as default you are giving it a name which is not defined
You can either do
export default (details) => {
}
or
const renderDetails = (details) => {
}
export default renderDetails;
One more thing, when you are trying to render components, make sure that their name starts with a Uppercase character.
try that way.
functions.jsx
export function renderDetails(details) => {
// function logic removed for brevity
}
then import like,
import { renderDetails } from './functions';
P.S.
./ is for if both files a re in same namespace/folder.
You can also write them like this:
export const exampleFunctionOne = () => {}
export const exampleFunctionTwo = () => {}
Then import the individual functions you require from said file like this:
import { exampleFunctionOne, exampleFunctionTwo } from './exampleFile';
I like this approach when wanting to combine similar functions into one file such as validators or filters making them easy to import and use across your application.
I am using Webpack 2 and importing my components via special require syntax.
There are over 100 components, but only 5-10 used at a time. Most of them (but not all) partially have same functionality like props and lifecycle hooks.
Here is code:
// app.js
...
Vue.component("foo", resolve => {
require(['./components/foo.vue'], resolve);
});
...
I want to apply mixin to async component, but how to do that? Global mixin apply to all components, but that's not what I need.
I found that feature request, but it closed.
I found some creepy(?) solution, but it works:
// mixins.js
export default class Mixins {
static fooMixin() {
return {
created: function () {
console.log('mixin hook called');
}
}
}
}
// app.js
Vue.component("foo", resolve => {
require(['./components/foo.vue'], resolve);
});
// foo.vue
<script>
import Mixins from "mixins";
export default {
...
mixins: [Mixins.fooMixin()]
}
</script>
But I hope that there is a more elegant solution.
I'm separating my code by writing a function in another .js file.
The problem is that I don't have the access to my redux connect function since I'm not using a class in my new .js file.
To clear things up:
This is the function (barChange) inside my class which I want to move in another js file
#connect((store) => {
return {
bar_change:store.bar_change
};
})
export default class Bardoctor extends React.Component {
constructor(props) {
super(props);
}
barChange(arg0){
//function code...
this.props.dispatch(user.changeBarNum(arg0,"bar_change_doctor"));
this.props.dispatch(user.sendBarDataDoctor("doctor_bar_send",temp_arr, flag_it, arg0));
}
In order to use my user. functions I have to have the this.props.dispatch.
How would I use this.props.dispatch if I pack the barChange function in another .js file and export the function and call it in my class?
For example:
import * as user from "../actions/asyncCAll.js"
export function barChange(arg0, prop_arr, prop_next, string_change, string_send){
//function code...
dispatch(user.changeBarNum(arg0,string_change));
dispatch(user.sendBarDataPatient(string_send, temp_arr, flag_it, arg0));
}
You can import store to your new js file and use dispatch method of the store directly.
import store from 'path/to/your/store/file'
function barChange(arg0) {
//function code...
store.dispatch(user.changeBarNum(arg0, "bar_change_doctor"));
store.dispatch(
user.sendBarDataDoctor("doctor_bar_send", temp_arr, flag_it, arg0)
);
}
If you're not creating your store in a separate file using createStore method, I recommend you to do that as follows.
const store = createStore(reducers, //...);
export default store;
Warning: This approach might have complications if you use it with server-side rendering.