React is only passing the last object in my array to a component - javascript

I have a react component where I map through a list of clients and display each client in a card.
return (
<div className='VolunteerClientsTab'>
{volunteerClients && volunteerClients.map((client) => (
<React.Fragment key={client.id}>
<div className='VolunteerClientsTab__card'>
<Avatar style={{ alignSelf: 'center', marginTop: '.5rem' }}>{client.first_name[0]}{client.last_name[0]}</Avatar>
<h2>{client.first_name} {client.last_name}</h2>
<h4>Details</h4>
<p><AiOutlineMail style={iconStyles} /> {client.email}</p>
<p><AiOutlinePhone style={iconStyles} /> {formatPhoneNumber(client.contact_number)}</p>
<h4 style={{ marginTop: '1rem' }}>Actions</h4>
<p onClick={handleOpenClientNeedsModal} className='hover-underline'><BiDonateHeart style={iconStyles} />View Needs</p>
<p className='hover-underline'><HiOutlineDocumentReport style={iconStyles} />Write Report</p>
<p className='hover-underline'><FiVideo style={iconStyles} />Contact Client</p>
</div>
<ClientNeeds open={openClientNeedsModal} handleClose={handleCloseClientNeedsModal} client={client} />
</React.Fragment>
))}
</div>
)
};
ClientNeeds is a component that renders an MUI modal to display additional client information. I am passing it the client object within the loop but when I open the modal only the client of the last index in the volunteerClients array was passed to all the modal components. Does anyone have any idea why this is happening?
ClientNeeds component
import React from 'react';
import Box from '#mui/material/Box';
import Modal from '#mui/material/Modal';
import PropTypes from 'prop-types';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
const ClientNeeds = ({ open, handleClose, client }) => {
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<h2>{client.email}</h2>
</Box>
</Modal>
</div>
)
};
ClientNeeds.propTypes = {
open: PropTypes.bool,
handleClose: PropTypes.func,
client: PropTypes.object
};
export default ClientNeeds;
MY SOLUTION
Passing the client onClick to an additional global state object and passing that state object to the modal,
const [selectedClient, setSelectedClient] = useState(null)
also only rendering the clientNeedsModal is there is an object in that state.
<div className='VolunteerClientsTab'>
{volunteerClients && volunteerClients.map((client) => (
<React.Fragment key={client.id}>
<div className='VolunteerClientsTab__card'>
<Avatar style={{ alignSelf: 'center', marginTop: '.5rem' }}>{client.first_name[0]}{client.last_name[0]}</Avatar>
<h2>{client.first_name} {client.last_name}</h2>
<h4>Details</h4>
<p><AiOutlineMail style={iconStyles} /> {client.email}</p>
<p><AiOutlinePhone style={iconStyles} /> {formatPhoneNumber(client.contact_number)}</p>
<h4 style={{ marginTop: '1rem' }}>Actions</h4>
<p onClick={() => handleOpenNeeds(client)} className='hover-underline'><BiDonateHeart style={iconStyles} />View Needs</p>
<p className='hover-underline'><HiOutlineDocumentReport style={iconStyles} />Write Report</p>
<p className='hover-underline'><FiVideo style={iconStyles} />Schedule Meeting</p>
</div>
{selectedClient ? (
<ClientNeeds open={openClientNeedsModal} handleClose={handleCloseClientNeedsModal} client={selectedClient} />
) : null}
</React.Fragment>
))}
</div>
const handleCloseClientNeedsModal = () => {
setSelectedClient(null)
setOpenClientNeedsModal(false);
}
const handleOpenNeeds = (client) => {
setSelectedClient(client)
handleOpenClientNeedsModal()
}
This allows me to pass any individual object within my array to the modal component as originally desired

Related

Warning: React does not recognize the `PaperComponent` prop on a DOM element

