Is there another way of writing this? By the way, this works perfectly but I feel it could be written better:
Profile = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
currentUser: Meteor.user(),
};
},
getInitialState(){
// we add an if statement to prevent undefined errors
// could this be written elsewhere?
if (Meteor.user().profile) {
this.profile = Meteor.user().profile;
} else {
this.profile = '';
};
return { firstname: this.profile.firstname };
},
...
)};
Yes, you can use an ES6 class, which is the way recommended when working with Meteor (ecmascript package has you covered). Also, you don't need to use Meteor.user() outside getMeteorData() method.
class Profile extends React.Component {
constructor(props){ super(props); }
mixins: [ReactMeteorData]
getMeteorData() {
return {
user: Meteor.user(),
};
}
getInitialState(){
return {
firstname: this.data.user && this.data.user.profile.firstname
}
}
}
Related
Trying to switch my code to the new composition API that comes with Vue 3 but I cant get it to work.
export default {
props: {
classProp: {type: String},
error: {type: String},
},
setup(){
// move to here (this is not working)
computed(() => {
const classObject = () => {
return ['form__control', this.classProp,
{
'form__invalid': this.error
}
]
}
})
},
computed: {
classObject: function () {
return ['form__control', this.classProp,
{
'form__invalid': this.error
}
]
}
},
}
skip "computed" all together
you need to use "ref" or "reactive". these are modules:
<script>
import { ref } from 'vue'
setup(){
const whateverObject = ref({ prop: "whatever initial value" });
whateverObject.value.prop= "if you change something within setup you need to access it trough .value";
return { whateverObject } // expose it to the template by returning it
}
</script>
if you want to use classes you import them like in this example of my own:
import { APIBroker } from '~/helpers/APIbroker'
const api = new APIBroker({})
Now "api" can be used inside setup() or wherever
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.
I'm having some problems resetting a textarea field after a POST request.
This is my component code
<template lang="pug">
.col-sm-12
h2 Add new Task
hr
.form-group
textarea.form-control(v-model="task.taskContent")
.form-group
button.btn.btn-primary(#click="createNewTask") Add Task
</template>
<script>
export default {
data() {
return {
task: {
taskContent: ''
}
};
},
methods: {
createNewTask() {
if (this.task.taskContent.length > 0) {
// Sending data to the server
this.$http.post('https://vue-taskmanager.firebaseio.com/task.json', this.task)
.then(response => {
console.log(response);
// Adding the new task to the main template list
this.$emit('taskWasCreated', this.task);
// Resetting textarea content
this.task.taskContent = '';
}, error => {
console.log(error);
});
} else {
alert("Sorry you can't create an empty task");
}
}
}
}
</script>
This is the parent component
<template lang="pug">
.container
.row
app-newtask(#taskWasCreated="addTask")
app-taskswrapper(:tasks="tasksArr")
app-footer
</template>
<script>
import { EventBus } from './main.js';
import UserRegistration from './components/user/UserRegistration.vue';
import TasksWrapper from './components/TasksWrapper.vue';
import NewTask from './components/NewTask.vue';
import Footer from './components/Footer.vue';
export default {
data() {
return {
tasksArr: [
'Just something to see'
]
};
},
methods: {
addTask(task) {
this.tasksArr.push(task)
}
},
// Listening on Events from Task.vue
created() {
// Delete task from array
EventBus.$on('taskWasDeleted', (taskIndex) => {
this.tasksArr.splice(taskIndex, 1);
// Delete task from db
this.$http.delete('https://vue-taskmanager.firebaseio.com/task.json', this.task)
.then(response => {
console.log(response);
}, error => {
console.log(error);
});
});
// Fetch tasks from db
this.$http.get('https://vue-taskmanager.firebaseio.com/task.json')
.then(response => {
return response.json();
})
.then(task => {
const resultsArray = [];
for (let key in task) {
resultsArray.push(task[key]);
}
this.tasksArr = resultsArray;
});
},
components: {
'app-taskswrapper': TasksWrapper,
'app-newtask': NewTask,
'app-footer': Footer,
'app-userregistration': UserRegistration
}
}
</script>
As you can see inside the response callback function I reset the task.taskContent value but the problem is that the string is sent to the db without problems while is not updated in the root component where I have an array storing all these strings.
I was thinking about using a watcher but I don't know if it's a good solution, do you have any suggestions?
Link to the github repo https://github.com/Polenj86/vue-taskmanager
It's clear what is happening now that you've posted your parent component.
You are storing the task object in the parent's array. This is not going to be a copy of the task, it's going to be a reference of the same task that you are about to clear. So when you later set this.task.taskContent = '' you are changing the task in the parent array too.
Consider this:
var task_holder_array = []
var task = {name: "mark"}
task_holder_array.push(task)
console.log("array before: ", task_holder_array)
task.name = ""
console.log("array after: ", task_holder_array)
You need to somehow create a new task object to push into the parent's array. There are a lot of ways you could do this. For example:
this.$emit('taskWasCreated', {name: this.task.name});
Or you could just pass the task name string to the parent and let the parent create the object.
Very basic simple GET example for react-redux
I have a "MockAPI" which simulates a GET request to an API like so:
const dashboards = [
{
"Id":1,
"title":"Overview"
},
{
"Id":2,
"title":"Overview"
},
{
"Id":3,
"title":"Overview"
},
{
"Id":4,
"title":"Overview"
}
];
class DashboardApi {
static getAllDashboards() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Object.assign([], dashboards));
}, delay);
});
}
}
I am trying to develop in a react-redux flow of dispatching an action via a button click and then updating the component via the redux store.
Here is my component code:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as dashboardActions from '../../actions/dashboardActions';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.loadDashboards = this.loadDashboards.bind(this);
}
loadDashboards() {
this.props.dispatch(dashboardActions.loadDashboards());
}
dashboardItem(dashboard, index) {
return <p key={index}>{dashboard.title}</p>;
}
render() {
return (
<div>
<h1>
Hello World!
<button onClick={this.loadDashboards}>load</button>
</h1>
{this.props.dashboards.map(this.dashboardItem)}
</div>
);
}
}
HomePage.propTypes = {
dashboards: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
dashboards: state.dashboards
};
}
export default connect(mapStateToProps)(HomePage);
And here is my dashboardActions.js:
import * as types from './actionTypes';
import dashboardApi from '../mockApi/mockDashboardApi';
export function loadDashboardsSuccess(dashboards) {
return { type: types.LOAD_DASHBOARDS_SUCCESS, dashboards };
}
export function loadDashboards() {
return dispatch => {
return dashboardApi
.getAllDashboards()
.then(dashboards => {
dispatch(loadDashboardsSuccess(dashboards));
});
};
}
And here is my reducer:
import initialState from './initialState';
import * as types from '../actions/actionTypes';
export default function dashboardReducer(state = initialState.dashboards, action) {
switch(action.types) {
case types.LOAD_DASHBOARDS_SUCCESS:
return action.dashboards;
default:
return state;
}
}
I am trying to get the onClick to load in the dashboards array and to render as <p> tags simply displaying the title value. Unfortunately it is not happening.
I see that the LOAD_DASHBOARDS_SUCCESS action is getting loaded, but I see that the dashboards property in the store is still an empty array instead of showing the returned data...
What am I missing here?
You've got a typo in your reducer. switch(action.types) should be switch(action.type) with no 's'
My component get some properties via props with the function:
const mapStateToProps = state => {
const { entities: { keywords } } = state
const {locale} = state
return {
keywords: keywords[locale]
}
}
I got state keywords using ajax, in the same component:
componentDidMount() {
this.props.loadKeywords()
}
My component gets rendered twice. First, before the ajax resolves, so in my render method I got undefined:
render() {
const { keywords } = this.props.keywords
...
Which is the proper way to solve it? I changed componentDidMount to componentWillMount without success.
Right now, based on the real-world example, I have initialized keywords state with an empty object:
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
My reducer:
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
import merge from 'lodash/merge'
import locale from './modules/locale'
import errorMessage from './modules/error'
import searchText from './modules/searchText'
// Updates an entity cache in response to any action with response.entities.
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
export default combineReducers({
locale,
router,
searchText,
errorMessage,
entities
})
My action:
import { CALL_API, Schemas } from '../middleware/api'
import isEmpty from 'lodash/isEmpty'
export const KEYWORDS_REQUEST = 'KEYWORDS_REQUEST'
export const KEYWORDS_SUCCESS = 'KEYWORDS_SUCCESS'
export const KEYWORDS_FAILURE = 'KEYWORDS_FAILURE'
// Fetches all keywords for pictos
// Relies on the custom API middleware defined in ../middleware/api.js.
function fetchKeywords() {
return {
[CALL_API]: {
types: [ KEYWORDS_REQUEST, KEYWORDS_SUCCESS, KEYWORDS_FAILURE ],
endpoint: 'users/56deee9a85cd6a05c58af61a',
schema: Schemas.KEYWORDS
}
}
}
// Fetches all keywords for pictograms from our API unless it is cached.
// Relies on Redux Thunk middleware.
export function loadKeywords() {
return (dispatch, getState) => {
const keywords = getState().entities.keywords
if (!isEmpty(keywords)) {
return null
}
return dispatch(fetchKeywords())
}
}
All based on the Real world redux example
My Solution
Given initial state to keywords entity. I'm getting json like this through ajax:
{'locale': 'en', 'keywords': ['keyword1', 'keyword2']}
However as I use normalizr with locale as id, for caching results, my initial state is as I describe in the reducer:
function entities(state = { users: {}, repos: {}, keywords: { 'en': { 'keywords': [] } } }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
What I don't like is the initial if we have several languages, also remembering to modify it if we add another language, for example fr. In this
keywords: { 'en': { 'keywords': [] } }
should be:
keywords: { 'en': { 'keywords': [] }, 'fr': { 'keywords': [] } }
This line looks problematic:
const { keywords } = this.props.keywords
It's the equivalent of:
var keywords = this.props.keywords.keywords;
I doubt that's what you intended.
Another thing worth checking is keywords[locale] in your mapStateToProps() which will probably initially resolve to undefined. Make sure your component can handle that, or give it a sensible default.