React, update component after async function set - node.js

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

Related

TypeError: Cannot read property startsWith of undefined

import React from 'react';
import AttributeDescription from './AttributeDescription';
class CompEntry extends React.Component{
render(){
let description;
if(this.props.description.startsWith("_")){
description= this.props.description.slice(1, this.props.description.length);
}
if(this.props.description.startsWith("__")){
description = this.props.description.slice(2, this.props.description.length);
}
return(
<div>
<div>
<AttributeDescription description={description}/>
</div>
</div>
);
};
}
export default CompEntry;
The mentioned error happened if I do the stuffs before the return. However, if i dont do anything before the return and just pass this props.description into the description prop of the <AttributeDescription/> tag, everything works fine, a defined props is passed into the tag. It seems like if the value of this.props.description does not exist if i try to access its property. Anyone knows why?
This is how I use the CompEntry component above:
import React from 'react';
import CompEntry from './CompEntry';
import CompHeading from './CompHeading';
class CompTable extends React.Component{
constructor(props){
super(props);
this.state = {
products: [],
attributes: [],
attDesc: [],
};
this.getEntries = this.getEntries.bind(this);
}
getEntries = async () => {
const response = await fetch('/api/hello/data');
const body = response.json();
return body;
};
componentDidMount(){
this.getEntries()
.then((resolve) => this.setState({
products: resolve.products,
attributes: resolve.attributes,
attDesc: resolve.attributesDescription}))
.catch(err=>console.log(err));
};
render(){
console.log(this.state.products);
let highlightEntry= true;
let compEntries = this.state.attributes.map( (item, index) =>{
highlightEntry = !highlightEntry;
return(
<CompEntry key={index} attribute={item} description={this.state.attDesc[index]} comparees={this.state.products} color={highlightEntry}/>
);
});
return(
<div id = "comp-table">
<div id="comp-sub-container">
<CompHeading comparees={this.state.products}/>
{compEntries}
</div>
</div>
);
}
}
export default CompTable;
Edit: As mentioned by #awarrier99 in the comments, the response.json() function returns a Promise so you need to handle that appropriately. The code below has been updated for that also.
You do not set description to anything if the leading character is not an underscore. Also if it starts with two underscores, it also starts with one underscore so that can double the work. I recommend doing this:
render(){
let description = this.props.description;
if (description.startsWith("__")) {
description = description.slice(2, description.length);
} else if (description.startsWith("_")) {
description= description.slice(1, description.length);
}
return(
<div>
<div>
<AttributeDescription description={description}/>
</div>
</div>
);
};
}
This way if this.props.description does not start with any underscores it will still send that value, and the slice only gets done once if there are underscores. The code also gets easier to read by using the simpler description variable instead of this.props.description being repeated throughout.
Update your getEntries function to return the Promise given by the json() function. You could also await on it, but since getEntries is async it's already returning a Promise so this is simplest.
getEntries = async () => {
const response = await fetch('/api/hello/data');
return response.json(); // return the Promise
};

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

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

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.

actions/reducers are not causing a rerender as expected

