How to continuously add objects to nested collection in Firestore - javascript

I am writing a React/Redux app that uses Firebase Auth/Firestore to keep track of a user's gym exercises. I have Redux Form to handle data submission and I have the below example data structure I want to achieve in Firestore:
users {
id {
name: 'John Smith'
uid: 'k1s7fxo9oe2ls9u' (comes from Firebase Auth)
exercises: {
{
name: 'Bench Press',
sets: 3,
reps: 10,
lbs: 100,
}
}
}
}
However, I can't figure out how to keep adding new exercise objects to the exercises subcollection (in Firestore I guess it would be a field type of map). What I want to do is have new objects in "exercises" as the user submits new forms. So for example, if the user wanted to add a Deadlift exercise, it would look like the below:
users {
id {
name: 'John Smith'
uid: 'k1s7fxo9oe2ls9u' (comes from Firebase Auth)
exercises: {
{
name: 'Bench Press',
sets: 3,
reps: 10,
lbs: 100,
},
{
name: 'Deadlift',
sets: 3,
reps: 12,
lbs: 120,
}
}
}
}
Calling db.collection('users').doc(doc.id).add({"exercises": values});
just updates the Bench Press object that's there already rather than adding a new one on submission of the form.
But calling db.collection('users').doc(doc.id).add({"exercises": values}); gives me this error: Uncaught (in promise) TypeError: _firebase__WEBPACK_IMPORTED_MODULE_3__.default.collection(...).doc(...).add is not a function.
I've been struggling with this for quite a while, any help is hugely appreciated.
This is my component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import db from '../../../firebase';
import '#firebase/firestore';
import { store } from '../../../App';
const formSubmit = (values)=> {
const currentUserId = store.getState().auth.uid;
db.collection("users").get().then((usersSnapshot) => {
usersSnapshot.forEach((doc) => {
// looking for the current user and then updating their data
if(doc.data().uid === currentUserId) {
db.collection('users').doc(doc.id).add({
"exercises": values,
});
}
});
});
}
let ExercisesForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit(formSubmit)}>
<div>
<Field name="name" component="input" type="text" placeholder="Exercise Name" />
</div>
<div>
<Field name="sets" component="input" type="number" />
<label htmlFor="sets"> sets</label>
</div>
<div>
<Field name="reps" component="input" type="number" />
<label htmlFor="reps"> reps</label>
</div>
<div>
<Field name="lbs" component="input" type="number" />
<label htmlFor="lbs"> lbs</label>
</div>
<button type="submit">Submit</button>
</form>
)
ExercisesForm = reduxForm({
form: 'exercise'
})(ExercisesForm)
const mapStateToProps = state => ({
uid: state.auth.uid,
});
export default connect(
mapStateToProps,
undefined
)(ExercisesForm);

You should be able to say:
db
.collection('users')
.doc(doc.id)
.collection('exercises')
.add(values);
Where values contains all the fields of the document you want to add. It will create a new document with a random id in the exercises subcollection.

So this won't work in the latest version of Firebase V9.
So below is how I added a subcollection to a document in the latest version:
const docRef = doc(database, "userDocs", session.user.email);
const colRef = collection(docRef, "docs");
await addDoc(colRef, {
fileName: input,
timestamp: serverTimestamp(),
})
.then(() => {
console.log("CREATED");
})
.catch((err) => {
console.error("Error creating document", err);
});
This will created a structure like
userDocs
-> userEmail
-> docs
-> uid
-> data

Related

What is the proper way of testing a component in Vue with API calls?

