how to get the height of the children from a parent? - React - reactjs

Sorry for the question.
I am new to react.
I want to get the height of each child then apply the maximum one on all of them.
by the way i do it, i get always the height of the last child.
on other hand i don't know exactly how to force the maximum height on all the children.
i really appreciate the help.
here is my code :
for the parent component :
export default function Parent({data, index}) {
const [testHeight, setTestHeight] = useState([]);
const listRef = useRef(null);
useEffect (() => {
setTestHeight(listRef.current.clientHeight)
})
const {
objects: blocs
} = data
return (
<>
{blocs && Object.values(blocs).map((itemsBlocks, i) => (
<ItemChild dataItem={itemsBlocks}
ref={listRef}
maxHeight= { testHeight}
/>
))}
</>
)
}
for the child component :
const Child = forwardRef(function Child ({dataItem, maxHeight}, ref) {
useEffect(() => {
console.log(ref.current.clientHeight);
})
const {
description,
title
} = dataItem || {}
return (
<>
<div className="class_child" ref={ref} >
{maxHeight}
<p> {title} </p>
<p> {description} </p>
</div>
</>
)
});
export default Child

You have multiple issues.
First off, you are storing the height, from the parent, and, you seem to be giving the same ref to all of your children ?
Each component should have their own ref.
If you want to get all height, you need to change this :
useEffect (() => {
setTestHeight(listRef.current.clientHeight)
})
You are replacing the value of your testHeight unstead of adding values to the array.
You should instead, do this :
useEffect (() => {
setTestHeight((current ) => [...current, listRef.current.clientHeight])
})
I advise you to look how to update hooks by using function, as in my reply, to avoid a lot of later trouble you might meet :)
But, in my opinion, it would be better to update the hook from the children, I'm not sure if you need ref for anything, since you are new to react, I would believe you do not. If you don't need it, then, pass the setTestHeight to children, update it how I showed your, and then you will get all your height, and from the parent, by seeing when your array is full, you will be able to determine your maxheight (that you should store in another hook) and then, update all your children max height
I'm also not sure why your are using forwardref though.

Related

Need to calculate on the parent the result of a hook call on each subcomponent

