Vinnaren i pepparkakshustävlingen!
2023-08-31, 17:23
  #1
Medlem
bithaxs avatar
Jag har två komponenter

Kod:
export const MenuListComposition = ({ title, children }) => 
  ... state och massa saker ...
   {children}

export const MenuListItem = ({parent, children}) => {
  return (
    <MenuItem onClick={parent.handleClose}>{children}</MenuItem>
  );
}

Det är tänkt att användas såhär

Kod:
<MenuListComposition title="Game">
      <MenuListItem>Create</MenuListItem> 
      <MenuListItem>Show all</MenuListItem> 
 </MenuListComposition>


Problemet är att jag vill kalla på handleClose från MenuListItem, men hur får jag ner en referens till
parenten till MenuListItem?

Har googlat runt, men de flesta lösningar verkar handla om att MenuListItem ska ligga hårdkodad innuti MenuListComposition och ha en ref som argument. Men det suger ju lite om man vill kunna återanvända saker tänker jag.

Men det kanske inte går att göra på dethär sättet?
__________________
Senast redigerad av bithax 2023-08-31 kl. 17:29.
Citera
2023-08-31, 17:42
  #2
Medlem
Jag kan tänka mig ett par olika lösningar, vad som passar beror lite på tycke och smak, hur flexibel du är med hur du strukturerar dina komponenter och hur resten av din app ser ut.

En variant är att du använder React.cloneElement när du renderar children inuti MenuListComposition, och skicka in handleClose som en prop:
Kod:
import { Children, cloneElement } from 'react' 
export const MenuListComposition = ({ title, children }) => {
    const handleClose = () => { ... }
    return Children.map(children, (child, index) =>
        cloneElement(child, {
          handleClose,
        })
      )
}

Ett annat alternativ är att bygga om din MenuListComposition så att den renderar sina children via en funktion du tar in som en prop:
Kod:
export const MenuListComposition = ({ title, items, renderItem }) => {
    const handleClose = () => { ... }
    return (
        <>
            {items.map(() => renderItem(item, handleClose))}
        </>
    )
}

<MenuListComposition 
    title="Game"
    items={['Create', 'Show all']},
    renderItem={(item, handleClose) => <MenuListItem onClick={handleClose}>{item}</MenuListItem>}
/>
__________________
Senast redigerad av yardline 2023-08-31 kl. 17:54.
Citera
2023-08-31, 18:05
  #3
Medlem
bithaxs avatar
Citat:
Ursprungligen postat av yardline
Jag kan tänka mig ett par olika lösningar, vad som passar beror lite på tycke och smak, hur flexibel du är med hur du strukturerar dina komponenter och hur resten av din app ser ut...

Tack!
Det funkade med

Kod:
 
{React.Children.map(children, child => {
    return React.cloneElement(child, {handleClose})
})}
(ej null där eftersom man vill ha kvar ursprungliga children för varje child).
Citera
2023-08-31, 18:17
  #4
Medlem
Citat:
Ursprungligen postat av bithax
Tack!
Det funkade med

Kod:
 
{React.Children.map(children, child => {
    return React.cloneElement(child, {handleClose})
})}
(ej null där eftersom man vill ha kvar ursprungliga children för varje child).

Den lösningen har minst påverkan på din existerande kod, men jag kan passa på att nämna att jag själv föredrar den andra lösningen i de allra flesta fallen. Även i dokumentationen för cloneElement säger de att den endast bör användas i undantagsfall.

En uppenbar nackdel med cloneElement-varianten är att du obfuskerar vad som händer i koden. Det är inte uppenbart utifrån denna koden...
Kod:
<MenuListComposition>
    <MenuListItem>Foo</MenuListItem>
</MenuListComposition>
... att dina komponenter kommer att klonas och få props injicerade. Det syns inte ens att handleClose-funktionen existerar utan att man börjar gräva i komponenterna.
Citera
2023-08-31, 21:30
  #5
Medlem
bithaxs avatar
Citat:
Ursprungligen postat av yardline
Den lösningen har minst påverkan på din existerande kod, men jag kan passa på att nämna att jag själv föredrar den andra lösningen i de allra flesta fallen. Även i dokumentationen för cloneElement säger de att den endast bör användas i undantagsfall.

En uppenbar nackdel med cloneElement-varianten är att du obfuskerar vad som händer i koden. Det är inte uppenbart utifrån denna koden...
Kod:
<MenuListComposition>
    <MenuListItem>Foo</MenuListItem>
</MenuListComposition>
... att dina komponenter kommer att klonas och få props injicerade. Det syns inte ens att handleClose-funktionen existerar utan att man börjar gräva i komponenterna.

Hur brukar man göra annars? Man gör olika komponenter för olika menyer och hårdkodar alla element?

Jag skulle kunna tänka mig att man skulle kunna skicka in mån array med elementen också, men det känns lite fulare.
Citera
2023-08-31, 22:03
  #6
Medlem
Citat:
Ursprungligen postat av bithax
Hur brukar man göra annars? Man gör olika komponenter för olika menyer och hårdkodar alla element?

Jag skulle kunna tänka mig att man skulle kunna skicka in mån array med elementen också, men det känns lite fulare.

I det andra exemplet jag visade så hårdkodar du inte vilken komponent som används för varje listelement eftersom du definierar funktionen renderItem i samband med att renderar listkomponenten:
Kod:
export const MenuListComposition = ({ title, items, renderItem }) => {
    const handleClose = () => { ... }
    return (
        <>
            {items.map((item) => renderItem(item, handleClose))}
        </>
    )
}

