Vue Component Versioning, is something along these lines possible? - javascript

I currently import my component dynamically as they are needed, however if a lot of changes are required on a component, I would want to make a new version of it, however it would still be the same component in a way.
I have the following within my app.js:
Vue.component( 'favourites-panel', () => import('./components/Favourites/Panel.vue' );
Can I change the above to something like this and get the version from the prop? Obviously this is theoretical code!
Vue.component( 'favourites-panel', (e) => import('./components/Favourites/Panel' + e.version + '.vue' );
This is how i'm calling my component:
<favourites-panel version="1"></favourites-panel>

No, that's not possible "versioning" a component using the props object.
First of all, you need to understand what you are doing: Vue.component is a function to load globally all the components you want. You can pass an absolute path or if required, a promise.
In this case, you want to load your component asynchronously and the statement import, return a Promise. If you inspect the e property you'll see that is the resolve callback.
Writing:
Vue.component( 'favourites-panel', () => import('./components/Favourites/Panel.vue' );
or:
Vue.component('favourites-panel', function (resolve) {
require(['./components/Favourites/Panel.vue'], resolve)
})
It's the same thing, both returns a Promise object.
For solving your problem you can add an environment variable and then load the
component according to the value of that particular environment variable.

No, this is entirely the wrong approach to both source control and dependency management. You should instead be creating NPM modules of your component(s) and then if for some reason you need to use an old one again you can npm install the old version.

Related

Build System CTA's/Callback function not working

I am working on a build/design system. and everything works fine. The only issue is when I publish my package and attempt to use the callback function. It doesn't properly return the data that is necessary for me to go to the next screen.
I attempted to get a reproducible example for you in CodeSandbox, however, there were some minor implications/errors that wouldn't allow me to get to the specific error I am talking to you about now. That seems to have its own issues.
So, how do you reproduce this error? Well, our package as of right now is public. As said above you can't import into CodeSandbox as it gives other errors on React versions (as said, I will deal with that later..). The package name is #sandboxcommerceeng/react the scss package you might need is #sandboxcommerceeng/scss. Go ahead and import into a CSS file. #import '#sandboxcommerceeng/scss/lib/global.css'. Then in the #sandboxcommerceeng/react package, import ECommercePlatformModal. The code below will give you a reproducible error. Platforms type, is also exported by #sandboxcommerceeng/react
const [showEcommerceModal, setShowEcommerceModal] = React.useState<boolean>(false);
const [url, setUrl] = React.useState<string>('');
const [selectedPlatform, setSelectedPlatform] = React.useState<keyof Platforms>();
<ECommercePlatformModal
selectedPlatform={selectedPlatform}
onSelectPlatform={(platform: keyof Platforms | undefined) =>
setSelectedPlatform(platform)
}
showModal={showEcommerceModal}
onCancel={() => setShowEcommerceModal(false)}
okButtonProps={{ onClick: () => console.log(url) }} // ✴
urlValidated={true}
onUrlChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUrl(e.target.value);
}}
url={url}
/>
✴: I am unable to get the URL from the state using a callback. I've tried just referencing a callback function separately and then logging the URL from there. I've also tried pasting in the URL from the callback. Nothing is working.
End Goal
The end goal here is to have it so that I am able to apply my package into my React application and have the package functionality, i.e. the javascript code implemented in the package itself, work as expected. Expectation for this particular component, is to be able to console.log the url and have it show in the package from the callback function okButtonProp{{onClick: () => console.log(url)}}
Please let me know if there is anything else you need from me.
Your answer lies here. (it could be just a comment but don't have enough reputation yet :) ).
After debugging, the issue did lay within the build system itself. Using React.memo on a button component was the culprit. The memoization wasn't working as intended due to the fact that none of the props were actually changing on the component, therefore it wasn't re-rendering the button component with the new function and variables being passed down to it.
I went ahead and removed the memoization from the memoized component, as I couldn't see a way to fit memoization in this instance.

How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?

I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.

How to use javaScript file as Higher Order wrapper for other JavaScript File

I want to ask, as in react we have HOC (Higher order components) where we pass components that modify it and then return modified Component for use
can we do same in javaScript?
for Example
// index1.js
// this is file where i am importing all the folder modules and exporting them
export { methodA, methodB } from './xyzAB'
export { methodC, methodD } from './xyzCD'
i am importing this file in another folder like this
import * as allMethods from './modules'
// this allows me to use this syntax
allMethods.methodA()
allMethods.methodB()
this is working fine, but i am looking for this kind of wrapper
// index2.js
// this is another file somewhere else where i want to use index1.js exported methods
import * as allMethods from './modules/xyz'
import anotherMethod from './somewhere/xyz'
// here i want customize some of `allMethods` functions and export them as new object
//which contains modifed version of default `index1.js` methods
allMethods.methodA = allMethods.methodA( anotherMethod ) // this is example of modified as HO Method
export default allMethods
My Above example may seem confusing,
why i am looking for such solution, i have set of utilities which i am trying to make them as library and use them in multiple projects,
now some of utils are dependent on main project related things, so instead of giving my utilities hard coded reference to their dependencies,
i want to pass different dependencies for different methods through my higher order method or configuration file,
so that each new project pass its dependent utilities from their config or higher order wrapper file as example shown above
I hope i was able to clear my question,
Few things which i tried,
i tried importing all modules in file which i count as wrapper file
in that if i try to use any module that returns webpack error as undefined method, due to methods not loaded fully until few seconds, i tried setTimeOut, that works fine, but this is not valid way of managing thing,
then i tried some async way, i used dynamic import() which returns promise, i used async/await syntax, and also used .then syntax but couldn't extract data and save it as variable (i may be doing something wrong at this step but i was totally failed) but this was only available with in promise or async await scope,
there were also other steps tried,
i am hoping i could find some neater syntax like below
import * as F from '../../core/lib/functions'
import { getModel } from '../entities'
F.getKeys = F.getKeys( getModel )
export default F
any suggestion is welcome
I think what you're looking for is some sort of currying or factory-like pattern.
There is no such thing as higher order modules but since JavaScript support higher order functions that is what you should use.
Just as a reminder, a higher order component is any component that takes a component as a parameter and returns another component. Similarly (simplified) a higher order function is one that takes a function as a parameter and returns a new function. (in reality all React components are more or less functions so thus why we are able to have higher order components).
The key thing is that you need to call a higher order function, not just import it (since again, there is no such thing as a higher order module). But this ties well into your idea of dependency injection.
I think, what you want is something like this in your utilities:
function a(dependency1, arg1, arg2) {}
function b(dependency2, arg1, arg2) {}
function createUtils(dependency1, dependency2) {
return {
a: a.bind(null, dependency1),
b: b.bind(null, dependency2)
}
}
This allows you to customize per project what dependency 1 and 2 are and the details for how they work (with some common interface). With the binding you don't have to pass that dependency in with every call to a function.
Then in one of your projects you'd set them up something like this:
import { createUtils} from 'utils';
import { dependency1, dependency2 } from 'somewhere' ;
const { a, b } = createUtils(dependency1, dependency2)
export { a, b };
You're not really doing any higher order function stuff, like I said this is more like a factory/dependency injection thing. Though bind is a higher order function (it takes the function it's called in and returns a new function with some arguments bound).
You can put place in createUtils for general modifications through another parameter with options. Or you can export smaller "factories" for each method that you want to be able to modify.
With that in mind you might to only export the raw functions from utils and use bind in your module setup code to bind the dependencies. I think bind is what you are missing. As well as that you have to create new functions to export, rather than modifying the imported functions. That also means that your imports in the rest of your code will come only from within your own module, not from the utils module.

Write Vue plugin with custom options

I'm trying to write a vue plugin with custom options. I followed the official vue guidelines (https://v2.vuejs.org/v2/guide/plugins.html) on doing so but can't find a way to define custom options. These options should be read by normal javascript which then exports an object that is used by a vue component.
My folder structure is like this:
/src
factory.js
CustomComponent.vue
factory.js
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = new ImgixClient({
domain: CUSTOM_OPTION_URL <-- important bit
domain: Vue.prototype.$imgixBaseUrl //tried it like this
});
export { imgixClient };
I already tried to set this custom bit by utilizing Vue.prototype in the install method like this but can't seem to get it working
export function install(Vue, options) {
if (install.installed) return;
install.installed = true;
Vue.prototype.$imgixBaseUrl = options.baseUrl;
Vue.component("CustomComponent", component);
}
I'm afraid this isn't going to be the simple answer you might have been hoping for... there's a lot to unpick here.
Let's start with factory.js. That is not a factory. It's a singleton. Singletons have problems around dependencies, configuration and the timing of instantiation and that's precisely the problem you're hitting. More on that later.
Now let's take a look at the plugin. First up, these two lines:
if (install.installed) return;
install.installed = true;
That shouldn't be necessary. Vue already does this automatically and should ensure your plugin is only installed once. Perhaps this came from an old tutorial? Take a look at the source code for Vue.use, there's not a lot to it:
https://github.com/vuejs/vue/blob/4821149b8bbd4650b1d9c9c3cfbb539ac1e24589/src/core/global-api/use.js
Digging into the Vue source code is a really good habit to get into. Sometimes it will melt your mind but there are some bits, like this, that aren't particularly difficult to follow. Once you get used to it even the more opaque sections start to become a little clearer.
Back to the the plugin.
Vue.prototype.$imgixBaseUrl = options.baseUrl;
It is not clear why you are adding this to the prototype.
I'm going to assume you are already familiar with how JavaScript function prototypes work.
Component instances are actually instances of Vue. So any properties added to Vue.prototype will be inherited by your components with almost no overhead. Consider the following simple component:
<template>
<div #click="onClick">
{{ $imgixBaseUrl }}
</div>
</template>
<script>
export default {
methods: {
onClick () {
const url = this.$imgixBaseUrl
// ...
}
}
}
</script>
As $imgixBaseUrl is an inherited property it is available within onClick via this.$imgixBaseUrl. Further, templates resolve identifiers as properties of the current Vue instance, so {{ $imgixBaseUrl }} will also access this.$imgixBaseUrl.
However, if you don't need to access $imgixBaseUrl within a component then there is no need to put it on the Vue prototype. You might as well just dump it directly on Vue:
Vue.imgixBaseUrl = options.baseUrl;
In the code above I've ditched the $ as there's no longer a risk of colliding with component instance properties, which is what motivates the $ when using the prototype.
So, back to the core problem.
As I've already mentioned, singletons have major problems around creation timing and configuration. Vue has its own built-in solution for these 'do it once at the start' scenarios. That's what plugins are. However, the key feature is that plugins don't do anything until you call install, allowing you to control the timing.
The problem with your original code is that the contents of factory.js will run as soon as the file is imported. That will be before your plugin is installed, so Vue.prototype.$imgixBaseUrl won't have been set yet. The ImgixClient instance will be created immediately. It won't wait until something tries to use it. When Vue.prototype.$imgixBaseUrl is subsequently set to the correct value that won't have any effect, it's too late.
One way (though not necessarily the best way) to fix this would be to lazily instantiate ImgixClient. That might look something like this:
import Vue from "vue";
import ImgixClient from "imgix-core-js";
var imgixClient = null;
export function getClient () {
if (!imgixClient) {
imgixClient = new ImgixClient({
domain: Vue.prototype.$imgixBaseUrl
});
}
return imgixClient;
}
So long as nothing calls getClient() before the plugin is installed this should work. However, that's a big condition. It'd be easy to make the mistake of calling it too soon. Besides the temporal coupling that this creates there's also the much more direct coupling created by sharing the configuration via Vue. While the idea of having the ImgixClient instantiation code in its own little file makes perfect sense it only really stands up to scrutiny if it is independent of Vue.
Instead I'd probably just move the instantiation to within the plugin, something like this:
import ImgixClient from "imgix-core-js";
export default {
install (Vue, options) {
Vue.imgixClient = Vue.prototype.$imgixClient = new ImgixClient({
domain: options.baseUrl
});
Vue.component("CustomComponent", component);
}
}
I've made a few superficial changes, using a default export and wrapping the function in an object, but you can ignore those if you prefer the way you had it in the original code.
If the client is needed within a component it can be accessed via the property $imgixClient, inherited from the prototype. For any other code that needs access to the client it can either be passed from the component or (more likely) grabbed directly from Vue.imgixClient. If either of these use cases doesn't apply then you can remove the relevant section of the plugin.

Require from the actual file using NormalModuleReplacementPlugin

I have a Storybook setup for which I need for my React component's children components to stop doing API calls. The setup is quite complex, and it is also irrelevant to the question, so I'll just say that I need the components to stop doing API calls.
My ultimate goal is to have the children component stay in a "loading state" forever, so mocking the server response not a solution here.
The approach that I came up with was to replace my Thunk action creators with a stubbed one. Similar to what we do on Jest unit tests
// note that I'm using redux ducks: https://github.com/erikras/ducks-modular-redux
jest.mock('./ducks/students');
Of course the above doesn't work since Storybook doesn't run on Jest. So my current approach is to use the NormalModuleReplacementPlugin to replace the real module ducks/students.js with a stubbed one ducks/stubs/students.js which contains the functions, but with an empty body:
// ./ducks/students.js
export const loadResources() = fetch('/resources');
export default (state, actions => {
// reducer's body
}
// ./ducks/stubs/students.js
export const loadResources() = Promise.resolve(); // STUBBED
export default (state, actions => {
// reducer's body
}
The problem is that I need only the thunk action creators to be stubbed, everything else in the file (other actions, and reducer) needs to be the same.
This are the approaches I have considered so far to fix this:
Copy/paste the rest of the actual file into the stubbed one. This wouldn't scale.
Attempting to use require.requireActual(). It turns out this is a Jest custom function so I can't use it on Storybook.
Ideally I could find a way to import everything from the actual module into the stubbed one, and export the stubbed functions and the rest of the real functions that I need.
Any ideas how can I access the actual module from the stubbed one when I'm using NormalModuleReplacementPlugin?
Update 1: 2019-07-08
Tarun suggestion about just mocking the fetch function and returning a new Promise() worked for the particular case of "indefinitely loading".
However, looking at the big picture, I still would rather just stubbing out all of the API calls, so that I can setup the stories by just modifying the redux state.
"But why can't you just mock the JSON response?" I hear you ask. The JSON response is not necessarily 1-to-1 mapping with the app domain model. We have mapper functions that takes care of the transformation.
I'd be better if the programmers could work and setup the test cases with just the domain model knowledge, and don't need to know the server response JSON structure. Needless to say, the app redux store structure is the domain model.
So I still need an answer on how to require from the actual file, when using NormalModuleReplacementPlugin.
I haven't tested this, but you might be able to achieve what you're after with the Aggregating/re-exporting modules syntax and overwriting your loadResources() function.
To do this, import your actual module into ./ducks/stubs/students.js, export all from that module, then define/overwrite loadResources() and export it as well. You can then use the NormalModuleReplacementPlugin as normal and pass in your stub file as the newResource that will contain all of your actual module reducers/actions that you wanted to keep with the thunk overwritten and stubbed out:
//ducks.stubs.students.js
export * from './ducks/students.js';
//override students.loadResources() with our stub
//order matters as the override must come after
//the export statement above
export const loadResources() = //some stubbed behavior;
//webpack.config.js
module.exports = {
plugins: [
new webpack.NormalModuleReplacementPlugin(
/ducks\.students\.js/,
'./ducks.stubs.students.js'
)
]
}
A couple of notes/caveats/gotchas with this solution:
You might have to update your exports to use let vs. const (not a big deal)
Per this issue, the export * from expression isn't supposed to handle default exports. As such, you might have to add export { default } from './ducks/students.js';. Of course, keep in mind that you won't be able to export a default function native to your stubs file (unless you're overriding the original default function with a stub of course).

Categories

Resources