Vue.js / Firebase and Vuefire - Read and update a single object - firebase

I've been trying to set up a simple app to test some Vue.js features and I've been finding here and there some intersting tutorials about basic CRUD implementation.
I've been stuck on something a little different since a few days, here's a simple description of what I try to achieve :
Set up a home page that displays first and last name.
Store first and last name in firebase as strings
Simply display the two strings on screen
later allow the logged-in user to edit the string (not part of my problem here but relevant to explain why I need the two fields to be stored in Firebase)
I've already worked on a small architecture with login management, different menus for logged in/out states, things like that.
So I already set up that in Firebase :
Firebase configuration
Then here my core files :
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import firebase from 'firebase'
import VueFire from 'vuefire'
import { store } from './store/store'
let app
let config = {
apiKey: '######',
authDomain: '######',
databaseURL: '######',
projectId: '######',
storageBucket: '######',
messaginSenderId: '######'
}
firebase.initializeApp(config)
firebase.auth().onAuthStateChanged(function (user) {
if (!app) {
/* eslint-disable no-new */
app = new Vue({
el: '#app',
store: store,
router,
template: '<App/>',
components: { App }
})
}
})
export const db = firebase.database()
export const homeContent = db.ref('homeContent')
Vue.config.productionTip = false
Index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '#/components/HelloWorld'
import Test from '#/components/Test'
import Login from '#/components/Login'
import SignUp from '#/components/SignUp'
import firebase from 'firebase'
import VueFire from 'vuefire'
Vue.use(Router)
Vue.use(VueFire)
let router = new Router({
routes: [
{
path: '*',
redirect: '/login'
},
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/test',
name: 'Test',
component: Test,
meta: {
requiresAuth: true
}
},
{
path: '/sign-up',
name: 'SignUp',
component: SignUp
},
{
path: '/hello-world',
name: 'HelloWorld',
component: HelloWorld,
meta: {
requiresAuth: true
}
}
]
})
router.beforeEach((to, from, next) => {
let currentUser = firebase.auth().currentUser
let requiresAuth = to.matched.some(record => record.meta.requiresAuth)
if (requiresAuth && !currentUser) next('/login')
else if (!requiresAuth && currentUser) next()
else next()
})
export default router
App.vue
<template>
<div id="app">
<div v-if="user">Logged in</div>
<div v-else>NOT logged in</div>
<Navigation></Navigation>
<button id="btLogout" v-if="user" v-on:click="logout">Déconnexion</button>
<img class="logo" src="./assets/logo.png">
<router-view/>
</div>
</template>
<script>
// Register Navbar component
import Navigation from './components/Nav.vue'
import firebase from 'firebase'
export default {
computed: {
user () {
return this.$store.getters.getUser
}
},
components: {
'Navigation': Navigation
},
methods: {
logout: function () {
firebase.auth().signOut().then(() => {
this.$store.dispatch('clearUser')
this.$router.replace('login')
})
},
setUser: function () {
this.$store.dispatch('setUser')
}
},
created () {
// when the app is created run the set user method
// this uses Vuex to check if a user is signed in
// check out mutations in the store.js file
this.setUser()
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.logo {
width: 50px;
height: auto;
clear: both;
display: block;
margin: 30px auto 0;
}
#btLogout {
clear: both;
display: inline-block;
}
</style>
Test.vue
<template>
<div class="homeScreen">
<p v-bind:key="homeContent['.key']" v-for="firstName of homeContent">{{ homeContent.firstName }}</p>
<p v-bind:key="homeContent['.key']" v-for="lastName of homeContent">{{ homeContent.lastName }}</p>
<img src="../assets/annonce_motw.jpg">
</div>
</template>
<!-- Javascript -->
<script>
import firebase from 'firebase'
import db from "../main"
export default {
data () {
return {
db: ''
}
},
firebase: {
homeContent: {
source: db.ref('homeContent'),
asObject: true
}
},
methods: {
setHomeName (key) {
// homeName.child(key).update({ edit: true })
}
},
created () {
}
}
</script>
<!-- SASS styling -->
<style scoped>
</style>
So here I am. The part where I'm stuck is that everytime I try to add in Test.vue the line db.ref('homeContent') the console returns that db is undefined.
I also can't figure how to simply output the stored strings after resolving the console problem.
So what did I do wrong? :D
Thanks and advance for every piece of help you'll bring! Cheers!

The line
export const db = firebase.database()
gets executed before firebase.initializeApp(config) has finished executing, so it will be undefined.
One way to solve this is to put the initialized Firebase object on the Vue prototype:
Vue.prototype.$firebase = Firebase.initializeApp(config)
Then, inside any of your components, such as Test.vue, you can refer to the Firebase object like this:
firebase: {
homeContent: {
source: this.$firebase.database().ref('homeContent'),
asObject: true
}
},
One caveat: This only works if you create the Vue app after you know Firebase has finished initializing. You did this correctly by putting the new Vue() statement inside firebase.auth().onAuthStateChanged() in main.js, so you will be guaranteed to have an initialized Firebase object available to you at this.$firebase in any of your components.

Related

Why isn't my t() texts refreshing in localhost/en but refreshing in localhost/fr on i18n.changeLanguage()?

Hi
I just made a website with a darkmode and multilanguage support to test around but I ran into an issue.
the code
I got rid of all things that aren't an issue
portfolio/src/pages/index.tsx
import { useTranslation } from 'react-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export default () => {
const { t,i18n } = useTranslation('common')
return <div onClick={()=>i18n.changeLanguage(i18n.language=='fr'?'en':'fr')}>
<div>{i18n.language}</div>
<span>{t('debug')}</span>
</div>
}
export async function getStaticProps({ locale }:any) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
// Will be passed to the page component as props
},
};
}
portfolio/src/public/locales/en/common.js
{"debug":"english"}
portfolio/src/public/locales/fr/common.js
{"debug":"français"}
portfolio/next-i18next.config.js
const path = require("path");
module.exports = {
debug: false,
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
localePath: path.resolve('./src/public/locales'),
};
portfolio/src/pages/_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import {appWithTranslation} from 'next-i18next'
export default appWithTranslation(({ Component, pageProps }: AppProps) => {
return <Component {...pageProps} />
})
The issue
When I do npm run dev and go to http://localhost:3000/fr, the page defaults to french and works good I can swap between languages without problems but when i go to http://localhost:3000/en the t('debug') doesn't translate when the i18n.language changes as intended.
Found what I wanted
So basicaly I need to use a next Link that will change the local and the link
Code application
index.js
//...
export default () => {
const { t,i18n } = useTranslation('common')
return (
<div>
<Link
href={i18n.language=='fr'?'/en':'/fr'}
locale={i18n.language=='fr'?'en':'fr'}
>{i18n.language}</Link>
<div>{t('debug')}</div>
</div>
)
}
//...
result
Now the text changes as intended both in the /fr and /en because it switches between the 2 however the result is far from smooth. It reloads the page and i'd like to avoid that because I use some animations on it.
Found what i wanted part 2
Browsing through the next-i18next documentation I found what I wanted.
solution
I needed to load the props using getStaticProps and in the serverSideTranslation function i needed to pass as argument the array off ALL the language necessary to load the page ['en','fr'] because i switched between the 2