<MenuListComposition 
    title="Game"
    items={['Create', 'Show all']}
    renderItem={(item, handleClose) => (<MenuListItem onClick={handleClose}>{item}</MenuListItem>)}
/>

Det finns andra fördelar med detta. T ex så separerar du datan från presentationen på ett tydligare sätt. Din array med items kan utökas eller krympa, hämtas in från en annan källa, sorteras m.m. utan att du behöver ändra någonting i presentationskoden.

Detta är en väldigt vanlig metod för att rendera t ex listor och tabeller i React
__________________
Senast redigerad av yardline 2023-08-31 kl. 22:11.
Citera
2023-08-31, 22:28
  #7
Medlem
bithaxs avatar
Citat:
Ursprungligen postat av yardline
I det andra exemplet jag visade så hårdkodar du inte vilken komponent som används för varje listelement eftersom du definierar funktionen renderItem i samband med att renderar listkomponenten:
Kod:
export const MenuListComposition = ({ title, items, renderItem }) => {
    const handleClose = () => { ... }
    return (
        <>
            {items.map((item) => renderItem(item, handleClose))}
        </>
    )
}

<MenuListComposition 
    title="Game"
    items={['Create', 'Show all']}
    renderItem={(item, handleClose) => (<MenuListItem onClick={handleClose}>{item}</MenuListItem>)}
/>

Det finns andra fördelar med detta. T ex så separerar du datan från presentationen på ett tydligare sätt. Din array med items kan utökas eller krympa, hämtas in från en annan källa, sorteras m.m. utan att du behöver ändra någonting i presentationskoden.

Detta är en väldigt vanlig metod för att rendera t ex listor och tabeller i React

Okej. Jag ger det ett försök! Tack för hjälpen.
Citera
2023-09-01, 16:24
  #8
Moderator
Protons avatar
Citat:
Ursprungligen postat av bithax
Jag har två komponenter

Kod:
export const MenuListComposition = ({ title, children }) => 
  ... state och massa saker ...
   {children}

export const MenuListItem = ({parent, children}) => {
  return (
    <MenuItem onClick={parent.handleClose}>{children}</MenuItem>
  );
}

Det är tänkt att användas såhär

Kod:
<MenuListComposition title="Game">
      <MenuListItem>Create</MenuListItem> 
      <MenuListItem>Show all</MenuListItem> 
 </MenuListComposition>


Problemet är att jag vill kalla på handleClose från MenuListItem, men hur får jag ner en referens till
parenten till MenuListItem?

Har googlat runt, men de flesta lösningar verkar handla om att MenuListItem ska ligga hårdkodad innuti MenuListComposition och ha en ref som argument. Men det suger ju lite om man vill kunna återanvända saker tänker jag.

Men det kanske inte går att göra på dethär sättet?
Varför inte ha en funktion i din parent som du skickar in som en prop i din child? Hade väl vart det absolut enklaste?
Citera
2023-09-04, 18:51
  #9
Medlem
bithaxs avatar
Citat:
Ursprungligen postat av Proton
Varför inte ha en funktion i din parent som du skickar in som en prop i din child? Hade väl vart det absolut enklaste?

Ja man kan göra så och göra en loop i stället om man är ok med att skicka in allt som en lista i props i stället.

Det var för att tänkte göra lite html liknande syntax som det blev krångligt.
Citera
2023-09-04, 21:12
  #10
Medlem
Om du vill kalla på parent från children gör du en callback funktion. Alternativt en hook eller använder dig av React.useContext.

Med context behöver du inte skicka props kors och tvärs neråt.

Kolla Reacts dokumentation
Citera
2023-09-05, 10:05
  #11
Medlem
Citat:
Ursprungligen postat av bithax
Jag har två komponenter

Kod:
export const MenuListComposition = ({ title, children }) => 
  ... state och massa saker ...
   {children}

export const MenuListItem = ({parent, children}) => {
  return (
    <MenuItem onClick={parent.handleClose}>{children}</MenuItem>
  );
}

Det är tänkt att användas såhär

Kod:
<MenuListComposition title="Game">
      <MenuListItem>Create</MenuListItem> 
      <MenuListItem>Show all</MenuListItem> 
 </MenuListComposition>


Problemet är att jag vill kalla på handleClose från MenuListItem, men hur får jag ner en referens till
parenten till MenuListItem?

Har googlat runt, men de flesta lösningar verkar handla om att MenuListItem ska ligga hårdkodad innuti MenuListComposition och ha en ref som argument. Men det suger ju lite om man vill kunna återanvända saker tänker jag.

Men det kanske inte går att göra på dethär sättet?

Varför exakt ska du skicka en referens till föräldraelementet istället för att skicka en referens till closefunktionen? Se https://stackoverflow.com/questions/...-prop-in-react
Citera
2023-09-06, 00:52
  #12
Medlem
bithaxs avatar
Citat:
Ursprungligen postat av IngetNick2
Varför exakt ska du skicka en referens till föräldraelementet istället för att skicka en referens till closefunktionen? Se https://stackoverflow.com/questions/...-prop-in-react

Nej det spelar ingen roll. Jag bara tänker objektorienterat att parenten har en metod, och inte ”jag skickar runt en massa callbacks” pga att jag normalt jobbar i C#.

Detta med javascript är bara nått man måste dras med ibland för att folk inte vill använda blazor pga av religion.
__________________
Senast redigerad av bithax 2023-09-06 kl. 00:56.
Citera

Stöd Flashback

Flashback finansieras genom donationer från våra medlemmar och besökare. Det är med hjälp av dig vi kan fortsätta erbjuda en fri samhällsdebatt. Tack för ditt stöd!

Stöd Flashback