How do I pass the props from FlowRouter to my react component. Is that possible? The documentation is that great.
Im doing something like this:
FlowRouter.route('/dashboard', {
name: 'dashboard',
action(){
var x = Projects.find().fetch(); // this not working
console.log(x); // x is []. Why?
ReactLayout.render(App, {
nav: <Nav />,
content: <Profile data={x}/>
});
}
});
In my app I wish to say this.props.data but the array is empty. I have to put the logic into the react component. Is that the correct way? I hope not.
I think you need subscriptions... see documentation here https://github.com/kadirahq/flow-router#subscription-management
FlowRouter.route('/dashboard', {
name: 'dashboard',
subscriptions(){
this.register('myProjects', Meteor.subscribe('projects'));
},
action(){
ReactLayout.render(App, {
nav: <Nav />,
content: <Profile data={myProjects}/>
});
}
});
But after further review, they are actually recommending that you do get the meteor data in the React Component... see documentation here
https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/with-react
Profile = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
var data = {};
var handle = Meteor.subscribe('projects');
if(handle.ready()) {
data.projects = Projects.find({});
}
return data;
},
});
Sample Project: https://github.com/aaronksaunders/meteor-react-demo2
Related
I have a question regarding Storybook and Vue components with v-models. When writing a story for let's say an input component with a v-model i want a control reflecting the value of this v-model. Setting the modelValue from the control is no problem, but when using the component itself the control value stays the same. I am searching the web for a while now but i can't seem to find a solution for this.
A small example:
// InputComponent.vue
<template>
<input
type="text"
:value="modelValue"
#input="updateValue"
:class="`form-control${readonly ? '-plaintext' : ''}`"
:readonly="readonly"
/>
</template>
<script lang="ts">
export default {
name: "GcInputText"
}
</script>
<script lang="ts" setup>
defineProps({
modelValue: {
type: String,
default: null
},
readonly: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
</script>
In Storybook:
Does anyone have a solution to make this working?
Thanks in advance!
In my case, I have a custom select input that uses a modelValue prop.
I tried this and worked for me:
at my-component.stories.js:
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
export default {
title: 'Core/MyComponent',
component: MyComponent,
argTypes: { }
}
const Template = (args) => ({
components: { MyComponent },
setup() {
let model = ref('Javascript')
const updateModel = (event) => model.value = event
return { args, model, updateModel }
},
template: '<my-component v-bind="args" :modelValue="model" #update:modelValue="updateModel" />'
})
export const Default = Template.bind({})
Default.args = {
options: [
'Javascript',
'PHP',
'Java'
]
}
I wanted to know how I can load in external javascript into a specific story in storybook. The only documentation I can find right now is how to do it globally https://storybook.js.org/docs/react/configure/story-rendering. Doing this works, i would just like to save on performance since only one of my stories uses an external js script.
No, there isn't a standard way to do this in storybook currently (version 6.5).
However you can achieve it with a decorator.
Depending on your needs it could look something like this (this is for a React story):
import { Story, Meta } from '#storybook/react';
import { useEffect } from '#storybook/addons';
export default {
title: 'My Story',
component: MyStory,
decorators: [
(Story) => {
useEffect(() => {
const script = document.createElement('script');
script.src = '/my-script';
document.body.appendChild(script);
}, []);
return <Story />;
},
],
};
There are some caveats here though:
The scripts will remain loaded as you navigate to other stories.
The story will render before the script has loaded.
To handle these caveats you can:
Add a cleanup handler to useEffect.
Don't render your <Story/> until the script has loaded.
For example:
import { Story, Meta } from '#storybook/react';
import { useEffect, useState } from '#storybook/addons';
export default {
title: 'My Story',
component: MyStory,
decorators: [
(Story) => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const script = document.createElement('script');
script.onload = () => {
setIsLoaded(true);
};
script.src = '/my-script';
document.body.appendChild(script);
return () => {
// clean up effects of script here
};
}, []);
return isLoaded ? <Story /> : <div>Loading...</div>;
},
],
};
If you have multiple scripts you'll have to wrap all the onload events into a Promise.all.
This could be wrapped up in an addon similar to storybook-addon-run-script.
I'm trying to conditionally display navbar elements of a navigation component based on the onAuthStateChanged Firebase function.
<template>
<navbar dark position="top" class="default-color" scrolling>
<mdb-navbar-brand href="#/" style="font-weight: bolder;">
Test
</mdb-navbar-brand>
<navbar-collapse>
<navbar-nav left>
<navbar-item href="#/" waves-fixed>Home</navbar-item>
<navbar-item href="#/css" waves-fixed>About</navbar-item>
<navbar-item href="#/jobs" waves-fixed>Jobs</navbar-item>
<navbar-item href="#/advanced" waves-fixed>Profile</navbar-item>
</navbar-nav>
<navbar-nav right>
<router-link to="/signup"><button v-if="!user" type="button" class="btn btn-primary">Signup</button></router-link>
<router-link to="/login"><button v-if="!user" type="button" class="btn btn-primary">Login</button></router-link>
<p><a v-if="user" #click="logout">Logout</a></p>
</navbar-nav>
</navbar-collapse>
</navbar>
</template>
<script>
import Navbar from '#/components/Navbar.vue';
import NavbarItem from '#/components/NavbarItem.vue';
import NavbarNav from '#/components/NavbarNav.vue';
import NavbarCollapse from '#/components/NavbarCollapse.vue';
import mdbNavbarBrand from '#/components/NavbarBrand.vue';
import firebase from 'firebase';
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
logout() {
firebase.auth().signOut()
.then(() => {
this.$router.push({path: '/'});
});
},
created() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
}
};
</script>
Unfortunately, for some reason, the onAuthStateChanged is not working. I also tried to simply display the user in the console from the component perspective, but it's not working as well:
console.log(firebase.auth().currentUser);
Thanks in advance for any hints.
I just wanted to point out another option. Renaud Tarnec's answer is correct but there is a second solution.
You can use the arrow function syntax. With arrow functions the context doesnt change so there is no need to set vm = this before the function since this will still work inside the function. I'm a huge fan of lambda/arrow functions and see no reason not to use them.
Renaud Tarnec's should be the accepted answer but just wanted to offer a second option :)
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
};
If you want to call firebase.auth().onAuthStateChanged() in the created lifecycle hook you should do as follows:
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
var vm = this;
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
vm.user = user;
} else {
vm.user = null;
}
});
}
};
The way you do it, you are declaring created as a "standard" component method.
When I am debugging using the browser console console or react dev tools, they always refer to my components as "Constructor" and I would like to change that. See the example below:
I would have hoped to set defined names for my components so they would show up as "MyComponent" for example. This would help on pages where there are many components and one of them is throwing a warning that I would like to solve.
Add the displayName property to your components:
var Component = React.createClass({
displayName: 'MyComponent',
...
});
You don't need to set the displayName property to your components actually. It is automatically set.
But there are certain cases you need to consider.
1.You put your component in a separate file and content of that file is -
export default React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
In this case displayName will be undefined.
2.You assigned the component in a variable.
var TestComponent = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
Now displayName is set to TestComponent.
See corresponding jsx conversion for more clarification.
var TestComponent = React.createClass({
displayName: "TestComponent",
render: function render() {
return React.createElement(
"h1",
null,
"Hello, ",
this.props.name
);
}
});
3.If you are using es6 e.g
class HelloMessage extends React.Component {
render() {
return <div > Hello component {
} < /div>;
}
}
In this case displayName will be name of class you have given i.e HelloMessage.
I'm trying to access the YouTube Data API v3, but I'm having some trouble figuring out how to load the script in React. this.init() fires, but I get gapi = {}. Oddly enough when i enter gapi in the console, it shows up as the appropriate object.
TestPage = React.createClass({
componentDidMount() {
$.getScript("https://apis.google.com/js/client.js", this.handleClientLoad);
},
handleClientLoad(data, textStatus) {
Utils.logObj(data, "data");
Utils.logObj(textStatus, "textStatus");
Utils.logObj(gapi, "gapi");
Utils.logObj(window.gapi, "window.gapi");
},
render() {
return (
<div>This is the test page.</div>
);
}
});
Your code works fine check the console: http://jsfiddle.net/ferahl/ocpz7pbm/
var App = React.createClass({
componentDidMount() {
$.getScript("https://apis.google.com/js/client.js", this.handleClientLoad);
},
handleClientLoad() {
console.log(window.gapi);
},
render() {
return (
<div>This is the test page.</div>
);
}
});
Perhaps your Utils.logObj is not working as expected?