Having difficulty setting up pinia stores in nuxt 3

I'm currently trying to setup a project using nuxt 3 with pinia for state management and I have bumped into the following error:
[h3] [unhandled] H3Error: defineStore is not defined
at createError (file:///home/johnr/Code/Personal/test/node_modules/h3/dist/index.mjs:191:15)
at Server.nodeHandler (file:///home/johnr/Code/Personal/test/node_modules/h3/dist/index.mjs:381:21) {
statusCode: 500,
fatal: false,
unhandled: true,
statusMessage: 'Internal Server Error'
}
I initialized the project with npx nuxi init and then ran npm i, followed by npm install #pinia/nuxt. I then added pinia to nuxt.config.ts:
// nuxt.config.js
export default {
// ... other options
modules: [
// ...
'#pinia/nuxt',
],
}
and created a basic store in store/counter.js:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
});
and have tried to use the returned count in the app template:
<template>
<div>
<p>The count is {{ counterStore.count.value }}</p>
</div>
</template>
<script setup>
import { useCounterStore } from './store/counter.js';
const counterStore = useCounterStore();
</script>
It looks like you forgot to import defineStore in store/counter.js:
import { defineStore } from 'pinia'

How to use SSR with Stencil in a Nuxt 3 Vite project?

In Nuxt 2 I could use server-side rendered Stencil components by leveraging the renderToString() method provided in the Stencil package in combination with a Nuxt hook, like this:
import { renderToString } from '[my-components]/dist-hydrate'
export default function () {
this.nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: false
})
page.html = render.html
})
}
Since the recent release of Stencil 2.16.0 I'm able to use native web components in Nuxt 3 that is powered by Vite. However I haven't found a way to hook into the template hydration process. Unfortunately there is no documentation for the composable useHydration() yet.
Does anybody know how I could get this to work in Nuxt 3?
I had the same problem. I solved it via a module.
Make a new custom nuxt module. documentation for creating a module
In the setup method hook into the generate:page hook:
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
documentation for nuxt hooks
documentation for stencil hydration (renderToString)
Register the css classes you need via nuxt.options.css.push(PATH_TO_CSS)
Register the module in the nuxt config.
Note: Make sure in the nuxt.config.ts the defineNuxtConfig gets exported as default.
Tap the vue compiler options in the nuxt config:
vue: {
compilerOptions: {
isCustomElement: (tag) => TEST_TAG_HERE,
},
},
This depends on how you wan't to use the custom elements. In my case I defined the elements over the stencil loader in my app.vue file:
import { defineCustomElements } from '<package>/<path_to_loader>';
defineCustomElements();
You could also import the elements you need in your component and then define them right there, for example in a example.vue component:
import { CustomElement } from '<package>/custom-elements';
customElements.define('custom-element', CustomElement);
Here is an example from my module and config:
./modules/sdx.ts
import { defineNuxtModule } from '#nuxt/kit';
import { renderToString } from '#swisscom/sdx/hydrate';
export default defineNuxtModule({
meta: {
name: '#nuxt/sdx',
configKey: 'sdx',
},
setup(options, nuxt) {
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
nuxt.options.css.push('#swisscom/sdx/dist/css/webcomponents.css');
nuxt.options.css.push('#swisscom/sdx/dist/css/sdx.css');
},
});
Important: This only works if the stenciljs package supports hydration or in other words has a hydrate output. Read more here
./nuxt.config.ts
import { defineNuxtConfig } from 'nuxt';
//v3.nuxtjs.org/api/configuration/nuxt.config export default
export default defineNuxtConfig({
typescript: { shim: false },
vue: {
compilerOptions: {
isCustomElement: (tag) => /sdx-.+/.test(tag),
},
},
modules: ['./modules/sdx'],
});
./app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { defineCustomElements } from '#swisscom/sdx/dist/js/webcomponents/loader';
defineCustomElements();
// https://v3.nuxtjs.org/guide/features/head-management/
useHead({
title: 'demo',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [{ name: 'description', content: 'demo for using a stencil package in a nuxt ssr app' }],
bodyAttrs: {
class: 'sdx',
},
});
</script>
Update
I tested my setup with multiple components and it looks like you cannot define your components in the module. I updated the answer to my working solution.
I've found defining a plugin using the 'render:response' hook to work for me:
server/plugins/ssr-components.plugin.ts
import { renderToString } from '#my-lib/components/hydrate';
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:response', async (response) => {
response.body = (await renderToString(response.body)).html;
});
});
Perhaps it will work for you :)
Try this in defineNuxtPlugin
nuxtApp.hook('app:rendered', () => {
const response = nuxtApp.ssrContext?.res
if (!response)
return
const end = response.end
response.end = function(chunk) {
chunk = 'hijacked'
end(chunk)
}
})