I would love getting some help on this one, I think I am getting there, but I am not sure about it and need some guidance.
I have a parent component, which renders multiple subcomponents, and on each of those subcomponents, I get a result from a hook that do a lot of calculations and other multiple hook calls.
This hook only accepts and a single entity, not arrays, and I cannot afford to modify everything in order to accept arrays in them.
So let's say my parent component is
const Parent = () => {
const cardsArray = [...]
return (
<Wrapper>
{cardsArray.map(
card => <CardComponent cardId={cardId} />
)}
</Wrapper>
)}
and my subComponent :
const CardComponent = ({cardId}) => {
const result = useCalculation(cardId)
return (
<div>My Calculation Result: {result}</div>
)}
Now my issue is this: I need to sum up all those results and show them in my Parent Component. What would be my best way to achieve this?
I thought about having an update function in my parent and pass it as a prop to my subcomponents, but, I am getting the problem that when the Card Subcomponent gets the result from the hook, calls the function and updates the parent state, although it works, I get an error on the console saying that I am performing a state update while rendering:
Cannot update a component (Parent) while rendering a different component (CardComponent). To locate the bad setState() call inside CardComponent, follow the stack trace as described in https://github.com/facebook/react/issues/18178#issuecomment-595846312
I feel like the answer must not be hard but I am not seeing it
thanks a lot
I made some assumptions about your implementation but i think it will cover your needs.
Your thought about having an updater function on the parent element and pass it to it's children sounds pretty good and that's the basic idea of my proposed solution.
So let's start with the Parent component:
const Parent = () => {
const cardsArray = [
{ cardId: 1 },
{ cardId: 2 },
{ cardId: 3 },
{ cardId: 4 }
];
const [sum, setSum] = useState(0);
const addToSum = useCallback(
(result) => {
setSum((prev) => prev + result);
},
[setSum]
);
return (
<div>
{cardsArray.map(({ cardId }) => (
<CardComponent key={cardId} cardId={cardId} addToSum={addToSum} />
))}
<strong>{sum}</strong>
</div>
);
};
I named your updater function addToSum assuming it aggregates and sums the results of the children elements. This function has 2 key characteristics.
It's memoized with a useCallback hook, otherwise it would end up in an update loop since it would be a new object (function) on every render triggering children to update.
It uses callback syntax for updating, in order to make sure it always uses the latest sum.
Then the code of your child CardComponent (along with a naive implementation of useCalculation) would be:
const useCalculation = (id) => {
return { sum: id ** 10 };
};
const CardComponent = memo(({ cardId, addToSum }) => {
const result = useCalculation(cardId);
useEffect(() => {
addToSum(result.sum);
}, [result, addToSum]);
return <div>My Calculation Result: {JSON.stringify(result)}</div>;
});
The key characteristics here are:
the updater function runs on an effect only when result changes (effect dependency).
the addToSum dependency is there to make sure it will always run the correct updater function
it is a memoized component (using memo), since it has expensive calculations and you only want it to update when it's props change.
I assumed that useCalculation returns an object. If it returned a primitive value then things could be a little simpler but this code should work for every case.
You can find a working example in this codesandbox.
Create a state in the parent (sum in the example), and update it from the children in a useEffect block, which happens after rendering is completed:
const { useEffect, useState } = React
const useCalculation = cardId => cardId * 3
const CardComponent = ({ cardId, update }) => {
const result = useCalculation(cardId)
useEffect(() => {
update(result)
}, [result])
return (
<div>My Calculation Result: {result}</div>
)
}
const Parent = ({ cardsArray }) => {
const [sum, setSum] = useState(0);
const updateSum = n => setSum(s => s + n)
return (
<div>
{cardsArray.map(
cardId => <CardComponent key={cardId} cardId={cardId} update={updateSum} />
)}
sum: {sum}
</div>
)
}
ReactDOM.render(
<Parent cardsArray={[1, 2, 3]} />,
root
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>

React - Alternatives to triggering rerender of specific components after child mounting finishes

This is more of a conceptual question than anything else.
I'm trying to draw a SVG line between two elements in my application. The way I'm currently doing this is by having a top level ref, inside of which I store a ref for each of the child elements indexed by an arbitrary key. I then draw the arrows, deduced from a 2 dimensional array of pairs of these keys, look up the ref, and use the positioning of those elements to create the SVG's coordinates.
The problem with this method is, besides initial render, every render update afterwards uses outdated ref data as the children get rendered (and therefore positioned) after the parent, inside which the SVG layer is contained.
I've already thought of the obvious answers, useEffects, setStates, etc., but there doesn't seem to be a good solution here that I can think of. A lot of the obvious answers don't necessarily work in this case since they cause render loops. Another solution that I initially thought of was breaking down the arrow rendering to within each of the children, but this had problems of its own that initially caused the transition to having all of the refs live at the parent level.
My current solution is to key the SVG Layer component to a state variable, and then change the value of this variable in a useEffect once the final item renders, but this solution feels messy and improper.
Not exact but here's some code of the issue
Parent Component:
export default ({ items }) => {
const [svgKey, setSvgKey] = useState(0);
const pairs = items.reduce((arr, item) => {
for (const subItem of item.subItems) {
arr.push([subItem, item]);
}
}, [])
const itemRefs = useRef({});
return (
<>
{items.map(item => <Row items={items} setSvgKey={setSvgKey} item={item} refs={refs} key={item.key} />}
<SVGLayer key={svgKey} pairs={pairs} refs={refs} />
</>
);
}
SVG Layer
export default ({ pairs, refs }) => (
<svg>
{pairs.map(([a, b], i) => (
<Arrow key={i} a={refs.current[a.key]} b={refs.current[b.key]} />
)}
</svg>
);
Arrow
export default ({ a, b }) => <path d={[math for line coords here]} />;
Row
export default ({ refs, item, items, setSvgKey }) => {
useEffect(() => {
if (item.key === items[items.length - 1].key) {
setSvgKey(key => ++key);
}
});
return (
<div ref={el => (refs.current[item.key] = el)} />
);
}

I want only one component state to be true between multiple components

I am calling components as folloews
{userAddresses.map((useraddress, index) => {
return (
<div key={index}>
<Address useraddress={useraddress} />
</div>
);
})}
Their state:
const [showEditAddress, setShowEditAddress] = useState(false);
and this is how I am handling their states
const switchEditAddress = () => {
if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
}
};
Well, it's better if you want to toggle between true and false to use the state inside useEffect hook in react.
useEffect will render the component every time and will get into your condition to set the state true or false.
In your case, you can try the following:
useEffect(() => { if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
} }, [showEditAddress])
By using useEffect you will be able to reset the boolean as your condition.
Also find the link below to react more about useEffect.
https://reactjs.org/docs/hooks-effect.html
It would be best in my opinion to keep your point of truth in the parent component and you need to figure out what the point of truth should be. If you only want one component to be editing at a time then I would just identify the address you want to edit in the parent component and go from there. It would be best if you gave each address a unique id but you can use the index as well. You could do something like the following:
UserAddress Component
const UserAddress = ({index, editIndex, setEditIndex, userAddress}) => {
return(
<div>
{userAddress}
<button onClick={() => setEditIndex(index)}>Edit</button>
{editIndex === index && <div style={{color: 'green'}}>Your editing {userAddress}</div>}
</div>
)
}
Parent Component
const UserAddresses = () => {
const addresses = ['120 n 10th st', '650 s 41 st', '4456 Birch ave']
const [editIndex, setEditIndex] = useState(null)
return userAddresses.map((userAddress, index) => <UserAddress key={index} index={index} editIndex={editIndex} setEditIndex={setEditIndex} userAddress={userAddress}/>;
}
Since you didn't post the actual components I can only give you example components but this should give you an idea of how to achieve what you want.

Conditionally assign ref in react

I'm working on something in react and have encountered a challenge I'm not being able to solve myself. I've searched here and others places and I found topics with similar titles but didn't have anything to do with the problem I'm having, so here we go:
So I have an array which will be mapped into React, components, normally like so:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr
return (<>
{arr.map((item, id) => {<ChildComponent props={item} key={id}>})}
</>)
}
but the thing is, there's a state in the parent element which stores the id of one of the ChildComponents that is currently selected (I'm doing this by setting up a context and setting this state inside the ChildComponent), and then the problem is that I have to reference a node inside of the ChildComponent which is currently selected. I can forward a ref no problem, but I also want to assign the ref only on the currently selected ChildComponent, I would like to do this:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and there's a state which holds the id of a selected ChildComponent called selectedObjectId
const selectedRef = createRef();
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={selectedObjectId == id ? selectedRef : null}
>
})}
<someContextProvider />
</>)
}
But I have tried and we can't do that. So how can dynamically assign the ref to only one particular element of an array if a certain condition is true?
You can use the props spread operator {...props} to pass a conditional ref by building the props object first. E.g.
export default ParentComponent = () => {
const selectedRef = useRef(null);
return (
<SomeContextProvider>
{arr.map((item, id) => {
const itemProps = selectedObjectId == id ? { ref: selectedRef } : {};
return (
<ChildComponent
props={item}
key={id}
{...itemProps}
/>
);
})}
<SomeContextProvider />
)
}
You cannot dynamically assign ref, but you can store all of them, and access by id
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and theres a state wich holds the id of a selected ChildComponent called selectedObjectId
let refs = {}
// example of accessing current selected ref
const handleClick = () => {
if (refs[selectedObjectId])
refs[selectedObjectId].current.click() // call some method
}
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={refs[id]}
>
})}
<someContextProvider />
</>)
}
Solution
Like Drew commented in Medets answer, the only solution is to create an array of refs and access the desired one by simply matching the index of the ChildElement with the index of the ref array, as we can see here. There's no way we found to actually move a ref between objects, but performance cost for doing this should not be relevant.