I have simple modal using material ui 'Modal', i want to make it draggable, so user could move modal whereever they want simply by dragging, so i used 'Draggable' from react draggable, but i'm getting this error: Warning: React does not recognize the PaperComponent prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase papercomponent instead. If you accidentally passed it from a parent component, remove it from the DOM element.
what am i doing wrong ?
codesandbox:
https://codesandbox.io/s/draggabledialog-material-demo-forked-srw9yn?file=/demo.js
code:
import * as React from "react";
import Button from "#mui/material/Button";
import { Modal, Box } from "#material-ui/core";
import { Close } from "#material-ui/icons";
import Paper from "#mui/material/Paper";
import Draggable from "react-draggable";
export default function DraggableDialog() {
const [open, setOpen] = React.useState(false);
function PaperComponent(props) {
return (
<Draggable
handle="#draggable-dialog-title"
cancel={'[class*="MuiDialogContent-root"]'}
>
<Paper {...props} />
</Draggable>
);
}
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button variant="outlined" onClick={handleClickOpen}>
Open draggable dialog
</Button>
<Modal
open={open}
onClose={handleClose}
PaperComponent={PaperComponent}
// aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
aria-labelledby="draggable-dialog-title"
>
<div>
<form
style={{
width: "360px",
color: "white",
display: "flex",
flexDirection: "column",
backgroundColor: "#1E2328"
}}
>
<div
style={{
backgroundColor: "black",
margin: "0",
display: "flex",
alignItems: "center",
height: "56px",
width: "360px",
color: "white",
justifyContent: "space-between"
}}
m={2}
>
<Box
style={{
color: "#E9ECEC",
fontSize: "21px"
}}
>
Countries{" "}
</Box>
<button
style={{ color: "white" }}
onClick={handleClose}
aria-label="close-settings-popup"
>
<Close />
</button>
</div>
<div style={{ height: "100%" }}>
<div>
Country Name
<p>Germany</p>
<p>France</p>
</div>
</div>
</form>
</div>
</Modal>
</div>
);
}
Modal component does not recognize PaperComponent that you passing to it as prop,
if you only want modal to be draggable modify your code like so:
import * as React from "react";
import Button from "#mui/material/Button";
import { Modal, Box } from "#material-ui/core";
import { Close } from "#material-ui/icons";
import Paper from "#mui/material/Paper";
import Draggable from "react-draggable";
export default function DraggableDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button variant="outlined" onClick={handleClickOpen}>
Open draggable dialog
</Button>
<Modal
open={open}
onClose={handleClose}
// aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
aria-labelledby="draggable-dialog-title"
>
<Draggable>
<div>
<form
style={{
width: "360px",
color: "white",
display: "flex",
flexDirection: "column",
backgroundColor: "#1E2328"
}}
>
<div
style={{
backgroundColor: "black",
margin: "0",
display: "flex",
alignItems: "center",
height: "56px",
width: "360px",
color: "white",
justifyContent: "space-between"
}}
m={2}
>
<Box
style={{
color: "#E9ECEC",
fontSize: "21px"
}}
>
Countries{" "}
</Box>
<button
style={{ color: "white" }}
onClick={handleClose}
aria-label="close-settings-popup"
>
<Close />
</button>
</div>
<div style={{ height: "100%" }}>
<div>
Country Name
<p>Germany</p>
<p>France</p>
</div>
</div>
</form>
</div>
</Draggable>
</Modal>
</div>
);
}
https://codesandbox.io/s/draggabledialog-material-demo-forked-sx2npc?file=/demo.js

Why doesn't my image show on my react app page?

So i've been trying to show an image on my page with an API, but everytime I go to the page I see the image for a few seconds and after that the page refreshes and it shows an error:
Uncaught TypeError: location is undefined
I think the problem could be about the way I used my image, but i am not sure. If I console log the image it just shows the correct image name. This is the code I used:
import { Row, Col } from "react-grid-system";
import { Separator } from "#fluentui/react";
import * as React from "react";
import { useEffect, useState } from "react";
function Recipe(props) {
const axios = require('axios');
const api = axios.create({
baseURL: 'http://localhost:5000/',
timeout: 10000
})
const [recipe, setRecipe] = useState({});
const [image, setImage] = useState({});
useEffect(() => {
getRecipe()
}, [])
function getRecipe() {
api.get('/recipe/' + props.id).then(res => {
setRecipe(res.data);
setImage("https://localhost:5001/Uploads/" + res.data.image)
});
}
return (
<div>
<div>
<Row>
<Col sm={6} md={6} lg={6}>
<img style={{ width: "700px", marginTop: "20px" }} src={image} alt={"error"} />
</Col>
<Col style={{ marginTop: "20px" }} sm={6} md={6} lg={6}>
<Separator className={"Separator"} />
<h1>Naam: <div style={{ fontSize: "20px" }}>{recipe.name}</div></h1>
<h1>Ingredienten: <div style={{ fontSize: "20px" }}> {recipe.ingredients}</div>
</h1>
<h1>Macro's:
<div style={{ fontSize: "20px" }}>
<ul>
<li>Kcal: {recipe.carbs}</li>
</ul>
</div>
</h1>
<h1>Voorbereiding: <div style={{ fontSize: "12px" }}>
{recipe.preparation} </div></h1>
</Col>
</Row>
</div>
</div>
)
}
export default Recipe