How to Debug White Screen Page (No Content Showing) in RN Expo App with No Error Prompts

I've been building an app in React Native Expo. First, I incorporated Facebook Login simply by copying and pasting the login async code into Login.js and added this.login() to componentWillMount. This worked - With the Facebook login popup showing up as app loads. I was able to log into my FB account with a success message.
However, as soon as I tried to incorporate Firebase, particularly somewhere between transferring code between my Home.js page and the Login.js page, I started getting this white screen to appear on page load.
There are no errors in a terminal; except a message that FacebookAppID and facebookDisplayName do not belong in app.json.
I tried adding a different background color (black) in CSS, which works, but still, there is no content.
Removing FacebookAppID and facebookDisplayName from app.json, which did nothing.
Updating my App Key to the correct one (I was missing the last number).
Restarted the terminal, expo web terminal x code and metro builder several times.
Updated my code so that every file in my Screens directory has { connect } & { login } imports as well as functionMapStateToProps and export default connect statements at bottom.
I tried changing a tab in TabNavigator.js to Login page, and using "Login" as the initialRouteName, but got an error that Login.js isn't a React component.
The first page that should show up before any other is the Facebook login...So it would seem the issue is there.
App.js
import React from 'react';
import Login from './screens/Login';
import reducers from './redux/reducers';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
const middleware = applyMiddleware(thunkMiddleware);
const store = createStore(reducers, middleware);
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Login/>
</Provider>
);
}
}
------ end of App.js ------------
Login.js
import React from 'react';
import styles from '../styles'
import RootNavigator from '../navigation/RootNavigator';
import { connect } from 'react-redux';
import { login } from '../redux/actions';
import * as firebase from 'firebase';
import firebaseConfig from '../config/firebase.js';
firebase.initializeApp(firebaseConfig)
import {
Text,
View,
TouchableOpacity
} from 'react-native';
class Login extends React.Component
state = {}
componentWillMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user != null) {
this.props.dispatch(login(true))
console.log("We are authenticated now!" + JSON.stringify(user));
}
});
}
login = async () => {
const { type, token } = await Expo.Facebook.logInWithReadPermissionsAsync('YourAppKeyGoesHere', {
permissions: ['public_profile'],
});
if (type === 'success') {
// Build Firebase credential with the Facebook access token.
const credential = await firebase.auth.FacebookAuthProvider.credential(token);
// Sign in with credential from the Facebook user.
firebase.auth().signInWithCredential(credential).catch((error) => {
// Handle Errors here.
Alert.alert("Try Again")
});
}
}
render() {
if(this.props.loggedIn){
return (
<RootNavigator/>
)
} else {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.login.bind(this)}>
<Text>{this.props.loggedIn}</Text>
</TouchableOpacity>
</View>
)
}
}
}
function mapStateToProps(state) {
return {
loggedIn: state.loggedIn
};
}
export default connect(mapStateToProps)(Login);
---------end of Login.js ----------
Home.js
import React from 'react';
import styles from '../styles';
import { connect } from 'react-redux';
import { login } from '../redux/actions';
import {
Text,
View,
Alert
} from 'react-native';
class Home extends React.Component {
state = {}
componentWillMount() {
}
render() {
return (
<View>
<Text>Home</Text>
</View>
)
}
}
function mapStateToProps(state) {
return {
loggedIn: state.loggedIn
};
}
export default connect(mapStateToProps)(Home);
-----end of Home.js ------
redux folder
actions.js
export function login(){
return function(dispatch){
dispatch({ type: 'LOGIN', payload: input });
}
}
----end of actions.js ----
reducers.js
export default reducers = (state = {
loggedIn: false,
}, action) => {
switch (action.type) {
case 'LOGIN': {
return { ...state, loggedIn: action.payload }
}
}
return state;
}
------end of reducers.js ------
-----end of redux folder ------
-----navigation folder (react navigation) -------
---RootNavigator.js---
import React from 'react';
import TabNavigator from './TabNavigator';
import {
createDrawerNavigator,
createStackNavigator,
createBottomTabNavigator,
createAppContainer,
} from 'react-navigation';
const AppNavigator = createStackNavigator(
{
Main: {
screen: TabNavigator,
},
}
);
const AppContainer = createAppContainer(AppNavigator);
export default class RootNavigator extends React.Component {
render() {
return <AppContainer/>;
}
}
----end of RootNavigator.js-----
----TabNavigator.js----
import React from 'react';
import Home from '../screens/Home';
import Profile from '../screens/Profile';
import Matches from '../screens/Matches';
import {
createDrawerNavigator,
createStackNavigator,
createBottomTabNavigator,
createAppContainer,
createMaterialTopTabNavigator,
} from 'react-navigation';
export default createBottomTabNavigator(
{
Profile: {
screen: Profile,
navigationOptions: {
tabBarLabel: 'Profile',
},
},
Home: {
screen: Home,
navigationOptions: {
tabBarLabel: 'Home',
}
},
Matches: {
screen: Matches,
navigationOptions: {
tabBarLabel: 'Matches',
},
},
},
{
navigationOptions: {
header: null
},
tabBarPosition: 'top',
initialRouteName: 'Home',
animationEnabled: true,
swipeEnabled: true,
tabBarOptions: {
style: {
height: 75,
backgroundColor: 'blue'
},
}
}
);
-----end of TabNavigator----
Have you tried remote js Debugging?
What you can do is, Debugg JS remotely.
https://developers.google.com/web/tools/chrome-devtools/remote-debugging/
try to console.log("hi"); when your first component of your app mounts.
Try to add it in login page when the login component mounts.
That will help you debug unseen error which gets listed in the js debugger.
Just check those errors and follow up!
You're good to go!
I was also getting splash logo white screen, tired possible solution nothing works out, at last I have remove node_module and yarn.lock. then reinstall and update expo
follows cmd:-
$ npm install
$ yarn add expo
$ expo update
try this , works for me.
!!enjoy!!
As the other answer suggests, once you've done console.log to see the component is actually loading, then for me the issue was I couldn't actually see the content.
My solution was to wrap my content with a <View> to align the content in the middle of the page.
I understand your question is more complex than that, but hopefully, my answer might be able to help other people.
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
}}>
<Text>Can you see this?</Text>
</View>
in my case,
style = {{ borderColor : #fff }}
my mistake is exceptin ' at borderColor value...
fix change to
style = {{ borderColor : '#fff' }}
Some components such as useState was imported from wrong url, I changed it and imported it from react and fixed it

Integration testing of Relay containers with Jest against a working GraphQL backend not working

I'd like to implement the integration testing of my Relay containers against a running GraphQL backend server. I'm going to use Jest for this. I'd like to say that unit testing of React components works well as expected with my Jest setup.
Here's what I have in the package.json for the Jest:
"jest": {
"moduleFileExtensions": [
"js",
"jsx"
],
"moduleDirectories": [
"node_modules",
"src"
],
"moduleNameMapper": {
"^.+\\.(css|less)$": "<rootDir>/src/styleMock.js",
"^.+\\.(gif|ttf|eot|svg|png)$": "<rootDir>/src/fileMock.js"
},
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react/",
"<rootDir>/node_modules/react-dom/",
"<rootDir>/node_modules/react-addons-test-utils/",
"<rootDir>/node_modules/react-relay/"
]
}
Here's the .babelrc I'm using:
{
"presets": ["es2015", "react", "stage-0"],
"plugins": ["./babelRelayPlugin.js"]
}
Here's the test itself. It must make a request to `http://localhost:10000/q' GraphQL endpoint to fetch a simple piece that represents the info about the current user ('me').
jest.disableAutomock();
import React from 'react';
import Relay from 'react-relay';
import TestUtils from 'react-addons-test-utils';
import RelayNetworkDebug from 'react-relay/lib/RelayNetworkDebug';
RelayNetworkDebug.init();
Relay.injectNetworkLayer(
new Relay.DefaultNetworkLayer('http://localhost:10000/q')
);
describe('Me', () => {
it('can make request to /q anyway', () => {
class RootRoute extends Relay.Route {
static queries = {
root: (Component) => Relay.QL`
query {
root {
${Component.getFragment('root')}
}
}
`,
};
static routeName = 'RootRoute';
}
class AppRoot extends React.Component {
static propTypes = {
root: React.PropTypes.object,
};
render() {
expect(this.props.root).not.toBe(null);
expect(this.props.root.me).not.toBe(null);
expect(this.props.root.me.firstName).not.toBe(null);
expect(this.props.root.me.authorities[0]).not.toBe(null);
expect(this.props.root.me.authorities[0].authority).toEqual('ROLE_ANONYMOUS_AAA');
return (
<div>
{this.props.root.me.firstName}
</div>
);
}
}
const AppContainer = Relay.createContainer(AppRoot, {
fragments: {
root: () => Relay.QL`
fragment on Root {
me {
firstName
email
authorities {
authority
}
}
}
`,
},
});
const container = TestUtils.renderIntoDocument(
<div>
<Relay.RootContainer Component={AppContainer} route={new RootRoute()} />
</div>
);
expect(container).not.toBe(null);
});
});
The problem is that the test passes. But in my opinion it must fail at this line inside the render() expect(this.props.root.me.authorities[0].authority).toEqual('ROLE_ANONYMOUS_AAA');. It seems like the render() method is not executed at all.
I'm running Jest like this
./node_modules/.bin/jest
Does this all suppose to work at all?
Thank you.
This is possible, take a look on the code: https://github.com/sibelius/relay-integration-test
and on my blog post: https://medium.com/entria/relay-integration-test-with-jest-71236fb36d44#.ghhvvbbvl
The missing piece is that you need to polyfill XMLHttpRequest to make it work with React Native.
And you need to polyfill fetch for React web

Resources