React read string as component - reactjs

I'm using bootstrap framework to render a simple 3 column grid layout out of some content on a page. Where it gets complicated, I'm trying to conditionally render bootstrap <Row> or </Row> depending on where we are in the loop - for instance ,render a <Row> if index % 3 == 0 and render a </Row> if (index + 1) % 3 == 0. Because there are problems with conditional rendering in map, I've come up with the following approach which would work if there was a way to read a string as a React component. Does anyone know how to do this?
const websiteData = [
{
name: "abcdefg",
url: "https://www.abcedfg.com/",
categories: ["react"],
thumbnail: require("./assets/images/image.png"),
},
{
name: "rrrrrr",
url: "https://www.rrrrrr.com/",
categories: ["wordpress"],
thumbnail: require("./assets/images/rrrrrr.png"),
},
{
name: "jjjjjjjj",
url: "https://www.jjjjjjjj.com/",
categories: ["wordpress"],
thumbnail: require("./assets/images/jjjjjjjj.png"),
},
{
name: "ffffff",
url: "https://www.ffffff.com/",
categories: ["wordpress"],
thumbnail: require("./assets/images/ffffff.png"),
},
{
name: "dddddd",
url: "https://www.dddddd.com/",
categories: ["wordpress"],
thumbnail: require("./assets/images/dddddd.png"),
},
{
name: "adadads",
url: "https://www.adadads.com/",
categories: ["wordpress"],
thumbnail: require("./assets/images/adadads.png"),
},
];
const RowOpen = (props) => {
const { index } = props;
if (index % 3 == 0) {
return "<Row>";
} else {
return <span />;
}
};
const RowClose = (props) => {
const { index } = props;
if (index + (1 % 3) == 0) {
return "</Row>";
} else {
return <span />;
}
};
const Websites = () => {
return websiteData.map((site, index) => {
return (
<React.Fragment>
<RowOpen index={index} />
<Col md="4">
<a>
<img style={{ maxWidth: "100%" }} src={site.thumbnail} />
</a>
</Col>
<RowClose index={index} />
</React.Fragment>
);
});
};
Ultimately I want to loop through websiteData and wind up with something like this:
<Row>
<Col md=4">
...
</Col>
<Col md=4">
...
</Col>
<Col md=4">
...
</Col>
</Row>
<Row>
<Col md=4">
...
</Col>
<Col md=4">
...
</Col>
<Col md=4">
...
</Col>
</Row>

You have <RowOpen index={index} />...<RowClose index={index} />, and then these are defined as returning "<Row>" and "</Row>". To be clear, these are strings, and will display as plain text, and not as components. You cannot define components like this in ReactJS. You must define them like so...
var component = <Row />;
Just try it with your approach in an a sandbox online:
Expected corresponding JSX closing tag for
Do this instead: Make a display function for each row...
const Websites = () => {
return (
websiteData.map((site, index) => {
return (
<React.Fragment>
{RowDisplay(site, index)}
</React.Fragment>
)
})
)
}
And then have your RowDisplay() function display either either <Row>'s or <span>'s...
const RowDisplay = (site, index) => {
if (index + 1 % 3 == 0) {
return RowDisplayRow(site, index);
} else {
return RowDisplaySpan(site, index);
}
}
Then you just need the final Row/Span display functions...
const RowDisplayRow = (site, index) => {
return (
<Row index={index}>
<Col md="4">
<a>
<img style={{ maxWidth: '100%' }} src={site.thumbnail} />
</a>
</Col>
</Row index={index}>
);
}
const RowDisplaySpan = (site, index) => {
return (
<Row index={index}>
<Col md="4">
<a>
<img style={{ maxWidth: '100%' }} src={site.thumbnail} />
</a>
</Col>
</Row index={index}>
);
}
I have done this all in sudo-code, but, I think you'll probably get the idea I'm trying to demonstrate. Explanation is sometimes much more important than just code!