Show length of an array based on what is left in array after its sliced

I have a react component that upon clicking showMore. it will load more comments. The issue im facing is that
View {showMore} More Comments
is not showing the items that are left in the array. Currently there are 7 comments in an array, and if you click show more, it will initially read show 3 more, but when i click again it says show 6 more. when it should be a lesser number than 6. It should be like show 2 more, etc. I'm quite confused on how to go about writing this logic.
What am i doing wrong
CommentList.tsx
import React, { Fragment, useState } from "react";
import Grid from "#material-ui/core/Grid";
import List from "#material-ui/core/List";
import Typography from "#material-ui/core/Typography";
import CommentItem from "./../commentItem/CommentItem";
import moment from "moment";
import OurLink from "../../../common/OurLink";
import OurSecondaryButton from "../../../common/OurSecondaryButton";
import OurModal from "../../../common/OurModal";
.....
function CommentList(props: any) {
const [showMore, setShowMore] = useState<Number>(3);
const [openModal, setOpenModal] = useState(false);
const [showLessFlag, setShowLessFlag] = useState<Boolean>(false);
const the_comments = props.comments.length;
const inc = showMore as any;
const showComments = (e) => {
e.preventDefault();
if (inc + 3 <= the_comments) {
setShowMore(inc + 3);
} else {
setShowMore(the_comments);
}
// setShowLessFlag(true);
};
........
const showMoreComments = () => {
return props.comments
.slice(0, showMore)
.sort((a, b) => a.id - b.id)
.map((comment, i) => (
<div key={i}>
<List style={{ paddingBottom: "20px" }}>
<img alt="gravatar" style={{ margin: "-10px 15px" }} src={comment.author.gravatar} width="30" height="30" />
<Typography style={{ display: "inline-block", fontWeight: 700, padding: "5px 0px" }} variant="h6" align="left">
{Object.entries(props.currentUser).length === 0 ? (
<Fragment>
<span style={{ cursor: "pointer", fontSize: "12px", fontWeight: isBold(comment) }} onClick={handleClickOpen}>
{comment.author.username}
</span>
{comment.userId === props.userId && <span style={{ fontSize: "12px" }}> (OP)</span>}
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
<OurLink
style={{ fontSize: "12px", fontWeight: isBold(comment) }}
to={{
pathname: `/profile/${comment.author.username}`,
}}
title={comment.author.username}
/>
{comment.userId === props.userId && <span style={{ fontSize: "12px" }}> (OP)</span>}
</Fragment>
)}
</Typography>
<div style={ourStyle}>
<CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">
{moment(comment.createdAt).calendar()}
</Typography>
</div>
</List>
</div>
));
};
console.log(props.comments.slice(0, showMore).length);
return (
<Grid>
<Fragment>
<div style={{ margin: "30px 0px" }}>
<OurSecondaryButton onClick={(e) => showComments(e)} component="span" color="secondary">
View {showMore} More Comments
</OurSecondaryButton>
</div>
</Fragment>
{showLessFlag === true ? (
// will show most recent comments below
showMoreComments()
) : (
<Fragment>
{/* filter based on first comment */}
{props.comments
.filter((item, i) => item)
.sort((a, b) => b.id - a.id)
.slice(0, showMore)
.map((comment, i) => (
<div key={i}>
<List style={{ paddingBottom: "20px" }}>
<img alt="gravatar" style={{ margin: "-10px 15px" }} src={comment.author.gravatar} width="30" height="30" />
<Typography style={{ display: "inline-block", fontWeight: 700, padding: "5px 0px" }} variant="h6" align="left">
{Object.entries(props.currentUser).length === 0 ? (
<Fragment>
<span style={{ fontSize: "12px", cursor: "pointer", fontWeight: isBold(comment) }} onClick={handleClickOpen}>
{comment.author.username}
{comment.userId === props.userId && <span style={{ fontSize: "12px" }}> (OP)</span>}
</span>
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
<OurLink
style={{ fontSize: "12px", fontWeight: isBold(comment) }}
to={{
pathname: `/profile/${comment.author.username}`,
}}
title={comment.author.username}
/>
{comment.userId === props.userId && <span style={{ fontSize: "12px" }}> (OP)</span>}
</Fragment>
)}
</Typography>
<div style={ourStyle}>
<CommentItem comment={comment} user={props.user} postId={props.postId} {...props} />
<Typography style={{ fontSize: "12px" }} variant="body1" align="left">
{moment(comment.createdAt).calendar()}
</Typography>
</div>
</List>
</div>
))}
</Fragment>
)}
</Grid>
);
}
// prevents un-necesary re renders
export default React.memo(CommentList);
You want to show 3 more comments each time, or 1-2 items if there are less than 3 items left. So "View 3 More comments" if there are more than 3 left, or "View 1/2 More Comments" if there are only 1 or 2 left.
Or in other words cap the number of new comments shown at 3:
the minimum value of either 3 or (total number of comments - current shown comments = number of comments left).
View {Math.min(3, the_comments - inc)} More Comments

