How to mapStateToProps only after the component receives data from Firebase - javascript

In the application I'm trying to render data, based on the route parameters.
When I run the code, I receive an error screen because mapStateToProps hasn't received data from Firestore and the .find() function cannot run on 'undefined'.
const mapStateToProps = (state, ownProps) => {
const id = ownProps.match.params.product_id;
return {
product: state.firestore.ordered.products.find(p => p.id === id)
};
};
I've tried using conditional rendering inside the component itself but the error is inside the mapStateToProps(). The data doesn't even get to the component because the script is stopped at the first attempt of mapping the state to its props.
Here is my component without conditional rendering, for reference.
<div className="product-detail-page top-padding container col-lg-10">
<div className="product-desc">
<div className="product-desc-image col-lg-5">
<img
src={this.props.product.image}
alt={this.props.product.name}
className="col-12"
/>
</div>
<div className="product-desc-right col-lg-7">
<h2 className="product-desc-title">{this.props.product.name}</h2>
<div className="product-desc-bottom-section">
<div className="product-desc-text">
<p>
In stock - Ships from Ireland. Sold and Shipped by{" "}
<span className="comp-logo">
<Link to="/">
comp<span className="red-dot">.</span>com
</Link>
</span>
</p>
<ul>
<li>{this.randomDetails(1)}</li>
<li>{this.randomDetails(2)}</li>
<li>{this.randomDetails(3)}</li>
<li>{this.randomDetails(4)}</li>
<li>{this.randomDetails(5)}</li>
<li>{this.randomDetails(6)}</li>
</ul>
<div className="star-rating">
<img src={this.starRating(1)} alt="*" />
<img src={this.starRating(2)} alt="*" />
<img src={this.starRating(3)} alt="*" />
<img src={this.starRating(4)} alt="*" />
<img src={this.starRating(5)} alt="x" />
</div>
</div>
<div className="product-desc-checkout">
<div className="product-desc-total">
<h4>€ {this.preTaxCalculator().toFixed(2)}</h4>
<p>VAT - € {this.taxCalculator().toFixed(2)}</p>
<p>Shipping: € {this.props.product.shipping}</p>
<h3>{this.props.product.price}</h3>
</div>
<div className="product-desc-action-buttons">ADD TO CART</div>
<div className="product-desc-action-buttons">
ADD TO WISHLIST
</div>
<div className="product-desc-action-buttons">
SAVE FOR LATER
</div>
</div>
</div>
</div>
</div>
<div className="product-details">
<h2>Learn more about the {this.props.product.name}</h2>
<table>
<tbody>
{this.renderKeys().map(key => (
<tr key={key}>
<td>{key}</td>
<td>{this.props.product.details[key]}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>

You should use a conditional in your mapStateToProps function. Check whether you have the data or not, and return an empty array, object, or the thing that suits you best, if the data is not available. When the state changes, the function maps again with the real deal.

you could use condition to check for props and render only if available. Note the changes and also make use of timeout to delay the return
const mapStateToProps = (state, ownProps) => {
const id = ownProps.match.params.product_id;
setTimeout(()=>
return {
product: state.firestore.ordered.products.find(p => p.id === id)
}),3000)
};
{this.props.product && <div className="product-detail-page top-padding container col-lg-10">
<div className="product-desc">
<div className="product-desc-image col-lg-5">
<img
src={this.props.product.image}
alt={this.props.product.name}
className="col-12"
/>
</div>
<div className="product-desc-right col-lg-7">
<h2 className="product-desc-title">{this.props.product.name}</h2>
<div className="product-desc-bottom-section">
<div className="product-desc-text">
<p>
In stock - Ships from Ireland. Sold and Shipped by{" "}
<span className="comp-logo">
<Link to="/">
comp<span className="red-dot">.</span>com
</Link>
</span>
</p>
<ul>
<li>{this.randomDetails(1)}</li>
<li>{this.randomDetails(2)}</li>
<li>{this.randomDetails(3)}</li>
<li>{this.randomDetails(4)}</li>
<li>{this.randomDetails(5)}</li>
<li>{this.randomDetails(6)}</li>
</ul>
<div className="star-rating">
<img src={this.starRating(1)} alt="*" />
<img src={this.starRating(2)} alt="*" />
<img src={this.starRating(3)} alt="*" />
<img src={this.starRating(4)} alt="*" />
<img src={this.starRating(5)} alt="x" />
</div>
</div>
<div className="product-desc-checkout">
<div className="product-desc-total">
<h4>€ {this.preTaxCalculator().toFixed(2)}</h4>
<p>VAT - € {this.taxCalculator().toFixed(2)}</p>
<p>Shipping: € {this.props.product.shipping}</p>
<h3>{this.props.product.price}</h3>
</div>
<div className="product-desc-action-buttons">ADD TO CART</div>
<div className="product-desc-action-buttons">
ADD TO WISHLIST
</div>
<div className="product-desc-action-buttons">
SAVE FOR LATER
</div>
</div>
</div>
</div>
</div>
<div className="product-details">
<h2>Learn more about the {this.props.product.name}</h2>
<table>
<tbody>
{this.renderKeys().map(key => (
<tr key={key}>
<td>{key}</td>
<td>{this.props.product.details[key]}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>}

Answer:
const mapStateToProps = (state, ownProps) => {
const id = ownProps.match.params.product_id;
if (state.firestore.ordered.products) {
return {
product: state.firestore.ordered.products.find(p => p.id === id)
};
}
};
I used an if statement in the mapStateToProps.

Related

How do I use Toggle method in Reactjs ? I have added sandbox link at the bottom

In this code, I want to toggle active class to icons div, I watched some youtube videos and they showed this method which is not what I want, this method does not add activeclass to a particular div it adds active class to every div that has icons class. I want whenever someone click onto the div active class should be added into it and when clicked again that class should get removed.
export default function Site() {
const [notActive, setActive] = useState(false);
const switchActive = () => {
notActive ? setActive(false) : setActive(true);
};
return (
<div id="container">
<nav id="navbar">
<a href="j" className="link">
Buzzify
</a>
<a href="f">
<FaRegUserCircle className="user-icon" />
</a>
</nav>
<div id="playlist-container">
<div id="playlist">
<div
className={notActive ? "icons active" : "icons"}
onClick={switchActive}
>
<BsCloudRain />
<VolSlider />
</div>
<div
className={notActive ? "icons active" : "icons"}
onClick={switchActive}
>
<GiForest />
</div>
<div className="icons">
<MdOutlineWaterDrop />
</div>
<div className="icons">
<BiWind />
</div>
<div className="icons">
<GiCampfire />
</div>
<div className="icons">
<GiThreeLeaves />
</div>
<div className="icons">
<BsMoonStars />
</div>
<div className="icons">
<BiWater />
</div>
<div className="icons">
<BiTrain />
</div>
<div className="icons">
<BiCoffeeTogo />
</div>
<div className="icons">
<FaFan />
</div>
<div className="icons">
<DiDigitalOcean />
</div>
<div className="icons">
<GiWaterfall />
</div>
<div className="icons">
<FaPlane />
</div>
<div className="icons">
<IoIosPlanet />
</div>
<div className="icons">
<GiOctopus />
</div>
</div>
</div>
</div>
);
}
CodeSandbox.io
You have two options in order to solve this:
1- You need a state for each icon that keeps track of if you clicked them or not. This could be done by making a React component that represents an icon and then use it to in your Site component:
export default function MyIcon({icon}) {
const [isActive, setIsActive] = useState(false)
return <div className={isActive ? "icons active" : "icons"}
onClick={() => {setIsActive(!isActive)}}>
{icon}
</div>
}
You can then use this component in Site() like this:
<MyIcon icon={<CertainIcon/>} />
2- You can programmatically toggle the classname "active" using the onClick event without relying on a state like this:
<div className="icons"
onClick={(event) => {event.currentTarget.classList.toggle('active')}}>
<CertainIcon/>
</div>
you should manage state per Icon like
const IconWithState = ({children}) => {
const [isActive, setIsactive] = useState(false)
const toggle = () => setIsactive(curr => !curr)
return (
<div
className={active ? "icons active" : "icons"}
onClick={toggle}
>
{children}
</div>
)
and then use it like
...
<div id="playlist-container">
<div id="playlist">
<IconWithState>
<BsCloudRain />
<VolSlider />
</IconWithState>
<IconWithState>
<GiForest />
</IconWithState>
....

React - Prevent child re-rendering in map

I have a re-render issue with a slider which appears in every element from a map.
const Attractions = () => {
const [slide, setSlide] = useState(0)
const handleSlider = (direction) => {
if(direction === "right"){
setSlide(slide + 1)
}else{
setSlide(slide - 1)
}
}
const translation = slide * - 110
return (
<div className="attractions">
<Navbar />
<Header nav={"attractions"}/>
<div className="attractionsContainer">
<div className="attractionsWrapper">
<div className="left">
</div>
<div className="right">
{attractionList.map(attraction => (
<div className="attractionWrapper" key={attraction.id}>
<div className="top">
<div className="topLeft">
<img src="https://holidaystoswitzerland.com/wp-content/uploads/2019/09/Lauterbrunnen.jpg.webp" alt="" className="avatarImg"/>
</div>
<div className="topRight">
<div className="title">{attraction.name}</div>
<div className="location"><LocationOnOutlinedIcon/>{attraction.location.city}, {attraction.location.country}</div>
</div>
<div className="moreInfo">
<MoreVertOutlinedIcon/>
</div>
</div>
<div className="middle">
<div className="sliderWrapper">
{slide > 0 &&
<div className="left" onClick={()=>handleSlider("left")}>
<ArrowBackIosNewOutlinedIcon style={{fontSize: "30px"}}/>
</div>
}
<div className="slider" style={{transform: `translateX(${translation}%)`}}>
{attraction.img.map(img =>(
<img src={img} alt="" />
))}
</div>
{slide < 2 &&
<div className="right" onClick={()=>handleSlider("right")}>
<ArrowForwardIosOutlinedIcon style={{fontSize: "30px"}}/>
</div>
}
</div>
</div>
<div className="bottom">
<div className="interactions">
<FavoriteBorderOutlinedIcon className="actionBtn"/>
<ChatBubbleOutlineOutlinedIcon className="actionBtn"/>
<CheckCircleOutlineRoundedIcon className="actionBtn"/>
<AddCircleOutlineOutlinedIcon className="actionBtn" />
</div>
<div className="description">
{attraction.description}
</div>
<div className="comments">
{attraction.comments.map(comment =>(
<div className="commentItem">
<h4>{comment.user}</h4>
<span>{comment.comment}</span>
</div>
))}
</div>
</div>
</div>
))}
</div>
</div>
</div>
<Footer />
</div>
)
}
When i get more elements from the attractionList array, whenever i click on the left or right arrow to go to the next slide, all of the sliders from the list will do the same. I figured out that i should use something so that the slide is set only based on the item from the map, but honestly i have no idea how.
If you want them to behave differently, then the attraction object should have its own slide variable and setSlide method. That way you can just put:
onclick = {() => attraction.setSlide(attraction.slide + 1)}

How to change color icons in react after onClick

export default function Post({post}) {
const [like,setLike] = useState(post.like)
const [islike,setIslike] = useState(false)
const handler=()=>{
setLike(islike? like-1:like+1 )
setIslike(!islike)
}
return (
<>
<div className="postcontainer">
<div className='postwrapper'>
<div className="postTop">
<div className="topleft">
<img className="images" src= {Users.filter((u)=> u.id === post.userId)[0].imagesProfile} alt="profile" />
<span className="names"> {Users.filter((u)=> u.id === post.userId)[0].userName} </span>
<span className="dates"> {post.date}</span>
</div>
<div className="topzRight">
<MoreVert />
</div>
</div>
<div className="postcenter">
<span className='text'>{post.descrip}</span>
<img className="imagepost" src={post.imagesPost} alt="newpost" />
</div>
<div className="postbotton">
<div className="postbottonLeft">
<LocationOnIcon className="icon" />
<FavoriteIcon className="icon" onClick={handler}/>
<span className='counter' > {like} People liked it</span>
</div>
<div className="postbottonRight">
<span>{post.comment} comments</span>
<SmsOutlined className="iconcomment" />
</div>
</div>
</div>
</div>
</>
)
First, you should try to talk more about what you want to do in the code instead of just putting the question in the title and putting the code as the body.
Second, if you want some HTML element to have a different color based on when you clicked it or not, you could make a CSS class for each state you want, so one class could be .isLiked and the other can be .isNotLiked with each one having a different color. Then in your code, you can have a conditional on the className attribute for the HTML element to decide what it should be based on a conditional.
Eg: className={isLiked ? 'isLiked' : 'isNotLiked'}

Javascript variables in css are not working

In this code i am passing both imageurl and size which are javascript variables but i am using in css.
css is not considering both. How to resolve this issue ??
const MenuItem = ({title, imageUrl, size}) => {
return(
<div className="${size} menu-item">
<div className="background-image" style={{backgroundImage: "url(${imageUrl})"}} />
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
);
}
This should work:
const MenuItem = ({title, imageUrl, size}) => {
return(
<div className={`${size} menu-item`}>
<div className="background-image" style={{backgroundImage: "url(${imageUrl})"}} />
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
);
}
To use variables inside a string like this, you need to use the following syntax:
className={`${size} menu-item`}
You should wrap them in back-ticks (``):
const MenuItem = ({title, imageUrl, size}) => {
return(
<div className=`${size} menu-item`>
<div className="background-image" style={{backgroundImage: `${url(${imageUrl})}`}} />
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
);
}

React & Typescript: Cannot read property '0' of undefined

I recently moved from .jsx to .tsx, and I'm having issues (before moving to typescript, was all working well). The first error is at line: "<Modal show={this.state.isOpen[key]} onClose={this.handleToggleModal.bind(__this,key)}>". I suspect there's something related to typescript, but I don't know what. Anybody can help me?
// TaskCard.tsx
//Dependencies
import * as React from 'react';
//Copmponents
import Modal, {ModalProps} from './Modal';
import Input from './Input';
import {IBoard} from './Board';
import PostComment from './PostComment';
export interface IComments{
body: string,
from: string,
date: string,
hour: string
}
export interface ITask{
board: string,
duedate: string,
tag: string,
tagClass: string,
tagText: string,
body: string,
risk: string,
responsable: string
comments: IComments[]
}
export interface TaskProps{
tasks: ITask[]
boards: IBoard[]
}
export interface TaskState{
isOpen: {}
}
class TaskCard extends React.Component<TaskProps, TaskState>{
constructor(props) {
super(props);
this.state = {
isOpen: props.isOpen
};
}
handleToggleModal(key) {
this.state.isOpen[key] = !this.state.isOpen[key];
this.setState(this.state.isOpen);
}
render(){
const { tasks, boards } = this.props;
let __this = this;
return(
tasks && tasks.map(
(tasks, key) =>
<div key={key} className="v-margin no-margin-top card-contour medium" onClick={this.handleToggleModal.bind(__this,key)}>
<div className="row middle-xs caption">
<div className="col-xs-6 no-padding">
<div className="row middle-xs">
<img className="img_icn no-padding-left" src="./assets/img/icn_calendar.svg" alt=""/>
<p className="txt-tertiary-color">{tasks.duedate}</p>
</div>
</div>
<div className="col-xs-6 no-padding">
<div className="row end-xs middle-xs">
<div className={tasks.tagClass + " tag tag_box center-align"}>
<p>{tasks.tag}</p>
<span className="tag_hint">{tasks.tagText}</span>
</div>
</div>
</div>
</div>
<div className="row middle-row v-padding">
<p>{tasks.body}</p>
</div>
<div className="row middle-xs caption">
<div className="col-xs-9 no-padding">
<div className="row middle-xs">
<img className="img_icn no-padding-left" src="./assets/img/icn_target.svg" alt=""/>
<p className="txt-tertiary-color">Riesgos: {tasks.risk}%</p>
</div>
</div>
<div className="col-xs-3 no-padding">
<div className="row middle-xs end-xs">
<img className="img_icn no-padding-left" src="./assets/img/icn_comments.svg" alt=""/>
<p className="txt-tertiary-color">{tasks.comments.length}</p>
</div>
</div>
</div>
<Modal show={this.state.isOpen[key]} onClose={this.handleToggleModal.bind(__this,key)}>
<div className="modal_container">
<section className="modal_section">
<div className="row middle-xs no-margin-left no-margin-right modal_section_title">
<object width="20px" height="20px" data="./assets/img/icn_objetive-blue.svg"></object>
<div className="col-xs start-xs">
<h4 className="semi-bold">Objetivo</h4>
</div>
</div>
<div className="col-xs-12 modal_section_content">
<p>{tasks.body}</p>
</div>
</section>
<section className="modal_section">
<div className="row middle-xs no-margin-left no-margin-right modal_section_title">
<object width="20px" height="20px" data="./assets/img/icn_target-blue.svg"></object>
<div className="col-xs start-xs">
<h4 className="semi-bold">Target</h4>
</div>
</div>
<div className="col-xs-12 modal_section_content">
<p>Riesgos / Problemas = {tasks.risk}%</p>
<div className="table v-margin">
<div className="row middle-xs">
<div className="col-xs">
<p className="table_head">Fecha de vencimiento</p>
<p className="table_body">{tasks.duedate}</p>
</div>
<div className="col-xs">
<p className="table_head">Tipo de objetivo</p>
<p className="table_body">{tasks.tagText}</p>
</div>
<div className="col-xs">
<p className="table_head">Responsable</p>
<p className="table_body">{tasks.responsable}</p>
</div>
<div className="col-xs-12 v-margin no-margin-bottom">
<p className="table_head">Estado de objetivo</p>
<div className="row middle-xs">
{
boards && boards.map(
(boards, board) =>
<div key={board} className="col-xs-3">
<Input name="state" type="radio" classNameCustom="input_radio"
checked={boards.id == tasks.board} value={boards.id} id={boards.id}/>
<label htmlFor={boards.id}>{boards.name}</label>
</div>
)
}
</div>
</div>
</div>
</div>
</div>
</section>
<section className="modal_section">
<div className="row middle-xs no-margin-left no-margin-right modal_section_title">
<object width="20px" height="20px" data="./assets/img/icn_activity-blue.svg"></object>
<div className="col-xs start-xs">
<h4 className="semi-bold">Actividad</h4>
</div>
</div>
<div className="col-xs-12 modal_section_content">
{
tasks.comments.length!=0 ?(
tasks.comments.map((comments, i) => {
return(
<div key={i} className="comment">
<div className="comment_box">
<p>{comments.body}</p>
</div>
<div className="comment_data row no-margin middle-xs">
<div className="col-xs start-xs">
<p>{comments.from}</p>
</div>
<div className="col-xs end-xs">
<p>{comments.date} a las {comments.hour} hs.</p>
</div>
</div>
</div>
)
})
):null
}
</div>
</section>
</div>
<PostComment />
</Modal>
</div>
)
)
}
}
export default TaskCard;
And here is the Modal component
//Modal.tsx file
//Dependencies
import * as React from 'react';
export interface ModalProps{
onClose: any,
show: boolean,
children: any
}
export default class Modal extends React.Component<ModalProps>{
handleStopPropagation = (e) =>{
e.stopPropagation();
}
render(){
const {onClose, show, children} = this.props;
// Render nothing if the "show" prop is false
if(!this.props.show) {
return null;
}
return (
<div className="modal_bkg" onClick={this.handleStopPropagation}>
<div className="modal_bkg_container row middle-xs center-xs">
<div className="modal card">
<div className="right-align modal_close">
<button className="btn btn_close" onClick={this.props.onClose}>x</button>
</div>
{this.props.children}
</div>
</div>
</div>
);
}
}
I'm unable to run the code at the moment, but as far as I can see the TaskCard.render function is using Array.map function, which is executing a callback function for every task item.
And there is a problem, because in the callback this is not the this you think it is :). According to documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map you can use map overload that accepts this.
I believe that if you do
return(tasks && tasks.map(
(tasks, key) => <div>put your component markup here</div>,
this)
);
inside TaskCard.render function you should see the error no more.
A friend helped me with this and now it's working. The first problem was the state "isOpen" at start always is undefined so, that's why runtime was throwing error "Cannot read property '0' of undefined". So now, "isOpen" is and empty object and the final constructor looks like this:
constructor(props) {
super(props);
this.state = {
isOpen: {}
};
}
After this, another error was "boards.map is not a function" and that was because I was passing the array "boards" as object. Changing for "board" was enought. So the final code was:
boards && boards.map(
(board, key) =>
<div key={key} className="col-xs-3">
<Input name="state" type="radio" classNameCustom="input_radio"
checked={board.id == task.board} value={board.id} id={board.id}/>
<label htmlFor={board.id}>{board.name}</label>
</div>
)
Anyway, thanks for trying to help me. Regards!

Categories

Resources