vue dynamic async component set props with javascript - javascript

I have some code similar to below where i am importing a component dynamically and then i want to set some props on it.
I dont have a <component></component> html tag as i am passing the component to a 3rd party plugin that will display the component inside their own component.
the below code works but then i have started seeing the following vue warning error in the console:
option "propsData" can only be used during instance creation with the `new` keyword.
structure of settings variable:
const settings = {
props:{
title: "new title"
}
}
component loading function:
function Load(compName, settings){
import("#/"+ compName +".vue").then((mod)=>{
let mycomponent = mod.default;
const props = settings.props;
mycomponent.propsData = {...props});
//3rd party package needs this format
let template = function(){
return {template : mycomponent};
}
//not shown - code that passes the template variable to the 3rd party package
});
}
is there another way to get the props into the component - or is it ok to ignore this warning?

Related

Instanciating extended VueJS components with props data within

Hello everyone,
Let me give you a little bit of context about my problem :
I'm trying to create a system that can add charts on a page with the simple push of a button.
These charts are going to contain elements from a MySQL database.
I have a Chart.vue file that contains the template for a single HighChart element. It also contains a prop :
export default {
name : "Chart",
props : ["tableToDisplay"],
And then I have my main vue that is named "Test.vue".
It imports the Chart.vue from the component folder and then I basically just need to write :
<Chart :table-to-display="tableToDisplay"/>
to create an instance of a chart of the table contained within the variable : this.tableToDisplay.
But this is not what I want to do : I want to create a chart with the push of a button, so I made some changes :
<div>
<button #click="createGraph">Add a graph</button>
<Chart :table-to-display="tableToDisplay"/>
</div>
And with it, I created the method:
createGraph(event)
{
let ChartClass = Vue.extend(Chart)
console.log(ChartClass)
let graphInstance = new ChartClass({
props:{
"tableToDisplay": this.tableToDisplay
}
})
graphInstance.$mount()
let divContainer = event.target.parentElement
divContainer.append(graphInstance.$el)
},
That is where my problem is.
Within that method, I want to send a table to display to the newly created Chart, but it seems that I can't manipulate the props value in that way.
I thought that this piece of code was the solution :
let graphInstance = new ChartClass({
props:{
"tableToDisplay": this.tableToDisplay
}
})
But It turns out that it is not.
When I click the button, an empty chart does appear but the prop "tableToDisplay" is undefined.
I looked at the console and I get a "[Vue warn]: Error in the mounted hook: "TypeError: ciphertext is null".
It doesn't matter if I put an argument or not in the ChartClass, I always have this error on the graphInstance.$mount() line.
First, I think you don't need to programatically instantiate your Chart components. A simple v-for loop will do the trick:
<template>
<Chart v-for="table of chartTables :table-to-display="table"/>
</template>
<script>
...
data() {
chartTables: []
},
methods: {
createChart() {
// Adding a new table to the array will create a new Chart component.
this.chartTables.push(this.tableToDisplay)
}
}
</script>
If this solution suits your needs, go ahead in that way!
That said, if you really need to instantiate a Vue component yourself, you have to use the propsData parameter to pass your props.
const instance = new ChartClass({
parent: this, // The parent is your current component
propsData: {
tableToDisplay: this.tableToDisplay,
},
})
let divContainer = event.target.parentElement
instance.$mount(divContainer)
The parent option is really important: it adds your component to the Vue component dependency tree. Without it, your component won't have inherited properties (such as the Vuex store, plugins etc.).

How do I update state when a window variable changes in react

I have a fairly simple class component that needs access to some data from a service written in vanilla JS. It's simply an interface for the Web MIDI API, that must get access to the MIDI ports, then triggers a callback. I'm importing a function setMidiPorts to the MIDI service then calling it and sending the list of ports on MIDI success. I then need to render those ports in a drop down, but can't seem to get them updated in the component. I've tried passing them down as props from the parent, I've tried importing them directly. Nothing seems to work. I'm pretty new to react so I'm probably doing something pretty wrong, can anyone help me by pointing out where I'm going wrong.
window.inputs = [];
export const setMidiPorts = (inputs) => {
window.inputs = inputs;
console.log(inputs);
};
export default class Preferences extends Component {
constructor(props) {
super(props);
this.state = {
midiInputs: window.inputs,
midiOutputs: [],
};
}
......
EDIT -
I'm looking at this question to see if I can update state from outside, but don't understand how it works properly.
Update component state from outside React (on server response)
Thanks for everyones advice, I managed to solve it by following what it says here.
https://brettdewoody.com/accessing-component-methods-and-state-from-outside-react/
Adding this inside the component render
ref={(Preferences) => {window.Preferences = Preferences;}}
Then I was able to define the setMidiPorts function inside the component and call it from anywhere with window.Preferences.setMidiPorts

Variable not defined when used outside of Vue <template>

I am exporting a value from a JS file and using it in my Vue. I display the value like in my template, but I would like to also use this value elsewhere, outside of the template.
Here is a simplified version of my setup and what I would like to achieve:
In my myJS.js (in actual app this file is used to call my API):
const appService = {
getPosts() {
return 123456;
}
}
export default appService
and then I have a VUE component that looks like this:
<template>
{{ info }}
</template>
alert(info);
import appService from '../myJS'
export default {
name: 'myvue',
props: {
msg: String
},
data () {
return {
info: {}
}
}
async created () {
this.info = await appService.getPosts();
}
}
The value is displayed in the template, but the alert(info) triggers a 'info is undefined' error.
How can I use this value in normal JavaScript outside of my template?
Hope that makes sense.
Thanks in advance!
There's basically two ways:
Assign the value to a variable, inside the file, but outside the component definition. So in your example you would say let info; and in your created() hook you would go this.info = info = await appService.getPosts().
Assign the component itself to a variable before you export it:
const myComponent = { component definition... }
// Access with myComponent.info
export default myComponent;
However! You will see that myComponent.info is initially undefined. And unlike in the template, the value of info will not reactively update when the asynchronous call resolves. Reactivity magic only works in the template and in the code of the actual component, e.g. in computed and watch properties. How to handle the specifics of asynchronous loading elegantly really depends on the specifics of how you want to use this variable.
Finally: the reason you don't just get access to the variable throughout the .vue file is because under the hood, the template compiles to a function which is executed in the context of the component. For convenience, all of the fields of the component are then made available as variables without the usual this prefix. In fact, in your template, info is treated as a shorthand for this.info. (Notice that you can substitute this.info for info in the template and it will still work.) This privilege doesn't apply to any other code in the file; that stuff is just vanilla js code.

Getting data attribute of html element in react.js context

The CMS passes a variable as data-rest-url attribute to the React.js App:
<div id="reactjs-root" data-rest-url="http://my-ip-addess:8080/Rest-api-here">...</div>
If I add jQuery to my React.js App, then I can simply:
componentWillMount() {
const $reactRoot = $('#reactjs-root');
const restUrl = $reactRoot.attr('data-rest-url');
}
But adding jQuery just for this? How would you pass some variable from a CMS to your Single Page React App and read / parse / get it with react.js?
Consider passing your data attributes to your component as props instead of hard coding the root element ID within the component itself.
Rendering:
var rootElement = document.getElementById('reactjs-root');
ReactDOM.render(
<YourComponent resturl={rootElement.getAttribute('data-rest-url')}></YourComponent>,
rootElement
);
Within the component you can access the injected url:
componentWillMount() {
console.log(this.props.resturl)
}
This makes for a more reusable component that is decoupled from a specific element ID.
const reactRoot = document.getElementById('reactjs-root');
const restUrl = reactRoot.getAttribute('data-rest-url');
Also, avoid using $ in your variable name. You're likely to run into a lot of libraries that conflict with the $ you have used as a variable.

How to access child component functions via refs

The docs for React state that component functions can be accessed by a parent component via refs. See: https://facebook.github.io/react/tips/expose-component-functions.html
I am attempting to use this in my application but run into an "undefined is not a function" error when the child function is called. I'm wondering if this has anything to do with using the ES6 format for React classes because I don't see any other differences between my code and the docs.
I have a Dialog component that looks like the following pseudocode. The Dialog has a "Save" button that calls save(), which needs to call the save() function in the child Content component. The Content component collects information from child form fields and performs the save.
class MyDialog extends React.Component {
save() {
this.refs.content.save(); <-- save() is undefined
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
}
}
I could instead pass a prop (saveOnNextUpdate) down to Content and then execute save whenever it is true, but I would rather figure out how to get the method detailed in the React doc above to work.
Any ideas on how to get the doc approach to work or access the child component function in a different way?
Redux connect accepts an option parametre as the forth parameter. In this option parameter you can set the flag withRef to true. Then you can access functions to refs by using getWrappedInstance(). Like this:
class MyDialog extends React.Component {
save() {
this.refs.content.getWrappedInstance().save();
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() { ... }
}
function mapStateToProps(state) { ... }
module.exports = connect(mapStateToProps, null, null, { withRef: true })(Content);
Read more about it here: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
Worth reading this article about use of refs and consider if there's better approaches: https://facebook.github.io/react/docs/refs-and-the-dom.html#dont-overuse-refs
An alternative way to do this would be to use some other prop name (other than ref). I've found that this also works well if you're using a library like styled-components or emotion For example in a connected MyComponent:
<MyComponent
...
innerRef={(node) => { this.myRef = node; }}
/>
As it turns out, m90 was right -- this was a different issue entirely. I'm posting the solution in case someone runs into the same problem in the future.
My application is built with Redux, and the problem stems from using the react-redux connect function to connect a component to the store/global state. For some reason, exporting a component and connecting it to the store makes it impossible to access the functions inside of it. In order to get around this, I had to remove all use of global state from Content so that I could export it as a "dumb" component.
To be more clear, Content.js looked like this:
var connect = require('react-redux').connect;
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
// Use of this.props.stateObject
}
}
function mapStateToProps(state) {
const {
stateObject
} = state;
return {
stateObject
};
}
module.exports = connect(mapStateToProps)(Content);
Removing the use of global state (and therefore the use of connect and mapStateToProps allowed me to export the component using:
module.exports = Content;
Accessing this.refs.content.save() magically worked after doing this.

Categories