Sending data from React form to Firebase database - forms

I am working on a form in React and wanting to send the data collected to the Firebase database. However, I'm not entirely sure on how to set this up efficiently. I have posted below some snippets of some of the code I have so far.
Here is the beginning of my component. From my understanding the componentDidMount is pulling the data from the json file to have into those fields. But I'm not sure if that is where I should enter the code to send to Firebase.
class FormContainer extends Component {
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: '',
email: '',
startDate: moment(),
courseName: '',
courseCity: '',
courseStateOptions: [],
courseStateSelection: '',
holeNumberOptions: [],
holeNumberSelection: '',
yardage: '',
clubOptions: [],
clubSelection: ''
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleClearForm = this.handleClearForm.bind(this);
this.handleFirstNameChange = this.handleFirstNameChange.bind(this);
this.handleLastNameChange = this.handleLastNameChange.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handleDateChange = this.handleDateChange.bind(this);
this.handleCourseNameChange = this.handleCourseNameChange.bind(this);
this.handleCourseCityChange = this.handleCourseCityChange.bind(this);
this.handleCourseStateSelect = this.handleCourseStateSelect.bind(this);
this.handleHoleNumberSelect = this.handleHoleNumberSelect.bind(this);
this.handleYardageChange = this.handleYardageChange.bind(this);
this.handleClubSelect = this.handleClubSelect.bind(this);
}
componentDidMount() {
fetch('./nhior_db.json')
.then(res => res.json())
.then(data => {
this.setState({
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
date: data.date,
courseName: data.courseName,
courseCity: data.courseCity,
courseStateOptions: data.courseStateOptions,
courseStateSelection: data.courseStateSelection,
holeNumberOptions: data.holeNumberOptions,
holeNumberSelection: data.holeNumberSelection,
yardage: data.yardage,
clubOptions: data.clubOptions,
clubSelection: data.clubSelection
});
});
}
Below this I have all of my handleFirstNameChange() functions, etc.. I won't post them all in here, but here are a few for reference.
handleCourseNameChange(e) {
this.setState({ courseName: e.target.value }, () => console.log('course name:', this.state.courseName));
}
handleCourseCityChange(e) {
this.setState({ courseCity: e.target.value }, () => console.log('course city:', this.state.courseCity));
}
handleCourseStateSelect(e) {
this.setState({ courseStateSelection: e.target.value}, () => console.log('course state', this.state.courseStateSelection));
}
handleHoleNumberSelect(e) {
this.setState({ holeNumberSelection: e.target.value}, () => console.log('hole number', this.state.holeNumberSelection));
Then I have my handleClearForm() and handleFormSubmit()
handleFormSubmit(e) {
e.preventDefault();
const formPayload = {
firstName: this.state.firstName,
lastName: this.state.lastName,
email: this.state.email,
date: this.state.date,
courseName: this.state.courseName,
courseCity: this.state.courseCity,
courseStateSelection: this.state.courseStateSelection,
holeNumberSelection: this.state.holeNumberSelection,
yardage: this.state.yardage,
clubSelection: this.state.clubSelection
};
alert('Thanks for the submission!');
// console.log('Send this in a POST request:', formPayload)
this.handleClearForm(e);
}
Lastly the render method contains all the inputs, here are a few.
render() {
return (
<form className="container" onSubmit={this.handleFormSubmit}>
<h6>If you are one of the SPECIAL FEW to make a hole in one, you have the opportunity to record your success in the national registry!
Please enter your information, the date of your Hole-In One and click Continue.</h6>
<SingleInput
inputType={'text'}
title={'First name'}
name={'name'}
controlFunc={this.handleFirstNameChange}
content={this.state.firstName}
placeholder={'First Name'} />
<SingleInput
inputType={'text'}
title={'Last name'}
name={'name'}
controlFunc={this.handleLastNameChange}
content={this.state.lastName}
placeholder={'Last Name'} />
<SingleInput
inputType={'text'}
title={'Email'}
name={'name'}
controlFunc={this.handleEmailChange}
content={this.state.email}
placeholder={'Email'} />
<DatePicker
selected={this.state.startDate}
onChange={this.handleDateChange}/>
I just need to know if there is a better more efficient way to send the data collected to Firebase.

If I understand correctly, you are asking two questions:
where I should enter the code to send to Firebase, and
how to set this up efficiently.
The answer depends on what you mean by "efficient".
To simply get your example to work, you add the firebase call inside handleFormSubmit, right where your console.log is. But you probably knew that.
So what's efficient? If you want to organize your code in a manageable way, then a good start is a state framework like MobX or Redux. It is a good idea to keep most of your state (i.e. data you get from firebase) in one place.
I use react-redux, and essentially separate my logic into 2 kinds of controllers. One controller (often called async functions or thunks) handles your database fetching/saving and the other controller prepares (maps) your data and event handlers for the view. This way I rarely use this.setState(...) or this.state and get a clear, unit-testable separation of concerns.
With your example, you would dispatch an async action (in redux terminology) in handleFormSubmit, and have this action push data to firebase and reflect the change (e.g. saving status) in the global state store. Then you simply render your component with data passed from the store into the props.

Related

Insert into relationship table using id created at user registration

I have two tables as seen below
The first table is for users and is populated via a registration form on the client side. When a new user is created, I need the second 'quotas' table to be populated with date, amount, and linked with the user id. The 'user_id' is used to pull the quotas information in a GET and display client side. I am having issues using the 'id' to populate the second table at the time of creation. I am using knex to make all queries. Would I be using join to link them in knex?
server
hydrateRouter // get all users
.route('/api/user')
.get((req, res) => {
knexInstance
.select('*')
.from('hydrate_users')
.then(results => {
res.json(results)
})
})
.post(jsonParser, (req, res, next) => { // register new users
const { username, glasses } = req.body;
const password = bcrypt.hashSync(req.body.password, 8);
const newUser = { username, password, glasses };
knexInstance
.insert(newUser)
.into('hydrate_users')
.then(user => {
res.status(201).json(user);
})
.catch(next);
})
client
export default class Register extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
glasses: 0
}
}
handleSubmit(event) {
event.preventDefault();
fetch('http://localhost:8000/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.state)
})
.then(response => response.json())
.then(responseJSON => {
this.props.history.push('/login');
})
}
server side route for displaying the water amount
hydrateRouter
.route('/api/user/waterconsumed/:user_id') // display water consumed/day
.all(requireAuth)
.get((req, res, next) => {
const {user_id} = req.params;
knexInstance
.from('hydrate_quotas')
.select('amount')
.where('user_id', user_id)
.first()
.then(water => {
res.json(water)
})
.catch(next)
})
Thank you!
Getting the id of an inserted row
So this is a common pattern in relational databases, where you can't create the egg until you have the unique id of the chicken that lays it! Clearly, the database needs to tell you how it wants to refer to the chicken.
In Postgres, you can simply use Knex's .returning function to make it explicit that you want the new row's id column returned to you after a successful insert. That'll make the first part of your query look like this:
knexInstance
.insert(newUser)
.into('users')
.returning('id')
Note: not all databases support this in the same way. In particular, if you happen to be developing locally using SQLite, it will return the number of rows affected by the query, not the id, since SQLite doesn't support SQL's RETURNING. Best is just to develop locally using Postgres to avoid nasty surprises.
Ok, so we know which chicken we're after. Now we need to make sure we've waited for the right id, then go ahead and use it:
.then(([ userId ]) => knexInstance
.insert({ user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT })
.into('quotas')
)
Or however you choose to populate that second table.
Note: DATE is a SQL keyword. For that reason, it doesn't make a great column name. How about created or updated instead?
Responding with sensible data
So that's basic "I have the ID, let's insert to another table" strategy. However, you actually want to be able to respond with the user that was created... this seems like sensible API behaviour for a 201 response.
What you don't want to do is respond with the entire user record from the database, which will expose the password hash (as you're doing in your first code block from your question). Ideally, you'd probably like to respond with some UI-friendly combination of both tables.
Luckily, .returning also accepts an array argument. This allows us to pass a list of columns we'd like to respond with, reducing the risk of accidentally exposing something to the API surface that we'd rather not transmit.
const userColumns = [ 'id', 'username', 'glasses' ]
const quotaColumns = [ 'amount' ]
knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
.then(([ user]) => knexInstance
.insert({
user_id: user.id,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
.then(([ quota ]) => res.status(201)
.json({
...user,
...quota
})
)
)
Async/await for readability
These days, I'd probably avoid a promise chain like that in favour of the syntactic sugar that await provides us.
try {
const [ user ] = await knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
const [ quota ] = await knexInstance
.insert({
user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
res
.status(201)
.json({
...user,
...quota
})
} catch (e) {
next(Error("Something went wrong while inserting a user!"))
}
A note on transactions
There are a few assumptions here, but one big one: we assume that both inserts will be successful. Sure, we provide some error handling, but there's still the possibility that the first insert will succeed, and the second fail or time out for some reason.
Typically, we'd do multiple insertions in a transaction block. Here's how Knex handles this:
try {
const userResponse = await knexInstance.transaction(async tx => {
const [ user ] = await tx.insert(...)
const [ quota ] = await tx.insert(...)
return {
...user,
...quota
}
})
res
.status(201)
.json(userResponse)
} catch (e) {
next(Error('...'))
}
This is good general practice for multiple inserts that depend on each other, since it sets up an "all or nothing" approach: if something fails, the database will go back to its previous state.

v-model and passing a parameter to a rest patch api call

Very new to Vue2, so far so good but I hit a little snag and front end is not my forte.
The table(vue-tables-2) displays correctly what's in the database. I am passing an id in a function to determine what particular row to update but I also want to update the value of the checkbox in the database whenever I press it. How can I achieve that? Many thanks.
<v-client-table :data="tableData" :columns="columns" :options="options" >
<input type="checkbox" v-model="props.row.powerOff" #change="powerOff(props.row.id, props.row.powerOff)">
</v-client-table>
export default {
data() {
return {
columns: ['id', 'name', 'location.address', 'status', 'payment', 'powerOff'],
tableData: []
}
},
created() {
HTTP.get('test').then( response => {this.tableData = response.data;})
.catch( error => {});
},
methods: {
powerOff(id, currentPowerOff) {
var testURL = 'test/' + id
HTTP.patch(testURL, {id, currentPowerOff})//
.then( response => {})
.catch( error => {console.log(error); });
}
}
}
It seems that changing from v-click:on to #change fixed my issue. Reading a little bit more about it, click event is run before v-model has updated the value, while #change does it afterwards. Thank you !

Uspert multiple documents with MongoDB/Mongoose

Say I have a list of models:
const documents = [{}, {}, {}];
And I want to insert these into the DB, or update them all, but only if a condition is met:
Model.update({isSubscribed: {$ne: false}}, documents, {upsert:true},(err, result) => {
});
The above signature is surely wrong - what I want to do is insert/update the documents, where the condition is met.
There is this Bulk API:
https://docs.mongodb.com/manual/reference/method/Bulk.find.upsert/
but I can't tell if it will work when inserting multiple documents.
Imagine this scenario: We have a list of employees and a form of some sorts to give them all a penalty, at once, not one by one :)
On the backend side, you would have your eg addBulk function. Something like this:
Penalty controller
module.exports = {
addBulk: (req, res) => {
const body = req.body;
for (const item of body) {
Penalty.create(item).exec((err, response) => {
if (err) {
res.serverError(err);
return;
}
});
res.ok('Penalties added successfully');
}
}
Then you'll probably have an API on your frontend that directs to that route and specific function (endpoint):
penaltyApi
import axios from 'axios';
import {baseApiUrl} from '../config';
const penaltyApi = baseApiUrl + 'penalty'
class PenaltyApi {
static addBulk(penalties) {
return axios({
method: 'post',
url: penaltyApi + '/addBulk',
data: penalties
})
}
}
export default PenaltyApi;
...and now let's make a form and some helper functions. I'll be using React for demonstration, but it's all JS by the end of the day, right :)
// Lets first add penalties to our local state:
addPenalty = (event) => {
event.preventDefault();
let penalty = {
amount: this.state.penaltyForm.amount,
unit: this.state.penaltyForm.unit,
date: new Date(),
description: this.state.penaltyForm.description,
employee: this.state.penaltyForm.employee.value
};
this.setState(prevState => ({
penalties: [...prevState.penalties, penalty]
}));
}
Here we are mapping over our formData and returning the value and passing it to our saveBulkEmployees() function
save = () => {
let penaltiesData = Object.assign([], this.state.penalties);
penaltiesData.map(penal => {
penal.employeeId = penal.employee.id;
delete penal.employee;
return penaltiesData;
});
this.saveBulkEmployees(penaltiesData);
}
...and finally, let's save all of them at once to our database using the Bulk API
saveBulkEmployees = (data) => {
PenaltyApi.addBulk(data).then(response => {
this.success();
console.log(response.config.data)
this.resetFormAndPenaltiesList()
}).catch(error => {
console.log('error while adding multiple penalties', error);
throw(error);
})
}
So, the short answer is YES, you can absolutely do that. The longer answer is above :) I hope this was helpful to you. If any questions, please let me know, I'll try to answer them as soon as I can.

Access mongoose parent document for default values in subdocument

I have a backend API for an Express/Mongo health tracking app.
Each user has an array of weighIns, subdocuments that contain a value, a unit, and the date recorded. If no unit is specified the unit defaults to 'lb'.
const WeighInSchema = new Schema({
weight: {
type: Number,
required: 'A value is required',
},
unit: {
type: String,
default: 'lb',
},
date: {
type: Date,
default: Date.now,
},
});
Each user also has a defaultUnit field, that can specify a default unit for that user. If that user posts a weighIn without specifying a unit, that weighIn should use the user's defaultUnit if present or else default to 'lb'.
const UserSchema = new Schema({
email: {
type: String,
unique: true,
lowercase: true,
required: 'Email address is required',
validate: [validateEmail, 'Please enter a valid email'],
},
password: {
type: String,
},
weighIns: [WeighInSchema],
defaultUnit: String,
});
Where is correct location for this logic?
I can easily do this in the create method of my WeighInsController, but this seems at best not best practice and at worst an anti-pattern.
// WeighInsController.js
export const create = function create(req, res, next) {
const { user, body: { weight } } = req;
const unit = req.body.unit || user.defaultUnit;
const count = user.weighIns.push({
weight,
unit,
});
user.save((err) => {
if (err) { return next(err); }
res.json({ weighIn: user.weighIns[count - 1] });
});
};
It doesn't seem possible to specify a reference to a parent document in a Mongoose schema, but I would think that a better bet would be in my pre('validate') middleware for the subdocument. I just can't see a way to reference the parent document in the subdocument middleware either.
NB: This answer does not work as I don't want to override all of the user's WeighIns' units, just when unspecified in the POST request.
Am I stuck doing this in my controller? I started with Rails so I have had 'fat models, skinny controllers' etched on my brain.
You can access the parent (User) from a sub-document (WeighIn) using the this.parent() function.
However, I'm not sure if it's possible to add a static to a sub-document, so that something like this would be possible:
user.weighIns.myCustomMethod(req.body)
Instead, you could create a method on the UserSchema, like addWeightIn:
UserSchema.methods.addWeightIn = function ({ weight, unit }) {
this.weightIns.push({
weight,
unit: unit || this.defaultUnit
})
}
Then just call the user.addWeightIn function within your controller and pass the req.body to it.
This way, you get 'fat models, skinny controllers'.

MERN Stack: Saving Form Data to mLab - Formatting Issue

I am getting the data from my form to my mLab but it is formatted in a way i don't like. it comes though like this
{ '{"email":"ben#benjamin.com","message":"not what i want"}': '' }
and even worse it saves as this
"data": "{\"{\\\"email\\\":\\\"ben#benjamin.com\\\",\\\"message\\\":\\\"this is ridiculous\\\"}\":\"\"}"
so it is setting my data as the key to nothing basically. i resorted to changing my Schema to an Object just so i can save something. ideally i want a key for each piece of data. and i want to be able to access it later. i couldn't get body parser to work so i am using express-formidable as middleware. there are alot of moving parts and if i change things around i crash the server and get error no matter what i do. okay here is some of the code from both sides.
SERVER CODE:
var formSchema = new Schema({
data: Object
})
app.use(formidable());
app.post('/contact', function(req,res,next){
console.log(req.fields)
var item = JSON.stringify(req.fields)
var form = new Form({data: item
}).save(function(err,data){
if(err) throw err
if(data) res.json(data)
})
})
CLIENT SIDE CODE
submitData(e){
e.preventDefault();
let email = this.state.value;
let msg = this.state.value2;
let data = {
email: email,
message: msg
}
fetch('/contact', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: JSON.stringify(data)
})
.then((response) => response.json())
.then((responseData) => {
console.log("Response:",responseData);
this.setState({
value: "",
value2: "",
status: "THANKS FOR CONTACTING ME I'LL BE IN TOUCH SOON"
})
}).catch((error) => {
console.log(error)
})
}
this is driving me insane. ideally i would like to do it right. or somehow access the object and get at the email and message keys and just change my Schema. then i could assign the keys in my new Form(... section
In a bizarre twist of events i solved it sort of. this can't be the best way but it works. i would still like to know where along the line i went wrong. i saw a tutorial where the form data was easily saved...granted in wasn't from a react front end...anyway this is what i did
My Custom Solution
var formSchema = new Schema({
email: String,
message: String
})
app.use(urlencodedParser);
app.post('/contact', function(req,res,next){
var item = Object.keys(req.body)
var o = JSON.parse(item[0])
var e = o.email
var m = o.message
console.log(e)
//var item = JSON.stringify(req.fields)
var form = new Form({email: e,message: m
}).save(function(err,data){
if(err) throw err
if(data) res.json(data)
})
})
i switched back to body-parser from formidable but either way i was getting that weird object
i still don't know why my data object was {'{'key':'value'}':''} i thought my problem was on the server side...but maybe it is something with how i send the POST. someone smarter and more experienced isnt hard to find :)