How to make states from the keys of state object in react? - node.js

So I was trying to implement states in Child component from the Parent component state,as you can see in the code.But it gives me undefined as state value in child componenet.To test you can conosle.log(questions) and you will see undefined.
Is there a mechanism to setState in Parent component in some way such that the subsequent props in child components wil be able to get the state values?
Here is my code:
import React, { useEffect, useState } from "react";
import io from "socket.io-client";
const ENDPOINT = "http://localhost:5000";
let socket = io(ENDPOINT);
export default function Screen() {
const [qValue, setQuestion] = useState({personalInfo:{},questions:[]});
const [aValue, setAnswer] = useState({personalInfo:{},answer:""});
useEffect(() => {
socket.on("screenAns", (input) => {
setAnswer(JSON.parse(input));
});
console.log(aValue);
}, [aValue]);
useEffect(() => {
socket.on("screenQs", (arrayValue) => {
setQuestion(JSON.parse(arrayValue));
});
console.log((qValue));
}, [qValue]);
return (
<div>
<h2>Screen</h2>
<QuestionSingleMode value={qValue} />
</div>
);
}
function QuestionSingleMode(props){
var [questions,setQuestions]=useState(props.value.questions);
var [renderQuestion,setRenderQuestion]=useState("")
var [counter,setCounter]=useState(props.value.questions.length)
useEffect(()=>{
console.log(questions)
setRenderQuestion(questions[0])
},[renderQuestion])
function nextQuestion(){
setQuestions(questions.splice(0,1))
setRenderQuestion(questions[0])
setCounter(counter--)
}
return(
<div>
<h1>{renderQuestion}</h1>
<button onClick={nextQuestion}>{counter ? "next" : "finish"}</button>
</div>
)
}

Actually I solved the issue by changing the renderQuestion to props.questions in the useEffect() array.

Related

How can I rerender an array of components in react?

I'm traing to make an experiment in react and websockets. The web app sends an input from the user to a server and the server responds. For each response from the server the web creates a component to show the response. I don't know how to rerender the array after each response.
import React, { useState, Component } from 'react'
import './HomePage2.css'
import Text from '../../commons/Text/Text'
import Input from '../../commons/Input/Input'
import TextInput from '../../commons/TextInput/TextInput'
const ws = new WebSocket('ws://localhost:5656')
var path = 'path$ '
var items = []
const hc = () =>{
this.forceUpdate()
}
ws.addEventListener("message", data => {
console.log(data.data)
items.push(<TextInput path={path} text={data.data} />)
//items.push(data.data)
if(data.data !== ''){
items.push(<Text text={data.data} />)
}
})
class HomePage2 extends Component {
render(){
function setCommand(comm){
ws.send(comm)
}
return(
<div className='homepage2' >
{items}
<Input path={path} callback={setCommand} />
</div>
)
}
}
export default HomePage2
save received data in an array and use the map method to render an element for each value in the array, e.g:
return (
<>
{items.map((item, key) => (
<TextInput path={path} text={item.data} key={key} />
))}
</>
);

React display data from object TypeError: value map is not a function

Getting "TypeError: value is undefined" error when trying to render the data from the value object, when I console log value the object does show.
CODE
import React from "react";
import { useParams } from "react-router-dom";
import {
useDocumentData,
} from "react-firebase-hooks/firestore";
import firebase from "../firebase";
const firestore = firebase.firestore();
const Threads = () => {
const { threadId } = useParams();
const [value, loading, error] = useDocumentData(firestore.collection("threads").doc(threadId));
console.log(value);
return (
<div>
{value.map((thread, i) => (
<div>
<h1>{thread.title}</h1>
<p>{thread.desc}</p>
</div>
))}
</div>
);
};
export default Threads;
CONSOLE
ERROR
The variable value is a object. Map function in JS is applicable only for arrays.
So try this.
<div>
<h1>{value.title}</h1>
<p>{value.desc}</p>
</div>

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

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.

Resources