I have a problem understand testing Vue components. In the component testing docs, it says:
Component tests should focus on the component's public interfaces
rather than internal implementation details. For most components, the
public interface is limited to: events emitted, props, and slots. When
testing, remember to test what a component does, not how it does it.
Say you have a Form Component:
<template>
<form #submit.prevent="submitForm">
Email: <input type="text" data-test="email" v-model="user.email" /> <br />
First: <input type="text" data-test="first" v-model="user.first" /> <br />
Last: <input type="text" data-test="last" v-model="user.last" /> <br />
<button type="submit">Create User</button>
</form>
</template>
<script>
import { createUser } from '#/api'
import { notify } from '#/helpers'
export default {
data: () => ({
user: {
email: '',
first: '',
last: ''
}
}),
methods: {
async submitForm() {
try {
const result = await createUser(this.user)
notify.success({
message: 'Success'
})
this.$router.push({ name: 'Home' })
} catch {
notify.error({
message: 'Error occurred'
})
}
}
}
}
</script>
and here's how I test it:
import router from '#/router';
import * as fetchers from '#/api'
import { notify } from '#/helpers'
import fp from 'flush-promises'
test('should save car details', async () => {
const successSpy = jest.spyOn(notify, 'success')
const createUserSpy = jest.spyOn(fetchers, 'createUser')
const routerPushSpy = jest.spyOn(router, 'push')
const wrapper = mount(Form)
await wrapper.find('[data-test="email"]').setValue('test#email.com');
await wrapper.find('[data-test="first"]').setValue('John');
await wrapper.find('[data-test="last"]').setValue('Doe');
await wrapper.find('button').trigger('click')
await fp();
expect(successSpy).toHaveBeenCalledWith({
message: 'Success',
})
expect(createUserSpy).toHaveBeenCalledWith({
email: 'test#email.com',
first: 'John',
last: 'Doe'
});
expect(routerPushSpy).toHaveBeenCalledWith({ name: 'Home' });
});
While this test works, I am not sure if this is the correct way of component testing based on Vue docs. Looks like I'm testing internal details in that test?
How should I do it?

How to update JSON data using input fields created using myData.map?

The JSON data is used to create dynamic Input fields for each item in the array. I would like the JSON data to be updated to match the quantity selected but am unsure the best way to go about this?
I plan on using Hooks to initially store the number of items selected then update the JSON file with a button press, although I am very open to the JSON file updating onChange. what is the best practise for this can you dynamically create react hooks?
here is my current code(I want the quantity to update in the JSON file).
JSON:
//Json data for the shopping ingredients
export default [
{
bread: {
Quantity: 0,
},
Name: 'Bread',
Price: "1.10",
},
{
milk: {
Quantity: 0,
},
Name: 'Milk',
Price: "0.50",
},
{
cheese: {
Quantity: 0,
},
Name: 'Cheese',
Price: "0.90",
},
{
soup: {
Quantity: 0,
},
Name: 'Soup',
Price: "0.60",
},
{
butter: {
Quantity: 0,
},
Name: 'Butter',
Price: "1.20",
}
]
React:
import React, { useState, useEffect } from "react";
import Data from '../shoppingData/Ingredients';
const ShoppingPageOne = (props) => {
//element displays
const [pageone_show, setPageone_show] = useState("pageOne");
//updates quatity of ingredients
const [bread_quantity, setBread_quantity] = useState(0);
const [milk_quantity, setMilk_quantity] = useState(0);
const [cheese_quantity, setCheese_quantity] = useState(0);
const [soup_quantity, setSoup_quantity] = useState(0);
const [butter_quantity, setButter_quantity] = useState(0);
useEffect(() => {
//sets info text using Json
if (props.showOne) {
setPageone_show("pageOne");
} else {
setPageone_show("pageOne hide");
}
}, [props.showOne]);
return (
<div className={"Shopping_Content " + pageone_show}>
{Data.map((Ingredients) => {
return <div className="Shopping_input" key={Ingredients.Name}>
<p>{Ingredients.Name} £{Ingredients.Price}</p>
<input onChange={} type="number"></input>
</div>
})}
<div className="Shopping_Buttons">
<p onClick={props.next_ClickHandler}>Buy Now!</p>
</div>
</div>
);
};
export default ShoppingPageOne;
Having input fields generated dynamically from a JSON file is great but using static hooks to update the JSON seems rather silly.
To work with a file system you need to have particular modules. You can do it using NodeJS using 'fs' module https://nodejs.org/dist/latest-v15.x/docs/api/fs.html.
You need a separate endpoint that will be responsible for data updating that will be on the server-side.

Mapping Array of Objects in ReactJS to create login form

