Is it possible to pass "this.state" object from child to parent component using function parameters? - node.js

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

Related

React, update component after async function set

I want to add data and see in below, and also when I start app, I want see added records. But I can see it, when I'm try to writing something in the fields.
The thing is, the function that updates the static list is asynchronous. This function retrieves data from the database, but before assigning it to a variable, the page has been rendered. There is some way to wait for this variable or update information other way than when you try to type it in the fields. This is before the form is approved.
[![enter image description here][1]][1]
class AddAdvertisment extends React.Component <any, any> {
private advertisment;
constructor(props, state:IAdvertisment){
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
this.state = state;
this.advertisment = new Advertisement(props);
}
onButtonClick(){
this.advertisment.add(this.getAmount(), this.state.name, this.state.description, this.state.date);
this.setState(state => ({ showRecords: true }));
}
updateName(evt){
this.setState(state => ({ name: evt.target.value }));
}
....
render() {
return (<React.Fragment>
<div className={styles.form}>
<section className={styles.section}>
<input id="name" onChange={this.updateName.bind(this)} ></input>
<input id="description" onChange={this.updateDescription.bind(this)} ></input>
<input type="date" id="date" onChange={this.updateDate.bind(this)} ></input>
<button className={styles.action_button} onClick={this.onButtonClick.bind(this)}>Add</button>
</section>
</div>
{<ShowAdvertismentList/>}
</React.Fragment>
);
}
class ShowAdvertismentList extends React.Component <any, any>{
render(){
let listItems;
let array = Advertisement.ad
if(array !== undefined){
listItems = array.map((item) =>
<React.Fragment>
<div className={styles.record}>
<p key={item.id+"a"} >Advertisment name is: {item.name}</p>
<p key={item.id+"b"} >Description: {item.description}</p>
<p key={item.id+"c"} >Date: {item.date}</p>
</div>
</React.Fragment>
);
}
return <div className={styles.adv_show}>{listItems}</div>;
class Advertisement extends React.Component {
public static ad:[IAdvertisment];
constructor(props){
super(props);
if(!Advertisement.ad){
this.select_from_db();
}
}
....
select_from_db = async () => {
const res = await fetch('http://localhost:8000/select');
const odp = await res.json();
if(odp !== "brak danych")
odp.forEach(element => {
if(Advertisement.ad){
Advertisement.ad.push(element);
}
else{
Advertisement.ad = [element];
I try to create function and child like:
function Select_from_db(){
const[items, setItems] = useState();
useEffect(() => {
fetch('http://localhost:8000/select')
.then(res => res.json())
.then(data => setItems(data));
}, []);
return <div className={styles.adv_show}>{items && <Child items={items}/>}
</div>;
}
function Child({items}){
return(
<>
{items.map(item => ( ...
))}
</>
And is working good in first moment, but if I want add item to db I must refresh page to see it on a list below.
I use is instead ShowAdvertismentList in render function. Elements be added to db but not showing below. In next click is this same, until refresh page.
And in my opinio better use a list, becouse I musn't want to conect to database every time to download all records.
[1]: https://i.stack.imgur.com/IYSNU.gif
I now recipe. I must change state on componentDidMount in AddAdvertisment class.
async componentDidMount(){
let z = await setTimeout(() => {
this.setState(state => ({ loaded: true}));
}, 1000);
}
render() {
return (<React.Fragment >
(...)
{this.state.loaded ? <ShowAdvertismentList /> : <Loading/>}
</React.Fragment>
);
}

Why is this function being called with an object if I am passing it just a primitive value?

I am learning react-testing-library.
I have this component called MovieForm, it has an onSubmit handler that is mapped to a function coming from props that are called submit form. The onSubmit is called with a state property called text
This is how the component looks:
export default class MovieForm extends Component {
state = {
text: 'x',
}
render() {
const {submitForm} = this.props
const {text} = this.state
debugger
return (
<div>
<form onSubmit={() => submitForm({text})} data-testid="movie-form">
<input onChange={(e) => this.onChange(e)} type="text" name="" id=""/>
<h1>text: {text}</h1>
<button>Submit</button>
</form>
</div>
)
}
}
Then in my test i am mocking that onSubmit with jest.fn()
But when I test if the submitForm was called with 'x, as that is what the initial state of state.text was, it throws an error and says it was actually called with {"text": "x"}
ERROR:
Expected mock function to have been called with:
["x"]
But it was called with:
[{"text": "x"}]
I don't understand why it says it was called as an object if it was passed just the value of text not the object:
<form onSubmit={() => submitForm({text})} data-testid="movie-form">
Can you explain why this is so?
This is the test:
const onSubmit = jest.fn()
test('<MovieForm />', () => {
const {debug, getByTestId, queryByTestId, container, getByText} = render(<MovieForm submitForm= {onSubmit} />)
fireEvent.click(getByText('Submit'))
expect(onSubmit).toHaveBeenCalledWith('x')
})
Thanks.
You are actually passing an object to onSubmit:
<form onSubmit={() => submitForm({text})} data-testid="movie-form">
{text} is an object.
Instead, change to pass text instead of {text}:
<form onSubmit={() => submitForm(text)} data-testid="movie-form">
Hope this helps.

onChange event is only occurring once when state is managed by redux

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)
);

React update component after loading data

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.

Can't Edit and Update properties with form Reactjs and MongoDB

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"

Resources