Drag and drop images in React

I'm trying to implement drag and drop behaviour using React JS and react-dropzone library with showing thumbnails.
The code is as follows:
import React from "react";
import ReactDOM from "react-dom";
import Dropzone from "react-dropzone";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dropzone1: [],
dropzone2: []
};
}
addFilesToDropzone(files, dropzone) {
let files_with_preview = [];
files.map(file => {
file["preview"] = URL.createObjectURL(file);
files_with_preview.push(file);
});
const new_files = [...this.state[dropzone], ...files_with_preview];
this.setState({ [dropzone]: new_files });
}
render() {
return (
<div className="App">
<Dropzone
onDrop={files => {
this.addFilesToDropzone(files, "dropzone1");
}}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()} className="">
<input {...getInputProps()} />
<div style={{ height: 100, backgroundColor: "yellow" }}>
Drop some files here
{dropzone1.map(file => (
<img
src={file.preview}
alt={file.path}
style={{ width: 40, height: 40 }}
/>
))}
</div>
</div>
)}
</Dropzone>
<div style={{ display: "flex", flexDirection: "row", marginTop: 25 }}>
<div style={{ width: "100%" }}>
DROPZONE 2
<Dropzone
onDrop={files => {
this.addFilesToDropzone(files, "dropzone2");
}}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()} className="">
<input {...getInputProps()} />
<div style={{ height: 100, backgroundColor: "yellow" }}>
Drop some files here
{this.state.dropzone2.map(file => (
<img
src={file.preview}
alt="dsds"
style={{ width: 40, height: 40 }}
/>
))}
</div>
</div>
)}
</Dropzone>
</div>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is the example on codesandbox.
Everything works fine when I drag files from a folder on my computer, but I want to be able to drag thumbnails generated with dropzone 1 to a dropzone 2. But that doesn't work.
Any idea how to do that?
Yes, that doesn't work because that's not what react-dropzone is designed for. Quote from the website,
Simple React hook to create a HTML5-compliant drag'n'drop zone for files.
Use react-dnd or react-beautiful-dnd instead.
You can use another package: react-file-drop

infinite scroll not working properly in React