Im currently trying to create a login form in ReactJS (which does not have any backend and is relying on a Users.js file for input.
My App.js looks something like this:
import React from 'react';
import myUser from './User'
import './App.css';
class App extends React.Component{
constructor(){
super()
this.state={userName:"",password:"",message:false} //myUser:[]
this.eventHandler=this.eventHandler.bind(this)
this.postDetails=this.postDetails.bind(this)
}
eventHandler(event){
const {name,value}=event.target
this.setState({[name]:value})
}
postDetails(event){
event.preventDefault()
return(this.state.userName===myUser.name&&this.state.password===myUser.password?
this.setState({message:true}):this.setState({message:false}))
}
render(){
return(
<div className="main-div">
<h1>{this.state.message===true?"Success":"Try again"}</h1>
<form>
<input type="text" placeholder="enter name" name="userName" onChange={this.eventHandler} />
<br />
<br />
<input type="password" placeholder="enter password" name="password" onChange={this.eventHandler} />
<br />
<br/>
<button value="submit" onClick={this.postDetails}>Submit</button>
</form>
</div>
)
}
}
export default App;
and my User.js looks something like this:
const users ={id:1,name:"mahesh",password:"mahesh123"}
export default users
So the above code only check whether the username and password fields entered in the form match the name and password of single record in the array of objects of User.js
the above code is working fine. But what if I wanted to make an array of objects , supposing:
const users =[{id:1,name:"mahesh",password:"mahesh123"},{id:2,name:"abc",password:"abc123"}]
and wanted to compare for multiple records? Do i have to use mapping? Please show an instance of how it is done. Please help , i'm fairly new to React. My apologies for the formatting.
Getting accustomed with most common array prototype methods ( like .some() ) would help a lot with solving those kinds of problems.
export const users = [
{ id: 0, name: 'user1', password: 'asd1' },
{ id: 0, name: 'user2', password: 'asd2' },
{ id: 0, name: 'user3', password: 'asd3' },
];
Then your postDetails would need to look like this:
import { users } from '...';
// ...
postDetails() {
const isUserValid = users.some(user => {
const username = this.state.userName;
const password = this.state.password;
return user.name === username && user.password === password;
});
this.setState({ message: isUserValid });
};
There is the functhion which try to find a user first and then if we find object with same name we check the password. If something isn't valid the function returns false otherwise it returns true
const users =[
{id:1,name:"mahesh",password:"mahesh123"},
{id:2,name:"abc",password:"abc123"}
]
const validation = (login, password) => {
const user = users.find(user => login === user.name) // find the user with same name
if (typeof user !== 'undefined') { // check the user. If we didn't find a object with same name, user will be undefined
return user.password === password // if passwords match it returns true
}
return false
}
console.log(validation('mahesh', 'mahesh123'))
console.log(validation('abc', 'abc123'))
console.log(validation('abc', 'sffh'))
console.log(validation('abdsawec', 'abc123'))

How to change DOM using onSubmit in React JS?

I am using react for the front end of a search application.
When user submits a query and a list of results pop up, each with a button that says "Learn More". When the "Learn More" button is pressed, the list of results should all disappear and be replaced with the information on that topic that was selected.
The search bar above should stay in place, if a user searches new information, the learn more info should go away and the new list of results should appear.
I am having trouble displaying the learn more information.
The biggest issue I am having is that I have to use the form with the onSubmit function and as soon as the onSubmit function is called my results will stay for a few seconds and then everything will disappear.
The following shows the parts of my file related to the issue
class Search extends React.Component {
learnMore(obj){
//Here is where i would like to replace the results class with the learn more info. obj.learnMore has the info stored
}
render() {
return (
<div className="search">
<div className="search-bar">
// Here is where my search bar is, results of search get added to results array
</div>
<div className= "results">
{this.state.results.map((obj) =>
<div key={obj.id}>
<p> {obj.name} </p>
<form id= "learn-more-form" onSubmit={() => {this.learnMore(obj); return false;}}>
<input type="submit" value="Learn More"/>
</form>
</div>
)}
</div>
</div>
);
}
}
There are many ways to handle this scenario. In this case, I recommend separating containers from components. The container will handle all things state and update its children components accordingly.
Please note that this example uses a lot of ES6 syntaxes. Please read the following to understand how some of it works: fat arrow functions, ES6 destruction, spread operator, ternary operator, class properties, a controlled react form utilizing event handlers and state, array filtering, and type checking with PropTypes.
It's a lot to take in, so if you have any questions, feel free to ask.
Working example:
containers/SeachForm
import React, { Component } from "react";
import moment from "moment";
import LearnMore from "../../components/LearnMore";
import Results from "../../components/Results";
import SearchBar from "../../components/Searchbar";
const data = [
{
id: "1",
name: "Bob",
age: 32,
email: "bob#example.com",
registered: moment("20111031", "YYYYMMDD").fromNow(),
description: "Bob is a stay at home dad."
},
{
id: "2",
name: "Jane",
age: 43,
email: "jane#example.com",
registered: moment("20010810", "YYYYMMDD").fromNow(),
description: "Jane is a CEO at Oracle."
},
{
id: "3",
name: "Yusef",
age: 21,
email: "yusef#example.com",
registered: moment("20180421", "YYYYMMDD").fromNow(),
description: "Yusef is a student at UC Berkeley."
},
{
id: "4",
name: "Dasha",
age: 29,
email: "dasha#example.com",
registered: moment("20050102", "YYYYMMDD").fromNow(),
description: "Dasha is an owner of a local antique shop."
},
{
id: "5",
name: "Polina",
age: 18,
email: "dasha#example.com",
registered: moment("20190102", "YYYYMMDD").fromNow(),
description: "Polina works at a local movie theather."
}
];
const initialState = {
searchQuery: "",
results: data, // <== change this to an empty array if you don't want to show initial user data
learnMore: false
};
class SearchForm extends Component {
state = { ...initialState }; // spreading out the initialState object defined above; it'll be the same as: "state = { searchQuery: "", results: data, learnMore: false }; "
handleSubmit = e => {
e.preventDefault(); // prevents a page refresh
if (!this.state.searchQuery) return null; // prevents empty search submissions
this.setState({
results: data.filter(
person => person.name.toLowerCase() === this.state.searchQuery.toLowerCase()
) // filters the dataset with the "searchQuery" (lowercased names) and returns the result if it finds a match
});
};
handleSearch = ({ target: { value } }) =>
this.setState({ searchQuery: value }); // updates searchQuery input with an event.target.value
handleReset = () => this.setState({ ...initialState }); // resets to initial state
handleLearnMore = person => {
this.setState({ learnMore: true, results: person }); // sets learnMore to true (to show the "LearnMore" component) and sets results to the selected user
};
render = () => (
<div className="container">
<SearchBar
handleReset={this.handleReset}
handleSearch={this.handleSearch}
handleSubmit={this.handleSubmit}
searchQuery={this.state.searchQuery}
/>
{!this.state.learnMore ? ( // if learnMore is false, then show "Results"
<Results
results={this.state.results}
handleLearnMore={this.handleLearnMore}
/>
) : (
<LearnMore {...this.state.results} /> // otherwise, show LearnMore
)}
</div>
);
}
export default SearchForm;
components/SearchBar
import React from "react";
import PropTypes from "prop-types";
const SearchBar = ({
handleReset,
handleSearch,
handleSubmit,
searchQuery
}) => (
<div className="search">
<div className="search-bar">
<form onSubmit={handleSubmit}>
<input
type="text"
className="uk-input"
value={searchQuery}
placeholder="Search for a name"
onChange={handleSearch}
/>
<div className="button-container">
<button
type="button"
className="uk-button uk-button-danger reset"
onClick={handleReset}
>
Reset
</button>
<button type="submit" className="uk-button uk-button-primary submit">
Submit
</button>
</div>
</form>
</div>
</div>
);
SearchBar.propTypes = {
handleReset: PropTypes.func.isRequired,
handleSearch: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
searchQuery: PropTypes.string
};
export default SearchBar;
components/Results
import React from "react";
import PropTypes from "prop-types";
const Results = ({ handleLearnMore, results }) => (
<div className="results">
{results && results.length > 0 ? (
results.map(person => (
<div key={person.id} className="uk-card uk-card-default uk-width-1-2#m">
<div className="uk-card-header">
<div className="uk-width-expand">
<h3 className="uk-card-title uk-margin-remove-bottom">
{person.name}
</h3>
</div>
</div>
<div className="uk-card-body">
<p>{person.description}</p>
</div>
<div className="uk-card-footer">
<button
onClick={() => handleLearnMore(person)}
className="uk-button uk-button-text"
>
Learn More
</button>
</div>
</div>
))
) : (
<div className="uk-placeholder">No users were found!</div>
)}
</div>
);
Results.propTypes = {
handleLearnMore: PropTypes.func.isRequired,
results: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
age: PropTypes.number,
email: PropTypes.string,
registered: PropTypes.string,
description: PropTypes.string
})
)
};
export default Results;
components/LearnMore
import React from "react";
import PropTypes from "prop-types";
const LearnMore = ({ name, email, age, description, registered }) => (
<div className="uk-card uk-card-default uk-card-body">
<h3 className="uk-card-header">{name}</h3>
<p>
<strong>Email</strong>: {email}
</p>
<p>
<strong>Registered</strong>: {registered}
</p>
<p>
<strong>Age</strong>: {age}
</p>
<p>
<strong>Job</strong>: {description}
</p>
</div>
);
LearnMore.propTypes = {
name: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
registered: PropTypes.string.isRequired,
description: PropTypes.string.isRequired
};
export default LearnMore;
You should do your onSubmit like this:
<form id= "learn-more-form" onSubmit={this.learnMore(obj)}>
<input type="submit" value="Learn More"/>
</form>
Then the function should be:
learnMore = (data) => (e) => {
e.preventDefault()
console.log(data) // probably setState with this data so you can display it when it, like this.setState({ currentMoreResults: data })
}

