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

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}) => {

Related

How do I pass one string of an array each time a component is rendered?

This component renders four cards for top teams in a database, they are already sorted by highest scores; I need a <p> tag that displays either "1st", "2nd", "3rd", "4th" on each one. The current code displays "1st2nd3rd4th" in the <p> tag of each card.
const seed = ["1st", "2nd", "3rd", "4th"];
const place = seed.map((seed) => seed);
const TeamCard = ({data}) => {
return (
<TeamCardStyle>
{!data && (
<Spinner />
)}
{data && data.getGroupScores && (data.getGroupScores.slice(0, 4).map((group) => (
<Row>
<Col className="teamCard mt-2 mb-2">
<Row>
<p># {place}</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>
)
}
export default TeamCard;
Happy Hacking!
You can use index from map function to get the relevant element from seed array.
const seed = ["1st", "2nd", "3rd", "4th"];
const TeamCard = ({data}) => {
return (
<TeamCardStyle>
{!data && (
<Spinner />
)}
{data && data.getGroupScores && (
data.getGroupScores.slice(0, 4).map((group, index) => {
return (
<Row>
<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>
)
}
export default TeamCard

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.

React read string as component

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 ! :)

Create search box in the list in react js component

Please tell, how to carry out search in the list of components which code is specified below correctly?
The search should be performed by title or full description of the list item.
Component with list of Item components:
const PathItems = () => {
const dispatch = useDispatch();
const pathDescription = useSelector(state => state.firestore.ordered.pathDescription);
const handleClick = (path) => {
dispatch(selectPath(path));
}
if(pathDescription && pathDescription.length !== 0){
return (
<React.Fragment>
<Row>
<Col className="pl-0 border-right border-dark">
{pathDescription && pathDescription.map(item => (
<PathItem
key={item.id}
item={item}
onInfoChange={ handleClick }
/>
))}
</Col>
<Col>
<FullDecript/>
</Col>
</Row>
</React.Fragment>
)
} else {
return (
<h5 className="text-muted text-center text-middle">Add your first route</h5>
)
}
}
export default compose(firestoreConnect(()=> ['pathDescription']))(PathItems);
Item component code:
const PathItem = ({ item, onInfoChange }) => {
const handleClick = () => {
onInfoChange(item);
}
return (
<React.Fragment>
<Card as="a"
style={{cursor: 'pointer'}}
className={'mb-2'}
onClick={ handleClick }>
<Card.Body>
<Row className="align-items-center">
<Col xs={1}>
</Col>
<Col xs={7}>
<h5>{item.title}</h5>
{item.sDescript}
</Col>
<Col xs={4} className="text-right">
<label>{item.length}600 km</label>
</Col>
</Row>
</Card.Body>
</Card>
</React.Fragment>
);
}
export default PathItem;
General view of the described components
Thanks in advance)
...
const [searchQuery, setQuery] = useState("");
const inputEvent = (event) => {
const data = event.target.value;
console.log(pathDescription);
setQuery(data);
}
const filterItems = pathDescription && pathDescription.filter(item => {
return item.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.fDescript.toLowerCase().includes(searchQuery.toLowerCase());
})
...
<Col className="pl-0 border-right border-dark" style={divStyle}>
<InputGroup className="mb-3">
<FormControl
type="text"
placeholder="Type..."
aria-describedby="basic-addon2"
value={ searchQuery }
onChange={ inputEvent }
/>
<InputGroup.Append>
<Button variant="outline-secondary">
<img
alt="Logo"
src="https://cdn1.iconfinder.com/data/icons/app-user-interface-line/64/search_focus_user_interface_app_zoom-256.png"
width="25"
height="25"
className="d-inline-block align-top"/>
</Button>
</InputGroup.Append>
</InputGroup>
{filterItems.sort((a, b) => b.favorite - a.favorite).map(item => (
<PathItem
key={item.id}
item={item}
onInfoChange={ handleClick }
/>
))}
</Col>

Resources