I am building a web client (react,redux) & API (mongo, express, node) that will show a list of deals to a user and allow them to "favorite/like" them. I am new to react/redux, as you will be able to tell. I am using axios to make my requests and have successfully rendered a list of deals. I have a "favorite" button that successfully makes the post request, and the request just sends back the deal that was favorited.. However, the "number of likes" is not updating and does not show the increased number until I manually refresh the page.
Here is my component that successfully produces a list of deals (2)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchDeals, favoriteDeal } from '../actions';
import DealCard from './DealCard';
class DealList extends Component {
componentDidMount(){
this.props.fetchDeals();
this.favoriteDeal = this.favoriteDeal.bind(this);
}
favoriteDeal = (dealId) => {
this.props.favoriteDeal(dealId)
}
renderDeals(){
return this.props.deals.map(deal => {
return(
<DealCard
onFavorite = {this.favoriteDeal}
key={deal._id}
{...deal}
/>
)
});
}
render(){
return(
<div>
{this.renderDeals()}
</div>
);
}
}
function mapStateToProps(state){
return {
deals: state.deals,
favoriteDeal: state.favoritedDeal
}
}
export default connect(mapStateToProps, {fetchDeals, favoriteDeal})(DealList)
Below is my individual deal card:
import React, { Component } from 'react';
class DealCard extends Component {
render() {
return (
<div key={this.props._id} className="card" style={{width: "18rem", marginTop: 10}}>
<img className="card-img-top" src={this.props.dealImage} style={{maxHeight: 200}} alt="${this.props.dealHeadline}" />
<div className="card-body">
<h4>{this.props.dealHeadline}</h4>
<p className="card-text">{this.props.dealDescription}</p>
<div>
<button onClick={() => this.props.onFavorite(this.props._id)}>Favorite</button>
<span>{this.props.dealId}</span>
<i className="fa fa-heart" aria-hidden="true"></i>
<p className="card-text">#of Likes: {this.props.dealNumberOfLikes}</p>
</div>
</div>
</div>
);
}
}
export default DealCard;
Below are my action creators:
export const fetchDeals = () => async dispatch => {
const res = await axios.get('/api/deals')
dispatch({type: FETCH_DEALS, payload: res.data})
};
export const favoriteDeal = (dealId) => async dispatch => {
const res = await axios.post(`/api/deals/${dealId}/favorites`)
dispatch({type: FAVORITE_DEAL, payload: res.data})
};
and finally my reducers:
// deals reducer
import { FETCH_DEALS } from '../actions/types';
export default function (state = [], action){
switch(action.type){
case FETCH_DEALS:
return action.payload;
default:
return state;
}
};
// favorite deals Reducer
import { FAVORITE_DEAL } from '../actions/types';
export default function (state = {}, action){
switch(action.type){
case FAVORITE_DEAL:
return action.payload;
default:
return state;
}
};
To summarize: I have a list of deals, and each deal has a button that when clicked, "favorites" a deal via an HTTP post request and increases the NumberOfDealLikes by 1. When the button is clicked, the request is successfully executed and the database shows that the NumberOfDealLikes is increased by one. However, on the screen, the update is not shown until I manually rerender. As twitter works, I would like to show that the increase happens simultaneously.
Thank you all for your help!
I think the problems lies in your favorite_deal reducer. As you said, the post request sends back the updated deal. It should then replace the old one in the deals array. Your deals reducer should look like:
import { FETCH_DEALS, FAVORITE_DEAL } from '../actions/types';
export default function (state = [], action){
switch(action.type){
case FETCH_DEALS:
return action.payload;
case FAVORITE_DEAL:
return state.map((d) => d._id === action.payload._id ? action.payload : d);
default:
return state;
}
};
As the deals array is updated, your component will be re-rendered. And you do not need another reducer.
By the way, as you defined the favoriteDeal function as a class property with an arrow function, you do not need to bind it to this.

Enabling submit button button when all inputs is filled?

Entire examples doesn't show simple solution how to keep submit button disabled until all fields is filled up in redux-form.
I tried to use this approach (TypeScript):
import * as React from 'react';
import * as reduxForm from 'redux-form';
export interface Props extends reduxForm.InjectedFormProps {}
let passedUsername = false;
let passedPassword = false;
const required = (value: string, callback: (passed: boolean) => void) => {
console.info(`PERFORM REQUIRED FIELD CHECK FOR ${value}`);
if (value && value.trim().length > 0) {
callback(true);
} else {
callback(false);
}
};
const usernameRequired = (value: string) => {
required(value, passed => { passedUsername = passed; });
};
const passwordRequired = (value: string) => {
required(value, passed => { passedPassword = passed; });
};
const isPassed = () => {
console.info(`USER PASSED: ${passedUsername}`);
console.info(`PASSWORD PASSED: ${passedPassword}`);
const result = passedUsername && passedPassword;
console.info(`PASSED: ${result}`);
return result;
};
const LoginForm = ({handleSubmit, pristine, submitting}: Props) => (
<form onSubmit={handleSubmit}>
<div>
<label>Username </label>
<reduxForm.Field
name="username"
component="input"
type="text"
validate={[usernameRequired]}
placeholder="Username"
/>
<br/>
<label>Password </label>
<reduxForm.Field
name="password"
component="input"
type="password"
validate={[passwordRequired]}
placeholder="Password"
/>
</div>
<br/>
<div>
<button type="submit" disabled={!isPassed()}>
<i className="fa fa-spinner fa-spin" style={{visibility: (submitting) ? 'visible' : 'hidden'}}/>
<strong>Login</strong>
</button>
</div>
</form>
);
export default reduxForm.reduxForm({
form: 'login'
})(LoginForm);
But this code above doesn't seems to be working. The form doesn't want to re-render even if I force it through subscribe event. It only re-render when pristine or submitting event is triggered. But if I want to re-render myself the form just ignore it. Maybe some flag I missed to re-render manually the form when I need to?
Ok, finally the solution has been found: just need to modify parameter pure in reduxForm constructor from true (default) to false
export default reduxForm.reduxForm({
form: 'login',
pure: false
})(LoginForm);
And the from will re-render whenever you need.

Resources