I am using infinite scroll plugin for react.js and for some reason it is not working the way it is supposed to work.
The problem is that all the requests are made at once when the page loads, and not like for example a request should be made for each time I scroll.
My code looks like below:
import React from 'react';
import {Route, Link} from 'react-router-dom';
import FourthView from '../fourthview/fourthview.component';
import {withRouter} from 'react-router';
import {Bootstrap, Grid, Row, Col, Button, Image, Modal, Popover} from 'react-bootstrap';
import traineeship from './company.api';
import Header from '../header/header.component';
import InfiniteScroll from 'react-infinite-scroller';
require('./company.style.scss');
class Traineeship extends React.Component {
constructor(props) {
super(props);
this.state = {
companies: [],
page: 0,
resetResult: false,
hasMore: true,
totalPages: null,
totalElements: 0,
};
}
componentDidMount() {
this.fetchCompanies(this.state.page);
}
fetchCompanies = page => {
let courseIds = '';
this.props.rootState.filterByCourseIds.map(function (course) {
courseIds = courseIds + '' + course.id + ',';
});
traineeship.getAll(page, this.props.rootState.selectedJob, courseIds.substring(0, courseIds.length - 1), this.props.rootState.selectedCity).then(response => {
if (response.data) {
const companies = Array.from(this.state.companies);
if(response.data._embedded !== undefined){
this.setState({
companies: companies.concat(response.data._embedded.companies),
totalPages: response.data.page.totalPages,
totalElements: response.data.page.totalElements,
});
}
if (page >= this.state.totalPages) {
this.setState({hasMore: false});
}
} else {
console.log(response);
}
});
};
render() {
return (
<div className={"wrapperDiv"}>
{/*{JSON.stringify(this.props.rootState)}*/}
<div className={"flexDivCol"}>
<div id="header2">
<div style={{flex: .05}}>
<img src="assets/img/icArrowBack.png" onClick={() => this.props.history.go(-1)}/>
</div>
<div style={{flex: 3}}>
<Header size="small"/>
</div>
</div>
<div id="result">
<div className={"search"}>
<h2 style={{fontSize: 22}}>Harjoittelupaikkoja</h2>
<p className={"secondaryColor LatoBold"} style={{fontSize: 13}}>{this.state.totalElements} paikkaa löydetty</p>
</div>
<div className={"filters"}>
<h5 style={{marginTop: '30px', marginBottom: '10px'}} className={"primaryColor"}>
<strong>Hakukriteerit</strong></h5>
{
this.props.rootState.filters.map((filter, key) => (
<div key={key} className={"filter"}>{filter.title}</div>
))
}
</div>
<div className={"searchResults"}>
<h5 style={{marginTop: '30px', marginBottom: '10px'}} className={"primaryColor"}>
<strong>Hakutulokset</strong></h5>
<InfiniteScroll
pageStart={0}
loadMore={this.fetchCompanies}
hasMore={this.state.hasMore}
loader={<div className="loader" key={0}>Loading ...</div>}
useWindow={false}
>
{
this.state.companies.map((traineeship, key) => (
<div id={"item"} key={key}>
<div className={"companyInfo"}>
<div className={"heading"}>
<div id={"companyDiv"}>
<p className={"LatoBlack"} style={{
fontSize: '18px',
lineHeight: '23px'
}}>{traineeship.name}</p>
</div>
{
traineeship.mediaUrl == null
? ''
:
<div id={"videoDiv"}>
<div className={"youtubeBox center"}>
<div id={"youtubeIcon"}>
<a className={"primaryColor"}
href={traineeship.mediaUrl}>
<span style={{marginRight: '3px'}}><Image
src="http://www.stickpng.com/assets/images/580b57fcd9996e24bc43c545.png"
style={{
width: '24px',
height: '17px'
}}/></span>
<span> <p style={{
fontSize: '13px',
lineHeight: '24px',
margin: 0,
display: 'inline-block'
}}>Esittely</p></span>
</a>
</div>
<div id={"txtVideo"}>
</div>
</div>
</div>
}
</div>
<div className={"location"}>
<div id={"locationIcon"}>
<Image src="assets/img/icLocation.png"
style={{marginTop: '-7px'}}/>
</div>
<div id={"address"}>
{
traineeship.addresses.map((address, key) => {
return (
<a href={"https://www.google.com/maps/search/?api=1&query=" + encodeURI("Fredrikinkatu 4, Helsinki")}>
<p key={key} className={"primaryColor"} style={{fontSize: '13px'}}>{address.street}, {address.city}</p>
</a>
)
})
}
</div>
</div>
<div className={"companyDescription"}>
<p className={"secondaryColor"} style={{
fontSize: '14px',
lineHeight: '20px'
}}>{traineeship.description}</p>
</div>
<div>
{
traineeship.images.map((image, key) => {
return (
<img id={"thumbnail"} width={"100%"}
src={image.url}
style={{
width: '80px',
height: '80px',
marginRight: '10px',
marginBottom: '10px'
}}
alt=""
key={key}
/>
)
})
}
</div>
<div className={"companyContacts"} style={{marginTop: '20px'}}>
<p className={"contactInfo"}>URL: {traineeship.website}</p>
<p className={"contactInfo"}>Email: {traineeship.email}</p>
<p className={"contactInfo"}>Puh: {traineeship.phonenumber}</p>
<p className={"contactInfo"}>Contact: {traineeship.contact}</p>
</div>
</div>
</div>
))
}
</InfiniteScroll>
</div>
</div>
</div>
</div>
);
}
}
export default withRouter(Traineeship);
What can I do so I can eliminate all the request are made when the page is load, I mean this is even worse sending let say 20 request one after another within a second or so.
Any suggestion what is wrong with my code?
by removing useWindow={false} it is working now!
Update: Haven't seen that you are using react-infinite-scroller. If you want to build the loader yourself, see my previous answer. With the plugin you can set a threshold.
The answer lies in here: Question: Infinite Scrolling
What you basically need to do is to set a variable in state, isLoading: false, and change it when data comes in via fetch, when data loading done set it back to false. In your infinite scroll function check (this.state.isLoading).

Categories