Express Vue not creating association, userId is null - postgresql

I am following this tutorial using Express.js Postgres and Vue to create a CRUD app for Songs with login and basic associations : https://www.youtube.com/watch?v=ipYlztBRpp0
I am using Vuex to manage the state object. However, for some reason, though registration of new users works to create a user record, when I try to create a Bookmark, which essentially pairs a UserId with a SongId, UserId is always recorded as null. SongId is saved correctly. Checking the requests in the network and Vue tabs of developer tools reveals that the User object seems to be empty.
Here are the relevant files.
User.js model
const Promise = require('bluebird')
const bcrypt = Promise.promisifyAll(require('bcrypt-nodejs'))
function hashPassword (user, options) {
const SALT_FACTOR = 8
if (!user.changed('password')) {
return;
}
return bcrypt
.genSaltAsync(SALT_FACTOR)
.then(salt => bcrypt.hashAsync(user.password, salt, null))
.then(hash => {
user.setDataValue('password', hash)
})
}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true
},
password: DataTypes.STRING
}, {
hooks: {
// beforeCreate: hashPassword,
// beforeUpdate: hashPassword,
beforeSave: hashPassword
}
})
User.prototype.comparePassword = function (password) {
return bcrypt.compareAsync(password, this.password)
}
return User
}
Songs model:
module.exports = (sequelize, DataTypes) => {
const Song = sequelize.define('Song', {
title: DataTypes.STRING,
artist: DataTypes.STRING,
genre: DataTypes.STRING,
album: DataTypes.STRING,
albumImageUrl: DataTypes.STRING,
youtubeId: DataTypes.STRING,
lyrics: DataTypes.TEXT,
tab: DataTypes.TEXT
})
return Song
}
Bookmark's controller:
const {Bookmark} = require('../models')
module.exports = {
async index (req, res) {
try {
const {songId, userId} = req.query
const bookmark = await Bookmark.findOne({
where: {
SongId: songId,
UserId: userId
}
})
res.send(bookmark)
} catch (err) {
res.status(500).send({
error: 'An error has occurred trying to fetch the songs'
})
}
},
async post (req, res) {
try {
const {songId, userId} = req.body
const bookmark = await Bookmark.findOne({
where: {
SongId: songId,
UserId: userId
}
})
if (bookmark) {
return res.status(400).send({
error: 'you already have this song bookmarked'
})
}
const newBookmark = await Bookmark.create({
SongId: songId,
UserId: userId
})
res.send(newBookmark)
} catch (err) {
console.log(err)
res.status(500).send({
error: 'An error has occurred trying to create the bookmark.'
})
}
},
async delete (req, res) {
try {
const {bookmarkId} = req.params
const bookmark = await Bookmark.findById(bookmarkId)
await bookmark.destroy()
res.send(bookmark)
} catch (err) {
res.status(500).send({
error: 'An error has occurred trying to delete the bookmark'
})
}
}
}
BookMarksService (for axios connection)
import Api from '#/services/Api'
export default {
index (bookmark) {
return Api().get('bookmarks', {
params: bookmark
})
},
post (bookmark) {
return Api().post('bookmarks', bookmark)
},
delete (bookmarkId) {
return Api().delete(`bookmarks/${bookmarkId}`)
}
}
and the component SongsMetaData:
<template>
<panel title='Song Metadata'>
<v-layout>
<v-flex xs6>
<div class="song-title">
{{song.title}}
</div>
<div class="song-artist">
{{song.artist}}
</div>
<div class="song-genre">
{{song.genre}}
</div>
<v-btn
dark
class="cyan"
:to="{
name: 'song-edit',
params () {
return {
songId: song.id
}
}
}">
Edit
</v-btn>
<v-btn
v-if="isUserLoggedIn && !bookmark"
dark
class="cyan"
#click="setAsBookmark">
Bookmark
</v-btn>
<v-btn
v-if="isUserLoggedIn && bookmark"
dark
class="cyan"
#click="unsetAsBookmark">
UnBookmark
</v-btn>
</v-flex>
<v-flex xs6>
<img class="album-image" :src="song.albumImageUrl" />
<br />
<div class="song-album">
{{song.album}}
</div>
</v-flex>
</v-layout>
</panel>
</template>
<script>
import {mapState} from 'vuex'
import BookmarksService from '#/services/BookmarksService'
export default {
props: [
'song'
],
data () {
return {
bookmark: null
}
},
computed: {
...mapState([
'isUserLoggedIn'
])
},
watch: {
async song () {
if (!this.isUserLoggedIn) {
return
}
try {
this.bookmark = (await BookmarksService.index({
songId: this.song.id,
userId: this.$store.state.user.id
})).data
} catch (err) {
console.log(err)
}
}
},
methods: {
async setAsBookmark () {
try {
this.bookmark = (await BookmarksService.post({
songId: this.song.id,
userId: this.$store.state.user.id
})).data
} catch (err) {
console.log(err)
}
},
async unsetAsBookmark () {
try {
await BookmarksService.delete(this.bookmark.id)
} catch (err) {
console.log(err)
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.song {
padding: 20px;
height: 330px;
overflow: hidden;
}
.song-title {
font-size: 30px;
}
.song-artist {
font-size: 24px;
}
.song-genre {
font-size: 18px;
}
.album-image {
width: 70%;
margin: 0 auto;
}
textarea {
width: 100%;
font-family: monospace;
border: none;
height: 600px;
border-style: none;
border-color: transparent;
overflow: auto;
padding: 40px;
}
</style>
and the vuex store.js file:
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export default new Vuex.Store({
strict: true,
plugins: [
createPersistedState()
],
state: {
token: null,
user: null,
isUserLoggedIn: false
},
mutations: {
setToken (state, token) {
state.token = token
if (token) {
state.isUserLoggedIn = true
} else {
state.isUserLoggedIn = false
}
},
setUser (state, user) {
state.user = user
}
},
actions: {
setToken ({commit}, token) {
commit('setToken', token)
},
setUser ({commit}, user) {
commit('setUser', user)
}
}
})

Related

Server Error: Error: Error serializing. Reason: `object` (“[object Object]”) cannot be serialized as JSON Next.js MongoDB

I am working on an online shopping project and implying a review system for shopping products. I am using Next.js and MongoDB for my project. It works fine without any reviews submitted but when I submit my review and the error happened as below;
the error is from getServerSideProps in pages/product/[slug].js and the code is as below;
import axios from 'axios';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router'
import React, { useContext, useState, useEffect } from 'react'
import Layout from '../../components/Layout'
import Product from '../../models/Product';
import db from '../../utils/db';
import { Store } from '../../utils/Store';
import { toast } from 'react-toastify';
import Rating from '#material-ui/lab/Rating';
import { getSession } from 'next-auth/react';
import { useForm } from "react-hook-form";
import { getError } from '../../utils/error';
export default function ProductScreen (props) {
const { product, user } = props;
const {state, dispatch} = useContext(Store);
const router = useRouter();
const [ reviews, setReviews ] = useState([]);
const [ rating, setRating ] = useState(0);
const [ comment, setComment ] = useState('');
const [ loading, setLoading ] = useState(false);
const {
handleSubmit,
register,
formState: { errors },
setValue,
} = useForm();
const submitHandler = async () => {
setLoading(true);
try {
await axios.post(`/api/products/${product._id}/reviews`, { rating, comment, user });
setLoading(false);
toast.success('Review submitted successfully');
fetchReviews();
} catch (err) {
setLoading(false);
return toast.error(getError(err));
}
}
const fetchReviews = async () => {
try {
const { data } = await axios.get(`/api/products/${product._id}/reviews`);
setReviews(data);
} catch (err) {
return toast.error('fetchReview err');
}
}
useEffect(() => {
fetchReviews();
}, []);
if (!product) {
return <Layout title="Product Not Found">Product Not Found</Layout>;
}
const addToCartHandler = async () => {
const existItem = state.cart.cartItems.find((x) => x.slug === product.slug);
const quantity = existItem ? existItem.quantity + 1 : 1;
const { data } = await axios.get(`/api/products/${product._id}`);
if (data.countInStock < quantity) {
return toast.error('Sorry. Product is out of stock');
}
dispatch ({ type: 'CART_ADD_ITEM', payload: { ...product, quantity }});
router.push("/cart");
};
return (
<Layout title={product.name}>
<div className='py-2'>
<Link href="/">back to products</Link>
</div>
<div className='grid md:grid-cols-4 md:gap-3'>
<div className='md:col-span-2'>
<Image
src={product.image}
alt={product.name}
width={640}
height={640}
layout='responsive'
/>
</div>
<div>
<ul>
<li className='mt-4'>
<h1 className='text-lg'>{product.name}</h1>
</li>
<li className='mt-4'>Category: {product.category}</li>
<li className='mt-4'>Brand: {product.brand}</li>
<li className='flex mt-4'>
<Rating value={product.rating} readOnly />
<Link href='#reviews'>
<a>({product.numReviews} reviews)</a>
</Link>
</li>
<li className='mt-4'>Description: {product.description}</li>
</ul>
</div>
<div>
<div className='card p-5'>
<div className='mb-2 flex justify-between'>
<div>Price</div>
<div>${product.price}</div>
</div>
<div className='mb-2 flex justify-between'>
<div>Status</div>
<div>{product.countInStock > 0 ? 'In stock' : 'Unavailable'}</div>
</div>
<button className='primary-button w-full' onClick={addToCartHandler}>Add to cart</button>
</div>
</div>
</div>
<div id='reviews' className='mt-4'>
<h2 className='text-xl'>Customer Reviews</h2>
{reviews.length === 0 && <div>No reviews</div>}
{reviews.map((review) => (
<div key={review._id}>
{review.name}
{review.createdAt.substring(0, 10)}
<Rating value={review.rating} readOnly />
{review.comment}
</div>
))}
</div>
<div>
{user ? (
<form onSubmit={handleSubmit(submitHandler)}>
<h1>Leave your review</h1>
<div>
<Rating
name='simple-controlled'
value={rating}
onChange={(e) => setRating(e.target.value)}
/>
<label htmlFor='comment'>Comment</label>
<input
type='text'
{...register('comment', { required: 'Please enter comment',})}
id='comment'
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
{errors.comment && (
<div className='text-red-500'>{errors.comment.message}</div>
)}
<button className='primary-button'>
{loading? 'Loading' : 'Submit'}
</button>
</div>
</form>
) : (
<div>
Please <Link href={`/login?redirect=/product/${product.slug}`}>Login</Link> to write a review.
</div>
)}
</div>
</Layout>
)
}
export async function getServerSideProps(context) {
const { params, req } = context;
const { slug } = params;
const session = await getSession({ req });
await db.connect();
const product = await Product.findOne({ slug }).lean();
await db.disconnect();
return (
{
props: {
product: product ? db.convertDocToObj(product) : null,
user: session ? session : null,
},
}
);
}
as my understanding, the getServerSideProps function at the bottom should convert the props into object with below code;
product: product ? db.convertDocToObj(product) : null,
am I using it correctly?
my product schema with review is as below;
import mongoose from 'mongoose';
const reviewSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
name: { type: String, required: true },
rating: { type: Number, default: 5 },
comment: { type: String, required: true },
},
{
timestamps: true,
});
const productSchema = new mongoose.Schema(
{
name: { type: String, required: true },
slug: { type: String, required: true, unique: true },
category: { type: String, required: true },
image: { type: String, required: true },
price: { type: Number, required: true },
brand: { type: String, required: true },
rating: { type: Number, required: true, default: 0 },
numReviews: { type: Number, required: true, default: 0 },
countInStock: { type: Number, required: true, default: 0 },
description: { type: String, required: true },
reviews: [reviewSchema],
}, {
timestamps: true,
}
);
const Product = mongoose.models.Product || mongoose.model('Product', productSchema);
export default Product;
the MongoDB Compass for Product is as below;
So, how can I fix my problew?
Thanks
Stan
latest Axios version(v1.2.0) changed header field, it is know issue
Not working old code, so you can avoid this error by adding header information.
https://github.com/axios/axios/issues/5298
Your three axios API call needs to add header with correct content type.
(I suppose all of json type in your code)
before
await axios.post(`/api/products/${product._id}/reviews`, { rating, comment, user });
const { data } = await axios.get(`/api/products/${product._id}`);
const { data } = await axios.get(`/api/products/${product._id}/reviews`);
after
await axios.post(`/api/products/${product._id}/reviews`, { rating, comment, user }, {
headers: {
'Content-Type': 'application/json'
}
});
const { data } = await axios.get(`/api/products/${product._id}`,
{
headers: {
'Accept-Encoding': 'application/json'
}
});
const { data } = await axios.get(`/api/products/${product._id}/reviews`,
{
headers: {
'Accept-Encoding': 'application/json'
}
});

I keep getting [Unhandled promise rejection: Error: Request failed with status code 404] while deleting a post from my API in React Native

I just started coding and I tried to delete a post through my mongoDB API. The code that I used is pretty basic, my API requests all go through a reducer which is set up pretty clear. But when I try to delete a post from my DB, I get the error ''[Unhandled promise rejection: Error: Request failed with status code 404]''. Did I do something wrong here or is this a mongoDB problem? I've included the necessary code.
This is my reducer request:
const deleteWorkout = (dispatch) => {
return async (_id) => {
await trackerApi.delete(`/workouts/${_id}`);
dispatch({ type: "delete_workout", payload: _id });
};
};
This is my complete reducer code:
import createWorkoutDataContext from "./createWorkoutDataContext";
import trackerApi from "../api/tracker";
const workoutReducer = (workouts, action) => {
switch (action.type) {
case "get_workouts":
return action.payload;
case "add_workout":
return [
...workouts,
{
title: action.payload.title,
exercises: action.payload.exercises,
},
];
case "edit_workout":
return workouts.map((Workout) => {
return Workout._id === action.payload.id ? action.payload : Workout;
});
case "delete_workout":
return workouts.filter((Workout) => Workout._id !== action.payload);
default:
return workouts;
}
};
const getWorkouts = (dispatch) => async () => {
const response = await trackerApi.get("/workouts");
dispatch({ type: "get_workouts", payload: response.data });
};
const addWorkout = (dispatch) => {
return async (title, exercises, callback) => {
await trackerApi.post("/workouts", { title, exercises });
if (callback) {
callback();
}
};
};
const editWorkout = (dispatch) => {
return async (title, exercises, _id, callback) => {
await trackerApi.put(`/workouts/${_id}`, { title, exercises });
dispatch({ type: "edit_workout", payload: { _id, title, exercises } });
if (callback) {
callback();
}
};
};
const deleteWorkout = (dispatch) => {
return async (_id) => {
await trackerApi.delete(`/workouts/${_id}`);
dispatch({ type: "delete_workout", payload: _id });
};
};
export const { Context, Provider } = createWorkoutDataContext(
workoutReducer,
{ addWorkout, getWorkouts, deleteWorkout },
[]
);
This is my code where I use the delete function:
const WorkoutListScreen = () => {
const { workouts, getWorkouts, deleteWorkout } = useContext(WorkoutContext);
return (
<View style={styles.container}>
<Text style={styles.pageTitle}>My Workouts</Text>
<NavigationEvents onWillFocus={getWorkouts} />
<FlatList
data={workouts}
keyExtractor={(item) => item._id}
renderItem={({ item }) => {
return (
<TouchableOpacity
onPress={() => navigate("WorkoutDetail", { _id: item._id })}
>
<View style={styles.row}>
<Text style={styles.title}>{item.title}</Text>
<TouchableOpacity onPress={() => deleteWorkout(item._id)}>
<Ionicons style={styles.deleteIcon} name="trash-outline" />
</TouchableOpacity>
</View>
</TouchableOpacity>
);
}}
/>
I simply included the deleteWorkout function in my TouchableOpacity, so I suppose that the problem lies within the reducer code?
here is the error I keep getting:
[Unhandled promise rejection: Error: Request failed with status code 404]
at node_modules\axios\lib\core\createError.js:16:14 in createError
at node_modules\axios\lib\core\settle.js:17:22 in settle
at node_modules\axios\lib\adapters\xhr.js:66:12 in onloadend
at node_modules\event-target-shim\dist\event-target-shim.js:818:20 in EventTarget.prototype.dispatchEvent
at node_modules\react-native\Libraries\Network\XMLHttpRequest.js:614:6 in setReadyState
at node_modules\react-native\Libraries\Network\XMLHttpRequest.js:396:6 in __didCompleteResponse
at node_modules\react-native\Libraries\vendor\emitter\_EventEmitter.js:135:10 in EventEmitter#emit
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:414:4 in __callFunction
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:113:6 in __guard$argument_0
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:365:10 in __guard
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:4 in callFunctionReturnFlushedQueue
This is what the objects in my databse look like (through Postman):
{
"userId": "615e06f36ce5e5f1a69c675e",
"title": "Defaults test",
"exercises": [
{
"exerciseTitle": "shadowboxing",
"exerciseProps": {
"time": 0,
"sets": 0,
"reps": 0
},
"_id": "6184c6fa685291fb44778df7"
},
{
"exerciseTitle": "bag workout",
"exerciseProps": {
"time": 4,
"sets": 0,
"reps": 12
},
"_id": "6184c6fa685291fb44778df8"
},
{
"exerciseTitle": "Exercise",
"exerciseProps": {
"time": 4,
"sets": 3,
"reps": 12
},
"_id": "6184c6fa685291fb44778df9"
}
],
"_id": "6184c6fa685291fb44778df6",
"__v": 0
}
I'm gratefull for your help!

Patch returning null instead of comments

Hi,
Im rather new to backend. I´m trying to patch a newComment to a list of comments but gets updatedTravelTips null. Where am I doing wrong? and which mongo _id should i target?
I know my database is a bit messy with nested objects...
this it how my users look like in mongoDBcompass:
https://i.stack.imgur.com/moncj.png
const onTravelTips = (event) => {
event.preventDefault()
const options = {
method: 'PATCH',
headers: {
Authorization: accessToken,
'Content-Type': 'application/json'
},
// neWcomment comes from a text input in a form
body: JSON.stringify({ comments: newComment })
}
//countryId comes from selected country from a dropdown and stored in state
fetch(API_URL(`countries/${countryId}`), options)
.then(res => res.json())
.then(data => {
if (data.success) {
console.log(data)
dispatch(user.actions.setErrors(null))
} else {
dispatch(user.actions.setErrors({ message: 'Failed to add travel tips' }))
}
})
}
<form className="add-tips-form">
<p>Choose one of your visited countries and add some tips:</p>
<select value={newCountryId} onChange={(event) => dispatch(user.actions.setCountryId(event.target.value))}>
<optgroup label='Countries'>
<option value="" disabled defaultValue>Select country</option>
{visitedList && visitedList.map(country => (
<option
key={country.country._id}
// country._id gets the new one, country.country._id gets the countryid
value={country._id}
>{country.country.country} {console.log(country._id)}</option>
))}
</optgroup>
</select>
<input
type="text"
value={newComment}
onChange={(event) => setNewComment(event.target.value)}
className="username-input"
placeholder="food"
/>
<button className="add-tips-button" onClick={onTravelTips}>Add travel tips</button>
</form>
const Country = mongoose.model('Country', {
country: String,
alphaCode: String,
})
const User = mongoose.model('User', {
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString('hex')
},
visitedCountries:[ {
country: {
type: Object,
ref: "Country",
},
comments: Array
}]
})
app.patch('/countries/:countryid', authenticateUser)
app.patch('/countries/:countryid', async (req, res) => {
const { countryid } = req.params
const { comments, } = req.body
const {id} = req.user
try {
console.log(countryid) // working, gets the country id or the nested object id depening on what we pass in FE
console.log(comments) // working, gets whatever we write in text input. *should it be so?
console.log(id) // working, gets user id
console.log("comment",newComment) // not working, return undefined
const updatedTravelTips = await User.findOneAndUpdate( {id, countryid, comments }, {
$push: {
visitedCountries: { comments: comments}
},
}, { new: true })
res.json({ success: true, updatedTravelTips })
console.log(updatedTravelTips) // not working, return null
} catch (error) {
res.status(400).json({ success: false, message: "Invalid request", error })
}
})
app.patch('/countries/:countryid', authenticateUser)
app.patch('/countries/:countryid', async (req, res) => {
const { countryid } = req.params
const { comments, } = req.body
const {id} = req.user
try {
const updatedTravelTips = await User.findOneAndUpdate( {_id: id, "visitedCountries._id": countryid }, {
$push: {
"visitedCountries.$.comments": comments
},
}, { new: true })
res.json({ success: true, updatedTravelTips })
} catch (error) {
res.status(400).json({ success: false, message: "Invalid request", error })
}
})

firebase facebook failed authentication

I have a screens where I am trying my hands on firebase's facebook authentication.
Using expo i can successfully generate facebook token. next what i did was to use this token to genrate cerdentials, which i think also ran successfully but when I am trying to use the credentials to signin to firebase i get error login failed.
I am not sure what is the problem. Do I need to register user using email & password before letting them login using facebook auth.
Any help will be appreciated...
here is the code...
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
Button,
StatusBar,
StyleSheet,
View,
Text,
} from 'react-native';
import Expo, { Facebook } from 'expo';
import * as firebase from 'firebase';
import ModalActivityIndicator from '../../components/ModalActivityIndicator';
export default class SignInFacebookScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
};
}
componentDidMount = async () => {
await this.facebookLogin();
};
facebookLogin = async () => {
const { type, token } = await Expo.Facebook.logInWithReadPermissionsAsync(
'<AppId>',
{
permissions: ['public_profile', 'email'],
}
);
if (type === 'success') {
await this.callGraph(token);
} else if (type === 'cancel') {
alert('Cancelled!', 'Login was cancelled!');
} else {
alert('Oops!', 'Login failed!');
}
};
callGraph = async token => {
const response = await fetch(
`https://graph.facebook.com/me?access_token=${token}`
);
const userProfile = JSON.stringify(await response.json());
const credential = firebase.auth.FacebookAuthProvider.credential(token);
await this.firebaseLogin(credential);
};
// Sign in with credential from the Facebook user.
firebaseLogin = async credential => {
firebase
.auth()
.signInAndRetrieveDataWithCredential(credential)
.then(() => {
this.setState({
isLoading: false,
hasError: false,
errorMessage: null,
});
this.props.navigation.navigate('App');
})
.catch(error => {
this.setState({
isLoading: false,
hasError: true,
errorMessage: error.errorMessage,
});
});
};
render() {
let { isLoading, hasError, errorMessage } = this.state;
return (
<View style={styles.container}>
<ModalActivityIndicator isLoading={!!isLoading} />
<Text>Sign In with Facebook</Text>
{hasError && (
<React.Fragment>
<Text style={[styles.errorMessage, { color: 'black' }]}>
Error logging in. Please try again.
</Text>
<Text style={[styles.errorMessage, { color: 'black' }]}>
{errorMessage}
</Text>
</React.Fragment>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
Well I fixed the issue with the following code. Seemed like the issue was with channing of async functions.
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
Button,
StatusBar,
StyleSheet,
View,
Text,
} from 'react-native';
import Expo, { Facebook } from 'expo';
import * as firebase from 'firebase';
import ModalActivityIndicator from '../../components/ModalActivityIndicator';
export default class SignInFacebookScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
firstName: '',
lastName: '',
};
}
componentDidMount = async () => {
const fbToken = await this.getFacebookToken();
const userProfile = await this.getFacebookUserProfile(fbToken);
this.setUserDetails(userProfile);
const credential = await this.getFirebaseFacebookCredential(fbToken);
await this.loginToFirebaseWithFacebook(credential);
};
getFacebookToken = async () => {
const { type, token } = await Expo.Facebook.logInWithReadPermissionsAsync(
'<AppId>',
{
permissions: ['public_profile', 'email'],
}
);
if (type === 'success') {
return token;
} else if (type === 'cancel') {
alert('Cancelled!', 'Login was cancelled!');
} else {
alert('Oops!', 'Login failed!');
}
};
getFacebookUserProfile = async token => {
this.setState({ isLoading: true });
const response = await fetch(
`https://graph.facebook.com/me?access_token=${token}&fields=first_name,last_name`
);
const userProfile = JSON.stringify(await response.json());
return userProfile;
};
setUserDetails = userProfile => {
const userProfileObj = JSON.parse(userProfile);
this.setState({
firstName: userProfileObj.first_name,
lastName: userProfileObj.last_name,
});
};
getFirebaseFacebookCredential = async token => {
const credential = firebase.auth.FacebookAuthProvider.credential(token);
return credential;
};
loginToFirebaseWithFacebook = async credential => {
firebase
.auth()
.signInAndRetrieveDataWithCredential(credential)
.then(() => {
let user = firebase.auth().currentUser;
firebase
.database()
.ref('users/' + user.uid)
.update({
firstName: this.state.firstName,
lastName: this.state.lastName,
});
})
.then(() => {
this.setState({
isLoading: false,
haserror: false,
errorMessage: null,
});
this.props.navigation.navigate('App');
});
};
render() {
let { isLoading, hasError, errorMessage } = this.state;
return (
<View style={styles.container}>
<ModalActivityIndicator isLoading={!!isLoading} />
<Text>Sign In with Facebook</Text>
{hasError && (
<React.Fragment>
<Text style={[styles.errorMessage, { color: 'black' }]}>
Error logging in. Please try again.
</Text>
<Text style={[styles.errorMessage, { color: 'black' }]}>
{errorMessage}
</Text>
</React.Fragment>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});

use ReactJS to .map through response of Express API

I have an Express API that responds with data from MongoDB when requested upon mounting of a reactJS component.
app.get('/api/characters', function(req, res) {
var db = req.db;
var collection = db.get('usercollection');
collection.find({},{},function(e,docs){
res.send({
data: docs
});
});
});
Code for Characters component :
export default class Characters extends React.Component {
constructor(props) {
super(props);
this.state = CharactersStore.getState();
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
CharactersStore.listen(this.onChange);
CharactersActions.getCharacters('http://localhost:3000/api/characters');
}
componentWillUnmount() {
CharactersStore.unlisten(this.onChange);
}
onChange(state) {
this.setState(state);
}
render() {
return (
<section>
<div className="container">
<div className="row">
<h2 className="text-center">{this.props.route.header}</h2>
<hr className="star-light"/>
<CharacterList data={this.state.characters}/>
</div>
</div>
</section>
)
}
}
Code for Characters component Action
class CharactersActions {
constructor() {
this.generateActions(
'getCharactersSuccess',
'getCharactersFail'
);
}
getCharacters(query) {
requestPromise(query)
.then((res) => {
this.actions.getCharactersSuccess(res)
}).catch((err) => {
console.log('error:', err);
this.actions.getCharactersFail(err)
})
}
}
export default alt.createActions(CharactersActions);
Code for Characters component Store
class CharactersStore {
constructor() {
this.bindActions(CharactersActions);
this.characters = [''];
this.isLoading = true;
}
getCharactersSuccess(res) {
this.characters = res;
this.isLoading = false;
}
getCharactersFail(err) {
toastr.error(err.responseJSON && err.responseJSON.message || err.responseText || err.statusText);
}
}
export default alt.createStore(CharactersStore);
The Characters component above requests the API upon mounting and sends the response data along to the store to be saved into State.
I then pass the Characters state into the child component CharacterList as props (named data)
Characters List Component
export default class CharacterList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="col-lg-3 col-sm-6">
{console.log(this.props.data)}
// THIS IS WHERE I AM TRYING TO .MAP THROUGH THE RESULTS
</div>
)
}
}
I am trying to use .map to loop through the returned data but am a little unsure on how to proceed, any advice would be appreciated. Data is being returned as follows:
{
"data": [
{
"_id": "58d5044b0898f816066227f1",
"character": "Luke Skywalker"
},
{
"_id": "58d504c60898f816066227f2",
"character": "Obi Wan Kenobi"
},
{
"_id": "58d504c60898f816066227f3",
"character": "Han Solo"
}
]
}
You want to set res.data as your characters store.
getCharactersSuccess(res) {
this.characters = res.data; // <--
this.isLoading = false;
}
Then you can map over the data array, and not the full response:
const data = [
{
_id: '58d5044b0898f816066227f1',
character: 'Luke Skywalker'
},
{
_id: '58d504c60898f816066227f2',
character: 'Obi Wan Kenobi'
},
{
_id: '58d504c60898f816066227f3',
character: 'Han Solo'
}
];
const Character = ({ data }) => <div>{data.character}</div>;
class CharacterList extends React.Component {
render() {
return (
<div>
{this.props.data.map((character, i) => (
<Character key={i} data={character} />
))}
</div>
);
}
}
ReactDOM.render(<CharacterList data={data} />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
It is simple.I dont know where you confused. try this
<div className="col-lg-3 col-sm-6">
{
Array.isArray(this.props.data)&& this.props.data.map((val,index)=>{
return val
})
}
</div>