So I'm using Nodejs, MongoDB and Reactjs
and I'm trying to Edit properties of projects.
I have multiple projects and when I want to edit properties of one I can't do it. We can access to properties inside inputs, we can see Title and Type but can't even delete, write, he access to properties by its ID but then I can't change it, I guess I have multiple problems here than.
I'll write here my server code, and my Edit/Update project page and a gif with an example when I say that I can't even change anything on inputs.
My server code:
//Render Edit Project Page byId
app.get('/dashboard/project/:id/edit', function(req, res){
let id = req.params.id;
Project.findById(id).exec((err, project) => {
if (err) {
console.log(err);
}
res.json(project);
});
}
//Update Projects Properties byId
app.put('/dashboard/project/:id/edit', function(req, res){
var id = req.params.id;
var project = {
title: req.body.title,
typeOfProduction: req.body.typeOfProduction
};
Project.findByIdAndUpdate(id, project, {new: true},
function(err){
if(err){
console.log(err);
}
res.json(project);
})
};
My React Component Edit Project Page
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import './EditProject.css';
class EditProject extends Component {
constructor(props){
super(props);
this.state = {
//project: {}
title: '',
typeOfProduction: ''
};
}
inputChangedHandler = (event) => {
const updatedProject = event.target.value;
}
componentDidMount() {
// console.log("PROPS " + JSON.stringify(this.props));
const { match: { params } } = this.props;
fetch(`/dashboard/project/${params.id}/edit`)
.then(response => { return response.json()
}).then(project => {
console.log(JSON.stringify(project));
this.setState({
//project: project
title: project.title,
typeOfProduction: project.typeOfProduction
})
})
}
render() {
return (
<div className="EditProject"> EDIT
<form method="POST" action="/dashboard/project/${params.id}/edit?_method=PUT">
<div className="form-group container">
<label className="form--title">Title</label>
<input type="text" className="form-control " value={this.state.title} name="title" ref="title" onChange={(event)=>this.inputChangedHandler(event)}/>
</div>
<div className="form-group container">
<label className="form--title">Type of Production</label>
<input type="text" className="form-control " value={this.state.typeOfProduction} name="typeOfProduction" ref="typeOfProduction" onChange={(event)=>this.inputChangedHandler(event)}/>
</div>
<div className="form-group container button">
<button type="submit" className="btn btn-default" value="Submit" onClcik={() => onsubmit(form)}>Update</button>
</div>
</form>
</div>
);
}
}
export default EditProject;
Erros that I have:
1- DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead.
2- Inputs can't change
3- When click "Update" button:
I think your update override the entire object because you forgot the $set operator. This is the operator to change only the atributtes of an object and not the entire object replacing!
Example:
Model.update(query, { $set: { name: 'jason bourne' }}, options, callback)
First of all, concerning the deprecation warning, you need to change the method findAndModify (As I do not see it here, I guess you're using it elsewhere, or maybe one of the methods you use is calling it) by one of the suggested methods and change your code accordingly.
Then, you need to learn about React and controlled components : https://reactjs.org/docs/forms.html
You need to set the component's state in your onChange handler, such as :
this.setState({
title: event.target.value // or typeOfProduction, depending on wich element fired the event
});
This is called a controlled component in React.
Concerning the response body you get when clicking on Update button, this is actually what you asked for :
res.json(project);
returns the project variable as a JSON file, which is displayed on your screenshot.
See this question for more information about it : Proper way to return JSON using node or Express
Try replace "value" in input tag with "placeholder"
Related
I'm try to create an app that take a image using drag and drop method, and immediately do the action specified in the form that containing it.
index.ejs
<form class="form"
id="form"
method="POST"
action="/images/upload" <-- Llamar a esta acción
enctype="multipart/form-data"
#dragover.prevent
v-cloak #drop.prevent="addFile"
>
</form>
I tried this way, the result is capture the object but I don't know how to send the specified action.
index.ejs
var app = new Vue({
el: '#form',
methods:{
addFile(e) {
file = e.dataTransfer.files[0]
console.log(file)
/// Llamar a action
},
})
Finally, it is the rout that is managed the action form.
router.post('/images/upload', (req, res) => {
uploadImage(req, res, (err) => {
if (err) {
err.message = 'The file is so heavy for my service';
return res.send(err);
}
console.log(req.file);
res.send('uploaded');
});
});
Thanks for your assist.
You can use #submit.prevent for calling a method for your action.
Now for your question part:
As soon as you successfully drag and drop the image, either you can click the submit button with the help of jQuery or refs dynamically and call the action method or you can direct call the method of your action.
<template>
<div>
<form class="form" id="form" method="POST" #submit.prevent="YOUR_METHOD_GOES_HERE" enctype="multipart/form-data" #dragover.prevent v-cloak #drop.prevent="addFile">
</form>
</div>
</template>
<<script>
export default {
methods: {
YOUR_METHOD_GOES_HERE(){
// place your action logic here
}
},
}
</script>
I've seen solutions in this post and in others, but I did follow the instructions and I'm still getting the error. I understand all the logic, id's can't be replaced, they are immutable, but in my mean stack app, the error persists. The code:
Node route
router.put("/:id" ,(req, res, next) => {
const note = new Note({
_id:req.body.id,
project: req.body.project,
note: req.body.note,
date:req.body.date,
});
Note.updateOne({ _id: req.params.id }, note).then(result => {
console.log(result)
res.status(200).json({ message: "Update successful!" });
});
});
Front End edit component:
ngOnInit(): void {
this.notesForm=this.fb.group({
note:['', Validators.required],
project:['', Validators.required],
date:[null,Validators.required]
})
this.route.paramMap.subscribe((paraMap:ParamMap)=>{
if(paraMap.has('noteId')){
this.mode='edit';
this.noteId= paraMap.get('noteId');
this.note=this.notePadService.getNote(this.noteId)
.subscribe(noteData=>{
this.note = {id: noteData._id, note: noteData.note, date: noteData.date, project: noteData.project};
})
}else{
this.mode='create';
this.noteId=null;
}
})
this.getProjects();
}
onSubmit(){
if(this.mode==='create'){
this.notePadService.submitNotes(this.notesForm.value)
console.log(this.notesForm.value);
this.router.navigate(['/notes-listing'])
}else{
this.notePadService.updateNote(
this.noteId,this.notesForm.value
)
}
}
Service:
getNote(id:string){
return this.http.get<any>('http://localhost:3000/api/notes/'+id)
}
updateNote(id:string,note:Notes){
this.http.put('http://localhost:3000/api/notes/'+id,note)
.subscribe(response=>console.log(response))
}
Also I cant pre-populate the reactive form with the values to edit:
<label>Select a Project</label>
<select (change)="test($event.target.value)" formControlName="project"
class="form-control">
<option
*ngFor="let p of projects"
[value]="p.name">{{ p.name }}</option>
</select>
<!------->
<label for="notes">Note</label>
<textarea class="form-control" formControlName="note" type="text" rows="4"></textarea>
<div class="input-group">
<mat-form-field appearance="fill">
<mat-label>Choose a date</mat-label>
<input formControlName="date" matInput [matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<button mat-raised-button color="warn">Submit</button>
</form>
When I click the edit button (in another component), I get the correct URL but no values, and onsubmit, the backend crashes:
http://localhost:4200/edit/601ec21882fa6af20b454a8d
Can someone help me out? I'm very confused and I cant understand the error since I've done everything the other posts suggest...
when you call updateOne you already pass as the first argument the id that should be updated. As second argument you pass note that should contain only the properties that will be updated, but not _id itself:
const note = {
project: req.body.project,
note: req.body.note,
date:req.body.date,
};
I am quite new to react and working on sending data from child component to parent component by passing function in parent component as a props to child. When I pass string as a function parameter it works fine, but when I pass this.state as a function parameter to parent component it logs empty object.
Here is the code for App.js (Parent) Component
class App extends Component {
onClick = (vals) =>{
console.log(`${"App Components"} ${vals}`)
}
render() {
return (
<div className="App">
<Form fetchValue={(vals) => this.onClick(vals)}>
</Form>
</div>
);
}
}
and here is the function I'm accessing in Form.js (Child) Component as a props whenever "onClickButton" is called
import React, { Component } from 'react'
class Form extends Component {
constructor(props) {
super(props)
this.state = {
user : "Enter User Name",
password: "Enter Your Password",
email: "enter your e-mail"
}
}
onUserValueChnage = (e) => {
this.setState({
user: e.target.value
})
}
onPassValueChnage = (e) => {
this.setState({
password: e.target.value
})
}
onEValueChnage = (e) => {
this.setState({
email: e.target.value
})
}
onClickButton = (e) => {
console.log(this.state)
this.props.on(this.state)
e.preventDefault()
}
render() {
return (
<div>
<form onSubmit={this.onClickButton}>
<input type="text" value={this.state.user} onChange= {this.onUserValueChnage} />
<input type="password" value={this.state.password} onChange={this.onPassValueChnage}/>
<input type="text" value={this.state.email} onChange={this.onEValueChnage}/>
<button>Submit</button>
</form>
</div>
)
}
}
export default Form
I've passed this.state as a parameter to App Component and it logs empty object.
The question I'm trying to ask is, is it possible to pass object as function parameter to parent component?
The issue you're having appears to be as a result of the onClickButton in the child component. You're calling this.props.on. However, the function you're passing in from the parent component's render function is called fetchValue. Instead, in onClickButton in your child component, you should call this.props.fetchValue. I also am performing a spread operation when passing state. It is bad to open up the potential to mutate state directly.
onClickButton = (e) => {
console.log(this.state)
this.props.fetchValue({ ...this.state})
e.preventDefault()
}
Optionally, you can also change your parent component to refer directly to the reference of the function, rather than an anonymous function.
<Form fetchValue={this.onClick} />
Updated Answer:
You'll need to change it to this if you want to see your value. When you do string interpolation and try to print an object, it will output [object, object] because it isn't a string. You'd need to wrap your variable with JSON.stringify(value) if you want it to print the object as a string when doing string interpolation. However, you could also update onClick in your App Component to be something like this, and it should print the object like you're expecting.
onClick = (value) =>{
console.log(value)
}
In your parent:
const onChildClicked = name => console.log(name)
return <Child onClickChild={onChildClicked} />
In Child:
return <div id='hey' onClick={e => props.onChildClicked(e.target)}
Your console should show : hey
I am fetching an array with single object from redux store.
this.props.license :[0: {id: 24, domain: "xyz.com", app_url: "https...", purchase_code: "395"}]
And then creating a form to update the value in the react form.
But when trying to change the value the onChange event is only occurring once.
I am managing a new state in the react component to save the changes that I am doing on onChange event.
Is this a correct way in which I am coding?
import React ,{Component} from 'react';
import {connect} from 'react-redux';
import * as actionCreators from '../../store/actions/index';
import Spinner from '../../components/Spinner/Spinner';
const DATABASE_LABELS={
id:'ID',
domain:'Domain',
app_url:'APP URL',
purchase_code:'Purchase Code',
}
class editLicense extends Component {
constructor(props){
super(props);
this.state={
editLicense:{}
}
}
onChangeHandler=(event, type)=>{
// [event.target.name]=[event.target.value]
let newLicense={...this.state.editLicense}
newLicense[type]=event.target.value
console.log(newLicense)
console.log('before',this.state.editLicense)
this.setState({
editLicense :{
...this.state.editLicense,
[event.target.name]:event.target.value
}
})
console.log(this.state.editLicense)
}
componentDidMount=()=>{
this.props.viewLicenceDetails(this.props.token, this.props.match.params.id)
this.setState({
editLicense:this.props.licenses[0]
})
console.log(this.state.editLicense)
}
render(){
let formdata=<Spinner/>;
if(!this.props.loading){
let license=Object.keys(this.props.licenses[0])
.map(key=>{
return [
key,
this.props.licenses[0][key]
]
})
let form=license.map((p, index)=>{
return(
<div className="form-group" key={p[0]}>
<label htmlFor={p[0]}> {DATABASE_LABELS[p[0]]} </label>
<input type="text" className="form-control"
id={p[0]}
value={p[1]}
name={p[0]}
onChange={(event) => this.onChangeHandler(event, p[0])} />
</div>)
})
formdata=(
<form>
{form}
<button type="submit" className="btn btn-primary">Submit</button>
</form>
)
}
return(
<div className="container">
{formdata}
</div>
)}
}
const mapStateToProps = (state)=>{
return({
token:state.auth.idToken,
licenses:state.license.licenses,
loading:state.license.loading,
err:state.license.error
})
}
const mapDispatchToProps = dispatch=>{
return({
updateLicenseData:(token, type, newVal)=>dispatch(actionCreators.updateLicense(token, type, newVal)),
viewLicenceDetails:(token, id)=>dispatch(actionCreators.fetchOneLicense(token, id))
})
}
export default connect(mapStateToProps, mapDispatchToProps)(editLicense);
The question title is a little misleading. Currently, your state is not fully managed by Redux, but only initially fetched from the Redux state.
You are currently:
Fetching the Redux state (via props), and copying it to your component state in componentDidMount.
Populating your input's value from the props (from the Redux state).
Updating your local component state via onChange -> onChangeHandler.
(2) is your problem currently. Your props are not currently being changed (since you're not calling any Redux actions), so the value of your input element is never changing. Its a little unintuitive, but this results in changes only being detected once.
You need to populate your input value prop using your state. In your render() function, try replacing instances of this.props.licenses[0] with this.state.editLicense:
if(!this.props.loading){
let license=Object.keys(this.state.editLicense)
.map(key=>{
return [
key,
this.state.editLicense[key]
]
})
...
}
This way, when your state is updated, the 'current' value of the form will be updated on re-render, and the input component will be fully controlled.
As a side note:
this.setState({
editLicense:this.props.licenses[0]
})
console.log(this.state.editLicense)
This is not guaranteed to work. setState should typically be considered asynchronous. If you want to do something in response to an updated state outside of the render cycle, you should provide a callback to setState:
this.setState({
editLicense:this.props.licenses[0]
},
() => console.log(this.state.editLicense)
);
So I have a component that shows categories from firestore, the component shows nothing the first time but when I click navbar button again it does show the data stored in firestore.
Here is the component file :
import * as React from "react";
import Category from "./Category";
import connect from "react-redux/es/connect/connect";
import {getCategories} from "../reducers/actions/categoryAction";
class CategoriesList extends React.Component{
constructor(props) {
super(props);
this.state = ({
categoriesList: [{}]
})
}
componentWillMount() {
this.props.getCategories();
this.setState({categoriesList: this.props.categories});
this.forceUpdate();
}
render() {
return (
<div className={'container categories'}>
<div className={'row center'} onClick={() => this.props.history.push('/addcategories')}>
<div className={'col s24 m12'}>
<p>Create New Category</p>
</div>
</div>
<div className={'row'}>
<div className={'col s24 m12'}>
{/*{() => this.renderCategories()}*/}
{this.state.categoriesList && this.state.categoriesList.map(category => {
return <Category category={category} key={category.id}/>
})}
</div>
</div>
</div>
);
}
}
const mapDisptachToProps = (dispatch) => {
return {
getCategories: () => dispatch(getCategories()),
}
};
const mapStateToProps = (state) => {
return {
categories: state.category.categories
}
};
export default connect(mapStateToProps, mapDisptachToProps)(CategoriesList)
And here is the reducer file:
import db from '../firebaseConfig'
const initState = {
categories: []
};
const categoryReducer = (state=initState, action) => {
switch (action.type) {
case 'CREATE_CATEGORY':
db.collection("Categories").add({
category: action.category.name
})
.then(function(docRef) {
db.collection("Categories").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// console.log(`${doc.id} => ${doc.data().category}`);
if(doc.id === docRef.id) {
state.categories.push({id: doc.id, name: doc.data().category});
console.log(state.categories)
}
});
});
})
.catch(function(error) {
console.error("Error adding document: ", error);
});
break;
case 'GET_CATEGORIES':
console.log('Getting data from firestore');
db.collection("Categories").get().then((querySnapshot) => {
if(state.categories.length !== querySnapshot.size) {
querySnapshot.forEach((doc) => {
state.categories.push({id: doc.id, name: doc.data().category});
});
}
});
break;
}
return state;
};
export default categoryReducer
Is there any way to update the component after fully loading the data? or a way to load all the data in the initalState?
There are few things one needs to understand. First, this.props.getCategories() performs an action that is asynchronous in nature and hence in the very next line this.setState({categoriesList: this.props.categories});, we wont get the required data.
Second, Storing props to state without any modification is un-necessary and leads to complications. So try to use the props directly without storing it. In case you are modifying the obtained props, make sure you override getDerivedStateFromProps apropiately.
Third, Try to use componentDidMount to perform such async operations than componentWillMount. Refer when to use componentWillMount instead of componentDidMount.
Fourth(important in your case), Reducer should not contain async operations. Reducer should be a synchronous operation which will return a new state. In your case, you need to fetch the data elsewhere and then dispatch within your db.collection(..).then callback. You can also use redux-thunk, if you are using too many async operations to get your redux updated.
So #Mis94 answer should work if you follow the fourth point of returning the new state in the reducer rather than mutating the redux directly in the db().then callback
First, you don't need to store the component's props in the state object. This is actually considered an anti-pattern in react. Instead of doing this, just use your props directly in your render method:
render() {
return (
<div className={'container categories'}>
<div className={'row center'} onClick={() => this.props.history.push('/addcategories')}>
<div className={'col s24 m12'}>
<p>Create New Category</p>
</div>
</div>
<div className={'row'}>
<div className={'col s24 m12'}>
{/*{() => this.renderCategories()}*/}
{this.props.categories && this.props.categories.map(category => {
return <Category category={category} key={category.id}/>
})}
</div>
</div>
</div>
);
}
Hence in your componentWillMount you only need to initiate your request:
componentWillMount() {
this.props.getCategories();
}
You can also do it in componentDidMount() lifecycle method.
Now when your request resolves and your categories update in the store (Redux) they will be passed again to your component causing it to update. This will also happen with every update in the categories stored in the store.
Also you don't have to call forceUpdate like this unless you have components implementing shouldComponentUpdate lifecycle method and you want them to ignore it and do a force update. You can Read about all these lifecycle methods (and you have to if you are using React) here.