Reputation: 715
I'm trying to do something like this:
When I click on a card on the left and then ctrl+click on a card on the right.. a line will be drawn. Ctrl+click on the card on the right again and the line will be removed. I'm going to draw the line using an svg path but I have to get the positions of the cards in the Container Component to do so.
I'm trying to do this with React Function Components using ref and I'm not quite getting it. I understand that function components don't have an "instance" and therefore ref doesn't work quite like it does in classes.
Please answer the following:
Any help is appreciated and thanks in advance!
EDIT
This is what I have so far:
ContainerComponent
import React, { useState, useRef } from 'react';
import { Container, Row, Col, Card, Button } from 'reactstrap';
import SectionContainer from './SectionContainer';
const ContainerComponent = (props) =>
{
const [keyCards, setKeyCards] = useState([
{ id: 1, name: 'key one', description: "this is a key card for one" },
{ id: 2, name: 'key two', description: "this is a key card for two" }
])
const [valueCards, setValueCards] = useState([
{ id: 1, name: 'value one', description: "this is a value card" },
{ id: 2, name: 'value two', description: "this is a value card" },
{ id: 3, name: 'value three', description: "this is a value card" }
])
const [connections, setConnections] = useState([
// I need something like this for all connections that need a line
{
keyID: 1,
keyPos: { x: 100, y: 200 },
attachedValues: [
{ valueID: 1, valPos: { x: 200, y: 200 } },
{ valueID: 2, valPos: { x: 200, y: 300 } }
]
}
])
const cardRef = useRef()
const getPositions = () =>
{
console.log(cardRef.current)
// if I can get positions and if they are selected
// then I can set the state and generate svg paths
}
return (
<>
<Row >
<Col className="display-section" md='6'>
<SectionContainer
title="Rules for Life"
items={keyCards}
cardRef={cardRef}
onClickHandler={getPositions}
/>
</Col>
<Col className="display-section" md='6'>
<SectionContainer
title="Entitlements for Days"
items={valueCards}
cardRef={cardRef}
onClickHandler={getPositions}
/>
</Col>
</Row>
{/* <DrawLines connections={connections} /> */}
</>
)
}
export default ContainerComponent
SectionContainer
import React from 'react';
import { Row, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
import CardContainer from './CardContainer'
const SectionContainer = ({ title, items, cardRef, onClickHandler }) =>
{
return (
<>
<h6>{title}</h6>
<Row >
<InputGroup>
<InputGroupAddon addonType="prepend" >
<InputGroupText>filter:</InputGroupText>
</InputGroupAddon>
<Input />
</InputGroup>
</Row>
<CardContainer
items={items}
cardRef={cardRef}
onClickHandler={onClickHandler}
/>
</>
)
}
export default SectionContainer
Card Container
import React from 'react'
import { Container, Row } from 'reactstrap'
import CardComponent from './CardComponent'
const CardContainer = ({ items, cardRef, onClickHandler }) =>
{
const getItems = (items) =>
{
return items.map(item =>
{
return <Row key={item.id}>
<CardComponent
item={item}
cardRef={cardRef}
onClickHandler={onClickHandler}
/>
</Row>
})
}
return (
<Container fluid className="node-container">
{getItems(items)}
</Container>
)
}
export default CardContainer
Card Component
import React, { useState } from 'react';
import { Card, CardHeader, CardTitle, CardBody, CardText } from 'reactstrap';
const CardComponent = React.forwardRef(({ item, cardRef, onClickHandler }, ref) =>
{
return (
<Card ref={cardRef} >
<CardHeader onClick={onClickHandler} >
<CardTitle>{item.name}</CardTitle>
</CardHeader>
<CardBody>
<CardText>{item.description}</CardText>
</CardBody>
</Card>)
})
export default CardComponent
This displays the cards similar to the drawing above no problem. I just can't get the ref thing to work.
I'm getting the following error for CardComponent even tho I have wrapped it with React.ForwardRef():
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of
ForwardRef
. in Card (at CardComponent.js:8) ...
Edit
I fixed the forwardRef() error by wrapping my Card in a div. Even tho the bootstrap Card renders as a div, you can't ref it.
Only one position is passing up to the ContainerComponent tho. Still working on that.
New CardComponent
import React, { useState } from 'react';
import { Card, CardHeader, CardTitle, CardBody, CardText } from 'reactstrap';
const CardComponent = ({ item, cardRef, onClickHandler }) =>
(
<div ref={cardRef}>
<Card >
<CardHeader onClick={onClickHandler} >
<CardTitle>{item.name}</CardTitle>
</CardHeader>
<CardBody>
<CardText>{item.description}</CardText>
</CardBody>
</Card>
</div>
)
export default CardComponent
Upvotes: 2
Views: 8692
Reputation: 715
I'm going to answer my own question here. I owe it to the community since my question was pretty vague. Big thanks to @dlya for his answer which led me down the path to righteousness :)
Yes. I was able to complete the task end to end without classy components.
None. I didn't end up having to use React.forwardRef() at all. In fact... I didn't use useRef() or useEffect() either. I just passed a function to the child and added the function to the ref like this:
<Card innerRef={updateCardLocations} id={`${type}-${item.id}`}>
Notice that I had to use innerRef instead of ref because I really needed to get the reactstrap ref instead of having to wrap Card in a div.
Then I just collect all of my Card positions in the parent like this:
let connections = {}
const updateCardLocations = (card) =>
{
if (card)
{
const id = card.id.substr(card.id.indexOf("-") + 1)
const pos = card.getBoundingClientRect()
connections[id] = {
...connections[id],
connectionPoint: [pos.right, pos.top + (pos.height / 2)]
}
console.log(connections)
}
}
I just showed what that looks like in the previous bullet. The parent has a function called updateCardLocations which is passed as a prop to the child. Pass the updateCardLocations function prop to a ref in the Child. In my case innerRef.
What I was missing from the documentation was that you're just creating a placeholder in the parent that you pass to the child component as a prop. Then when the child component is rendered it updates that placeholder with it's dom information. I don't fully grasp things until I can get a successful hands on experience... but in my defense, the whole forwardRef thing really confused me because I couldn't tell which direction I was forwarding the ref and at that time I still thought that I had created a ref in the parent as well with useRef(). Hopefully this helps someone that is as easily confused as me :)
Here's a working sample on CodeSandbox. I also included an onClickHandler that adds cards and you can see the positions updating in the console.
Upvotes: 4
Reputation: 93
Using react hooks you can basically do anything that you did in a class component in a functional component.
I guess you can get the position using getBoundingClientRect().
import React, { useRef } from 'react';
//import Card from '.../...';
const App = () =>
{
const cardRef = useRef();
const onClickHandler = () =>
{
const position = cardRef.current.getBoundingClientRect();
//This returns an object with left, top, right, bottom, x, y, width, and height.
console.log(position)
}
return (
<div>
<Card cardRef={cardRef} onClickHandler={onClickHandler} />
</div>
);
}
export default App
const Card = ({ cardRef, onClickHandler }) => (
<div ref={cardRef} onClick={onClickHandler}>
<button > Click Me </button>
</div>
)
Upvotes: 2