You just simply put the conditions in the map like
return (
websiteData.map((site, index) => {
return (
<React.Fragment>
{index % 3 == 0 ? <Row /> : <span />}
<Col md="4">
<a>
<img style={{ maxWidth: '100%' }} src={site.thumbnail} />
</a>
</Col>
{index + 1 % 3 == 0 ? </Row> : <span /> }
</React.Fragment>
)
})
)
Try this You will get your desired output ! It will help
Simple and best approach ! :)

Related

hide buttons from interface

I have a modal, and this modal has two interfaces, the first is “QRReader” and the second is “PatientForm”, and the modal has two buttons, the first is “Approve” and the second is “Cancel”.
And I want to hide the two buttons within the interface of the "QRReader"
How can i solve the problem?
And this file contains the entire modal, knowing that the BasicModal tag is modal
import { Button, Col, Row } from "antd";
import {
useState
} from "react";
import { QrReader } from "react-qr-reader";
import patient from "../../../api/nuclearMedicineApi/services/Patient";
import { decrypt } from "../../../utils/decryption";
import PatientForm from "./form";
import { QrcodeOutlined } from '#ant-design/icons';
import BasicModal from "../modal";
import { FormattedMessage } from "react-intl";
import { notify } from "../notification";
const QRScanner = () => {
const [data, setData] = useState<number>(0);
const [patientInfoData, setPatientInfoData] = useState({})
const [modelVisible, setModelVisible] = useState<any>();
console.log('datadatadata: ', data)
const openNotificationWithIcon = () => {
// onSuccess: (data) => {
notify('success', 'ok', 'approve-message');
// },
};
return (
<>
<QrcodeOutlined
className='cursor-pointer'
style={{ fontSize: '2rem' }}
color={'#fff'}
onClick={(e) => {
e.stopPropagation()
setModelVisible(true)
}}
/>
<BasicModal
header={<>
<h2 className='text-primary'><FormattedMessage id="qr-scanner" /></h2>
</>}
content={
<>
{
data !=0 ?
<PatientForm patientInfoData={patientInfoData} data={data} />
:
<Row>
<Col span={18} offset={3}>
<QrReader
onResult={async (result: any, error) => {
if (!!result) {
const t = result?.text;
const d = decrypt(t);
let zz: any = d.match(/(\d+)/)
Math.floor(zz[0])
setData(zz[0]);
const pationInfo = await patient.patientGet({ Id: Number(zz[0]) })
setPatientInfoData(pationInfo)
}
if (!!error) {
console.info(error);
}
}} // style={{ width: '100%' }}
constraints={{ facingMode: 'user' }}
// style={{ width: '100%' }}
/>
</Col>
</Row>
}
<Row>
<Col span={1} offset={3}>
<Button
type='primary'
className='savebtn'
onClick={() => {
patient.switchToPresent(data || 0)
openNotificationWithIcon()
}}
>
<FormattedMessage id={'approve'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={() => {
setModelVisible(false);
setData(0);
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
</>
}
isOpen={modelVisible}
footer={false}
width='50vw'
handleCancel={() => {
setModelVisible(false);
}}
afterClose={
() => setData(0)
}
/>
</>
)
};
export default QRScanner;
I think you should be able to use a similar condition as you are using to determine if you should render patientForm vs QRReader. You could wrap your buttons with something like this
{ data = 0 && (
<Row>
<Col span={1} offset={3}>
<Button
type='primary'
className='savebtn'
onClick={() => {
patient.switchToPresent(data || 0)
openNotificationWithIcon()
}}
>
<FormattedMessage id={'approve'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={() => {
setModelVisible(false);
setData(0);
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
)
}
You can have the same condition for showing the buttons which you have for QRScanner and PatientForm
{data != 0 ? (
<Row>
<Col span={1} offset={3}>
<Button
type='primary'
className='savebtn'
onClick={() => {
patient.switchToPresent(data || 0)
openNotificationWithIcon()
}}
>
<FormattedMessage id={'approve'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={() => {
setModelVisible(false);
setData(0);
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
) : </>}

Matching two divs height on resize in React

I have this
const divRef = createRef()
const [dimensions, setDimensions] = useState({ width: 1, height: 2 })
const useRefDimensions = (ref) => {
useEffect(() => {
if (ref.current) {
const { current } = ref
const boundingRect = current.getBoundingClientRect()
const { width, height } = boundingRect
setDimensions({ width: Math.round(width), height: Math.round(height) })
}
}, [ref])
return dimensions
}
useRefDimensions(divRef)
On the render...
<Row>
<Col md={12} lg={7}>
<Card bg="dark" ref={divRef}>
Some image or video here
</Card>
</Col>
<Col md={12} lg={5}>
<PerfectScrollbar style={{height: dimensions.height}} >
<Card bg="dark">
Some stuff in here
</Card>
</PerfectScrollbar>
</Col>
</Row>
I can make it work (sort of) but only if I use the ref dependency. Problem is, I get an error and what it looks like an infinite loop
index.js:1 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
EDIT - The entire component
import React, {useState, useEffect, useRef} from 'react'
import { Card, Row, Col } from 'react-bootstrap'
import PerfectScrollbar from 'react-perfect-scrollbar'
import succession from '../../assets/images/succession/succession.jpeg'
import successiontrailerposter from '../../assets/images/succession/succesion trailer.jpg'
import successiontrailer from '../../assets/images/succession/successionTrailer.mp4'
import {ReactVideo} from 'reactjs-media'
const metadata = [
{
title: "Succession",
source: "HBO",
type: "Original",
mediatype: "TV Series",
rating: "TV-MA",
seasons: 3,
episodes: 30,
poster: succession,
trailerposter: successiontrailerposter,
trailer: successiontrailer,
summary: "When Logan Roy (Brian Cox) announces his plans to step back from running the family's multi-national entertainment company, a feud over who will be have control causes tension among his four children.",
info: [
{
releasedate: "June 3, 2018",
availabilitydate: "May 27, 2020",
runtime: "56 - 70 minutes",
genres:[ "Drama", "Comedy-Drama", "Black-Comedy" ],
tags: ["Patriarchy", "finance", "based on true story" ],
regions: ["North America", "Latin America"],
language: "English",
awards: ["Golden Globe: Best TV Series"],
prod_budget: "$50,000,000",
marketing_budget: "$3,000,000",
}
]
}
]
const Home = () => {
const divRef = useRef()
const [dimensions, setDimensions] = useState({ width: 1, height: 2 })
useEffect((ref) => {
if (ref.current) {
const { current } = ref
const boundingRect = current.getBoundingClientRect()
const { width, height } = boundingRect
setDimensions({ width: Math.round(width), height: Math.round(height) + 35 })
}
return dimensions
}, [dimensions])
return (
<div className="container-fluid pt-3 px-5">
{metadata.map((item, index) => {
return (
<div key={index}>
<div className="d-flex align-items-end justify-content-between pb-4">
<h1 className="item-title m-0 text-primary text-bold">{item.title}</h1>
<div className="d-flex flex-column align-items-end justify-content-end">
<p className="m-0">{item.source} {item.type} | {item.mediatype} | {item.rating}</p>
<p className="m-0">Seasons: {item.seasons} ({item.episodes} episodes)</p>
</div>
</div>
<Row>
<Col xs={12} md={3} lg={2}>
<Card bg="dark">
<Card.Img variant="top" src={item.poster} />
</Card>
</Col>
<Col xs={12} md={9} lg={10}>
<Row className="media-info">
<Col
md={12}
lg={7}
className="media-container" >
<Card bg="dark" ref={divRef}>
<ReactVideo
src={item.trailer}
poster={item.trailerposter}
className="video-player"/>
</Card>
</Col>
<Col
md={12}
lg={5}
className="metadata-container" >
<PerfectScrollbar style={{ height: dimensions.height }}>
<Card bg="dark">
<Card.Body>
<div className="mb-3">
<h4 className="card-h4">Series Summary</h4>
<p className="card-p">{item.summary}</p>
</div>
<div>
<h4 className="card-h4">Series Metadata</h4>
<ul className="card-ul">
<li>
<label>Release Date: </label>
<span>{item.info[0].releasedate}</span>
</li>
<li>
<label>HBO Max First Availability Date: </label>
<span>{item.info[0].availabilitydate}</span>
</li>
<li>
<label>Runtime: </label>
<span>{item.info[0].runtime}</span>
</li>
<li>
<label>Genre(s): </label>
{ item.info[0].genres.map((genre, index) => {
return (
<span key={`genre_${index}`}>
{ (index ? ', ' : '') + genre }
</span>
)
})}
</li>
<li>
<label>Plot Tag(s): </label>
{ item.info[0].tags.map((tag, index) => {
return (
<span key={`tag_${index}`}>
{ (index ? ', ' : '') + tag }
</span>
)
})}
</li>
<li>
<label>Region Availability: </label>
{ item.info[0].regions.map((region, index) => {
return (
<span key={`region_${index}`}>
{ (index ? ', ' : '') + region }
</span>
)
})}
</li>
<li>
<label>Original Language: </label>
<span>{item.info[0].language}</span>
</li>
<li>
<label>Awards: </label>
{ item.info[0].awards.map((award, index) => {
return (
<span key={`award_${index}`}>
{ (index ? ', ' : '') + award }
</span>
)
})}
</li>
<li>
<label>Production Budget: </label>
<span>{item.info[0].prod_budget}</span>
</li>
<li>
<label>Marketing Budget: </label>
<span>{item.info[0].marketing_budget} </span>
</li>
</ul>
</div>
</Card.Body>
</Card>
</PerfectScrollbar>
</Col>
</Row>
</Col>
</Row>
</div>
)
})}
</div>
)
}
export default Home
You are creating a new ref every time setDimensions is called. Change createRef to useRef. I'd also remove the useRefDimensions function and just call useEffect directly.
const divRef = useRef(null)
const [dimensions, setDimensions] = useState({ width: 1, height: 2 })
useEffect(() => {
if (divRef.current) {
const { current } = divRef
const boundingRect = current.getBoundingClientRect()
const { width, height } = boundingRect
setDimensions({ width: Math.round(width), height: Math.round(height) })
}
}, [divRef])

How do you pass data from one child component to another sibling?

My goal is to display different values in the < h1 > of the GoalsDone component depending on which TeamCard is being hovered over. Both of these components are rendered inside of the TopGroups component, in these code snippets I am attempting to pass through the parent TopGroups.
Child component displaying the number of goals done:
const GoalsDone = ({displayGoals}) => {
return (
<GameifyStyle className="">
<Col className="GPM">
<img className="pt-3 mx-auto pl-0" src="../images/bullseye.png" />
<h1 className="pt-1"> {displayGoals}75 Goals</h1>
<p>DONE</p>
</Col>
</GameifyStyle>
)
}
Child Component that updates the score after being hovered over:
It currently has an unhandled runtime error "setDisplayGoals is not a function"
const TeamCard = ({data}, {setDisplayGoals}) => {
return (
<TeamCardStyle>
{!data && (
<Spinner />
)}
{data && data.getGroupScores && (
data.getGroupScores.slice(0, 4).map((group, index) => {
return (
<Row onMouseEnter={() => {setDisplayGoals(group.totalScore)}}>
<Col className="teamCard mt-2 mb-2">
<Row>
<p>{seed[index]}</p>
</Row>
<Row>
<Col className="hideSmall">
<img className="mouseOn" src="../images/group.png" />
<img className="mouseOff" src="../images/groupSelected.png" />
</Col>
</Row>
<p>{group.name}</p>
</Col>
</Row>
)
})
)}
</TeamCardStyle>
)
}
Parent component:
ATTN lines 38, 48
const GET_GROUP_SCORES = gql`
query GET_GROUP_SCORES($quarter: String, $groupId: String) {
getGroupScores(quarter: $quarter, groupId: $groupId) {
name
id
totalScore
goalsDone
milestonesDone
}
}
`;
const TopGroups = () => {
const {loading, error, data } = useQuery(GET_GROUP_SCORES, {variables: { quarter: "Q2 2021" }})
if (data) {
const sortedGroups = data.getGroupScores.sort((a, b) => {
if (a.totalScore > b.totalScore) {
return -1
}
if (a.totalScore < b.totalScore) {
return 1
} else {
return 0
}
})
}
if (error) {
return <p>An error has occured</p>
}
if (loading) {
<Spinner />
}
const [displayGoals, setDisplayGoals] = useState('0');
return (
<Col className="col-12">
<TeamCardStyle>
<Row>
<TeamCard
data={data}
setDisplayGoals={setDisplayGoals}
/>
</Row>
</TeamCardStyle>
<GameifyStyle>
<Row className="cardContainer mt-3 XsWidthAdjust">
<Col className="SideBorder TopGroupsFonts mx-1">
<GoalsDone
displayGoals={displayGoals} />
</Col>
<Col className="SideBorder TopGroupsFonts mx-1">
<PrizesWon />
</Col>
<Col className="SideBorderPH TopGroupsFonts mx-1">
<MilestonesOnTrack />
</Col>
</Row>
</GameifyStyle>
</Col>
)
}
The error "Child Component that updates the score after being hovered over: It currently has an unhandled runtime error "setDisplayGoals is not a function"" happens because you are destructuring the props wrong in your TeamCard component. Instead of doing
const TeamCard = ({data}, {setDisplayGoals}) => {
You should do:
const TeamCard = ({data, setDisplayGoals}) => {

How to hide header and footer for a specific Route

I have a OrderPrintReceiptScreen, on loading this screen I want the header and footer to not show on the screen. And after that I want to use window.print(); And this way a clean PDF Receipt can be achieved. But due to header and Footer they make it very dense and I'm not sure how to remove that on loading this OrderPrintReceiptScreen.
This is the layout of App
function App() {
return (
<Router>
<Header />
<main className="py-1">
<Container>
//.....
<Route path="/order-receipt/:id" component={OrderPrintReceiptScreen} />
//.....
</Container>
</main>
<Footer />
</Router>
);
}
export default App;
OrderPrintReceiptScreen.js
import React, { useEffect } from "react";
import { Button, Row, Col, ListGroup } from "react-bootstrap";
import { Page, Text, View, Document, StyleSheet } from "#react-pdf/renderer";
import { LinkContainer } from "react-router-bootstrap";
import { useDispatch, useSelector } from "react-redux";
import Message from "../components/Message";
import Loader from "../components/Loader";
import {
getOrderDetails,
// payOrder,
} from "../actions/orderActions";
import {
ORDER_PAY_RESET,
ORDER_DELIVER_RESET,
} from "../constants/orderConstants";
// Create styles
const styles = StyleSheet.create({
page: {
flexDirection: "row",
backgroundColor: "#E4E4E4",
},
section: {
margin: 10,
padding: 10,
flexGrow: 1,
},
});
function OrderPrintReceiptScreen({ match, history }) {
const orderId = match.params.id;
const dispatch = useDispatch();
const orderDetails = useSelector((state) => state.orderDetails);
const { order, error, loading } = orderDetails;
const orderPay = useSelector((state) => state.orderPay);
const { loading: loadingPay, success: successPay } = orderPay;
const orderDeliver = useSelector((state) => state.orderDeliver);
const { loading: loadingDeliver, success: successDeliver } = orderDeliver;
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
if (!loading && !error) {
order.itemsPrice = order.orderItems
.reduce((acc, item) => acc + item.price * item.qty, 0)
.toFixed(2);
}
useEffect(() => {
if (!userInfo) {
history.push("/login");
}
if (
!order ||
successPay ||
order._id !== Number(orderId) ||
successDeliver
) {
dispatch({ type: ORDER_PAY_RESET });
dispatch({ type: ORDER_DELIVER_RESET });
dispatch(getOrderDetails(orderId));
}
}, [dispatch, order, orderId, successPay, successDeliver]);
const printReceipt = (e) => {
e.preventDefault();
window.print();
};
return loading ? (
<Loader />
) : error ? (
<Message variant="danger">{error}</Message>
) : (
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>Section #1</Text>
</View>
<View style={styles.section}>
<Text>Section #2</Text>
</View>
<Row>
<Col md={10}>
<ListGroup variant="flush">
<ListGroup.Item>
<LinkContainer to={`/order-receipt/${order._id}`}>
<Button
variant="outline-success"
className="mx-4 my-4 btn-lg"
fluid
onClick={printReceipt}
>
Download Receipt
</Button>
</LinkContainer>
</ListGroup.Item>
<ListGroup.Item>Order ID : {order._id}</ListGroup.Item>
<ListGroup.Item>
Created On : {order.createdAt.substring(0, 10)},{" "}
{order.createdAt.substring(11, 19)}
</ListGroup.Item>
<ListGroup.Item>
Order Items:
{order.orderItems.length === 0 ? (
<Message variant="info">Order is empty</Message>
) : (
<ListGroup flush>
{order.orderItems.map((item, index) => (
<ListGroup.Item key={index}>
<Row>
<Col>{item.name}</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</ListGroup.Item>
<ListGroup variant="flush">
<ListGroup.Item>Name : {order.user.name}</ListGroup.Item>
<ListGroup.Item>
Phone Number : {order.shippingAddress.phoneNumber}
</ListGroup.Item>
<ListGroup.Item>
Shipping Address : {order.shippingAddress.address},{" "}
{order.shippingAddress.city}
{" "}
{order.shippingAddress.postalCode},{" "}
{order.shippingAddress.country}
</ListGroup.Item>
{order.isPaid ? (
<Message variant="light">
Payment Status : Paid On {order.paidAt.substring(0, 10)},{" "}
{order.paidAt.substring(11, 19)}
</Message>
) : (
<Message variant="warning">Not Paid</Message>
)}
<ListGroup variant="flush">
<ListGroup.Item>Payment Summary : </ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Items Price :</Col>
<Col>PKR {order.itemsPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping Price :</Col>
<Col>PKR {order.shippingPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Tax Amount :</Col>
<Col>PKR {order.taxPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Total Payable :</Col>
<Col> PKR {order.totalPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Total Paid :</Col>
<Col> PKR {order.totalPricePaid}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Remaining Amount:</Col>
<Col>
{" "}
PKR {Number(order.totalPrice) - order.totalPricePaid}
</Col>
</Row>
</ListGroup.Item>
</ListGroup>
{order.isDelivered ? (
<Message variant="light">
Delivery Status : Delivered on{" "}
{order.deliveredAt.substring(0, 10)},{" "}
{order.deliveredAt.substring(11, 19)}
</Message>
) : (
<Message variant="warning">Not Delivered</Message>
)}
</ListGroup>
</ListGroup>
</Col>
{/* <Col md={4}>
</Col> */}
</Row>
</Page>
);
}
export default OrderPrintReceiptScreen;
There are 2 ways to do this:
The first method is to check your matching URL before rendering:
render() {
const {match: {url}} = this.props;
if(url.startWith('/ignore-header-path') {
return null;
} else {
// your render jsx
}
}
The second method is to use #media print:
#media print {
/* Your print styles */
.header, .footer { display: none !important; }
}
You need to create a component Like a layout inside layout you can manage conditional header and footer Like this example.
Remove the Header footer from the App file.
Have a look I hope it's helpful
const OrderPrintReceiptScreen= (props) => {
return (
<Layouts
showFooter={false}
showHeader={false}
>
<Childeren {...props} />
</Layouts>
);
};
const Layouts= ({showFooter,showHeader,children}) =>{
return (
{showHeader && <Header/>}
{children}
{showFooter && <Footer/>}
)
}
........
You can use the window.location.pathname to get the current route after that do the validation on the route if the same hide the header else show the header.
{
window.location.pathname!=="/login"? <Header/> : null
}

Removing complex components from an array in ReactJS

I'm trying to make a list of components. I need to remove the items individually but it seems that the state inside the remove function is always outdated.
For example, if I add 10 authors and click in the 10th author remove button, it'll show me 9 elements (which is already wrong) and if I click on the 2nd author, it shows me just 1 element inside the array. Am I missing something here?
const [authorsFields, setAuthorsFields] = useState<Array<JSX.Element>>([]);
const removeAuthorField = () => {
console.log(authorsFields.length);
}
const removeButton = () => {
return (
<Col className={"d-flex justify-content-end py-1"}>
<Button variant={"danger"} onClick={() => removeAuthorField()}>Remove author</Button>
</Col>
);
}
const authorField = (removable: boolean) => {
return (
<>
<Row className={"mb-2"}>
<Form.Group className={"py-1"}>
<Form.Label>Author name</Form.Label>
<Form.Control type={"text"}/>
</Form.Group>
{removable && removeButton()}
</Row>
</>
);
}
const addAuthorField = () => {
if (authorsFields.length !== 0) {
setAuthorsFields((old) => [...old, authorField(true)]);
} else {
setAuthorsFields([authorField(false)]);
}
}
useEffect(() => {
if (authorsFields.length === 0) {
addAuthorField();
}
}, [])
return (
<>
<Col sm={3} style={{maxHeight: "60vh"}} className={"mt-4"}>
<Row>
{authorsFields}
<Row>
<Form.Group className={"py-1"}>
<Button style={{width: "100%"}} onClick={() => addAuthorField()}>
Add Author
</Button>
</Form.Group>
</Row>
</Row>
</Col>
</>
);
Use the following functional component as an example to modify your code on how to use JSX elements seperated from the state management inside the functional components.
import React, { useState } from "react";
import { Button, Row, Col } from "antd";
function App() {
const [authorsCount, setAuthorsCount] = useState(0);
// Use authorsFields to manage authors details in an array of objects
const [authorsFields, setAuthorsFields] = useState([]);
const removeAuthorField = (id) => {
// To remove relevant author filter out the authors without the relevant id
setAuthorsFields((old) =>
old.filter((authorField) => authorField.id !== id)
);
};
const addAuthorField = () => {
setAuthorsFields((old) => [...old, { id: authorsCount, removable: true }]);
setAuthorsCount((old) => old + 1);
};
return (
<div>
<Col sm={3} style={{ maxHeight: "60vh" }} className={"mt-4"}>
<Row>
{authorsFields.map((authorField) => (
<Row className={"mb-2"}>
<div className={"py-1"}>
<div>{`Author name ${authorField.id}`}</div>
</div>
{authorField.removable && (
<>
<Col className={"d-flex justify-content-end py-1"}>
<Button
variant={"danger"}
onClick={() => removeAuthorField(authorField.id)}
>
Remove author
</Button>
</Col>
</>
)}
</Row>
))}
<Row>
<div className={"py-1"}>
<Button
style={{ width: "100%" }}
onClick={() => addAuthorField()}
>
Add Author
</Button>
</div>
</Row>
</Row>
</Col>
</div>
);
}
export default App;
Following is the view.

Resources