React JS- Encode data of a config file and pass it to Header for api calls

In my application, I am trying to do base 64 encoding for the user details so that I can pass these data in subsequent api calls. For example, if the logged in user is Rohn, the localStorage should have the encoded data for Rohn and likewise. To do that,I have created a config file where I have saved all the user details like below-
config.json-
{
"Rohn": {
"age": "30",
"city": "Mexico",
"occupation": "service",
},
"Max": {
"age": "32",
"city": "Colorado",
"occupation": "Business",
},
}
I want these data to be encoded to base 64 and passed in a Header for every request params. So to do that, I am including this config.json file in my LogIn Component like below-
import React from "react";
import Header from "./header";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { createLogIn } from "../actions/action";
const axios = require("axios");
import config from "../config/config";
class Login extends React.Component {
constructor() {
super();
this.state = {
account : { user:'', password: ''},
authorizationError: false,
UserData: ""
};
let userDetails = this.state.UserData;
localStorage.setItem("UserData", btoa(JSON.stringify(config[this.state.account.user])))
}
componentDidMount () {
var currentUserJwt = config[this.state.account.user];
console.log(currentUserJwt);
}
handleAccountChange = ({ target: input}) => {
const account = {...this.state.account}
account[input.name] = input.value
this.setState({ account})
};
handleLoginForm = (e) => {
e.preventDefault();
let postLoginData = {};
// call to action
this.props.dispatch(createLogIn(postLoginData));
this.props.history.push('/intro');
this.setState({ authorizationError: false });
};
render() {
const {account} = this.state;
return (
<div className="intro">
<form onSubmit={this.handleLoginForm}>
<div className="content container">
<div className="row">
<div className="logo-wrap">
<div className="logo intro-logo">
</div>
</div>
</div>
<div className="row">
<div className="col-xs-12">
<input
type="text"
autoFocus
placeholder="username"
name="user"
value={account.user}
onChange={this.handleAccountChange}
/>
<input
type="password"
placeholder="password"
name="password"
value={account.password}
onChange={this.handleAccountChange}
/>
<button className="lg-cta">
<span>Sign in</span>
</button>
</div>
</div>
</div>
</form>
</div>
);
}
}
const mapStateToProps = state => ({
logIn: state.logIn
});
export default connect(mapStateToProps)(Login);
I am trying to encode the data for a corresponding logged in user and send the encoded data in my api calls but the above approach is not working.I am not able to see the encoded data in my localStorage and hence not able to pass this in header for subsequent calls.

Categories

Resources