React hooks - Dispatching a reset state from parent component

This is my application with the scenario reproduced, here the demo in codesandbox
I have two components, Leagues ( parent ) and Details ( Child ).
I have a implemented reset button example in the Details Component button which does
const cleanArray = () => {
setDataHomeTeam([]);
};
<button onClick={cleanValue} type="button">Reset</button>
You can see in the demo that is emptying out an array of a team stat
My question is, can i implement the same button but out from Details component and from the parent component Leagues for example? Whats the way to achieve it?
I thought to go this way but i can not get it done.
So in my Details.js
let Details = forwardRef(({ ....
const cleanArray = () => {
setDataHomeTeam([]);
};
useImperativeHandle(ref, () => {
return {
cleanValue: cleanValue
}
});
in App.js
<Leagues/>
<button onClick={cleanValue} type="button">Reset</button>
<Details ref={ref} />
I get this error : 'cleanValue' is not defined no-undef
is it something that i can not do with react? How can i achieve it?
I think your approach sounds correct except for lacking the way of calling the api cleanValue you exposed. Basically you have to call it via a ref you pass to it as following:
function App() {
const ref = useRef();
return (
<>
<Leagues />
{/* call it via ref */}
<button onClick={() => ref.current.cleanValue()} type="button">Reset</button>
<Details ref={ref} />
</>
)
}
Codesandbox link: https://codesandbox.io/s/nifty-raman-c0zff?file=/src/components/Details.js
I don't completely understand what you are trying to do, but here is a solution I think is going to work for your problem
let's say you wanna filter that array with the selected team which is liverpool, first if you have control over the incoming data I recommend changing the obj in the array likethis
{day : 16 , teamName:"liverpool"}, this is going to help you filter that array later,
then you useEffect & useState to update that array
[teams, setTeams] = useState([]);
// the array changes here {day: 1 , teamName : "sao paulo"} , {day:2 ,teamname:"liverpool"}]
useEffect(()=>{
setTeams((T)=>{
return T.filter((oneTeam)=>{
return oneTeam.teamName == selectedTeam;
});
})
},[teams,selectedTeam]);

Resources