Is it possible to add ref to the props.children elements? - javascript

I have a Form and Input components, which are rendered as below.
<Form>
<Field />
<Field />
<Field />
</Form>
Form component will act as wrapper component here and Field component ref are not being set here. I want iterate through props.children in Form Component and want to assign a ref attribute to each children. Is there any possibility to achieve this?

You need Form to inject your refs with React.Children and React.cloneElement APIs:
const FunctionComponentForward = React.forwardRef((props, ref) => (
<div ref={ref}>Function Component Forward</div>
));
const Form = ({ children }) => {
const childrenRef = useRef([]);
useEffect(() => {
console.log("Form Children", childrenRef.current);
}, []);
return (
<>
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
ref: (ref) => (childrenRef.current[index] = ref)
})
)}
</>
);
};
const App = () => {
return (
<Form>
<div>Hello</div>
<FunctionComponentForward />
</Form>
);
};

You can map children create new instance of component based on it using one of two ways showed in React Docs.
With React.Children.map and React.cloneElement (this way, key and ref from original element are preserved)
Or only with React.Children.map (Only ref from original component is preserved)
function useRefs() {
const refs = useRef({});
const register = useCallback((refName) => ref => {
refs.current[refName] = ref;
}, []);
return [refs, register];
}
function WithoutCloneComponent({children, ...props}) {
const [refs, register] = useRefs();
return (
<Parent>
{React.Children.map((Child, index) => (
<Child.type
{...Child.props}
ref={register(`${field-${index}}`)}
/>
)}
</Parent>
)
}
function WithCloneComponent({children, ...props}) {
const [refs, register] = useRefs();
return (
<Parent>
{
React.Children.map((child, index) => React.cloneElement(
child,
{ ...child.props, ref: register(`field-${index}`) }
)
}
</Parent>
)
}

Related

React functional component child won't re-render when props change

I'm passing an array of objects to a child as props, and I wanted the child component to re-render when the aray changes, but it doesn't:
parent:
export default function App() {
const [items, setItems] = useState([]);
const buttonCallback = () => {
setItems([...items, { text: "new item" }]);
};
return (
<div className="App">
<h1>Items list should update when I add item</h1>
<button onClick={buttonCallback}>Add Item</button>
<Items />
</div>
);
}
child:
const Items = ({ itemlist = [] }) => {
useEffect(() => {
console.log("Items changed!"); // This gets called, so the props is changing
}, [itemlist]);
return (
<div className="items-column">
{itemlist?.length
? itemlist.map((item, i) => <Item key={i} text={item.text + i} />)
: "No items"}
<br />
{`Itemlist size: ${itemlist.length}`}
</div>
);
};
I found this question with the same issue, but it's for class components, so the solution doesn't apply to my case.
Sandbox demo
<Items propsname={data} />
const buttonCallback = () => {
setItems([...items, { text: "new item" }]);
};
but you should put it as:
const buttonCallback = () => {
setItems([...items, { text: "new item", id: Date.now() }]);
};
Because is not recommended to use index as a key for react children. Instead, you can use the actual date with that function. That is the key for React to know the children has changed.
itemlist.map((item) => <Item key={item.id} text={item.text} />)
Try below:
you are adding array as a dependency, to recognise change in variable, you should do deep copy or any other thing which will tell that useEffect obj is really different.
`
const Items = ({ itemlist = [] }) => {
const [someState,someState]=useState(itemlist);
useEffect(() => {
someState(itemlist)
}, [JSON.stringify(itemlist)]);
return (
<div className="items-column">
{someState?.length
? someState.map((item, i) => <Item key={i} text={item.text
+ i}
/>)
: "No items"}
<br />
{`Itemlist size: ${someState.length}`}
</div>
);
};

React JS pass the data or child component to parent component

Is it possible to pass the data from the child component to the parent component using props?
-Parent component
--- ItemList component.
--- DisplatSelect component from the itemList component
I have a list of item in the child component which came from to the parent component, then I want to send the index of the selected data to the other child component located in the parent component.
Can't example well, kindly see the attached screenshot for other references.
Thanks a lot!
enter image description here
You can keep the data in the Parent component and use a function to pass the props from the child to the Parent. This concept is called Lifting State Up where you define the state at the highest common ancestor so all the child components are using the same data which in this case is the selecetd item
function Parent() {
const [selectedItem, setSelectedItem] = useState(null);
const data = []; // Your Data
return (
<>
<h1>Your selected Item = {selectedItem}</h1>
{data.map((item) => {
<Child item={item} setSelectedItem={setSelectedItem} />;
})}
</>
);
}
function Child({ item, setSelectedItem }) {
return <Button onClick={() => setSelectedItem(item.id)}> {item} </Button>;
}
The simplest way, I think, is for the child component where the selection is made to accept a function properly, something like onSelectionChanged. If you had a button for each item passed to the child you could do something like:
Child Component A
const ChildA = ({ items, onSelectionChanged }) => {
return (
<div>
{items.map((item, index) => (
<button onClick={() => onSelectionChanged(index)}>Item</button>
))}
</div>
)
}
Child Component B
const ChildB = ({ selectedItem }) => {
return (
<div>
Selected {selectedItem}
</div>
)
}
Parent Component
const Parent = () => {
const [selection, sets election] = useState({});
const onSelectionChanged = index => {
console.log(`ChildA selection changed: ${index}`);
}
return (
<div>
<ChildA items={items} onSelectionChanged={onSelectionChanged} />
<ChildB selectedItem={selection} />
</div>
)
}
So when your child component handles a change in the selection, it invokes the function passed as a prop onSelectionChanged. You can pass whatever data you want from ChildA to that function.
Note that the parent Component keeps the selected value (from ChildA) in local state, then passes that value to ChildB via a prop.
You can have a state variable in the parent component and pass it to child components to share data between them. I'll post a sample code block on how you can do this for your case.
export default function ParentComponent (props) {
const data = ['image_1_url', 'image_2_url', ...] // Data for the images
const [selectedIndex, setSelectedIndex] = useState(-1); // Selected index (-1 represents no selection)
return (
<ImageList data={data} selectImage={setSelectedIndex} />
{(selectedIndex !== -1) ? (<SelectedImage data={data[selectedIndex]} />) : (<No ImageSelected/>)}
);
}
And the image list component can then use the selectImage prop to select the image
export default function ImageList (props) {
return (
<div>
props.data.map((imageUrl, index) => (
<div onClick={() => {props.setSelected(index)}}>
<img src={imageUrl}/>
</div>
))
</div>
);
}
Yes it's possible. We have one parent state value and update every on click child component to the component.
import React, { useState } from "react";
const Child1 = (props) => {
return (
props.items.map( (item, index) => (
<button key={index.toString()} onClick={() => { props.updateIndex(item.id) }}>
{item.name}
</button>
) )
)
}
const Child2 = (props) => {
return (
<h1>Item selected: {props.selectItem}</h1>
)
}
const ParentComponent = () => {
const listItems = [
{
id:1,
name: "sample name 1"
},
{
id:2,
name: "sample name 2"
}
]
const [selectItem, setSelectItem] = useState('None');
return (
<>
<Child1 items={listItems} updateIndex={setSelectItem}/>
<Child2 selectItem={selectItem}/>
</>
)
}
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}

How to pass HTML attributes to child component in React?

I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}

How to pass ref to children of children using array of refs React?

I'm trying to pass multiple times refs to children of children but it is not working. I have a functional component called AvatarBuilder that uses the Avatars component. This component renders a list of Avatar components. The idea is to have in AvatarBuilder references to each of the Avatar component.
Here is the code snippet summarized:
const AvatarBuilder = props => {
...
// in this dummy example i would have 5 avatars
const URLS=[1,2,3,4,5];
const refs = URLS.map(item => ({ ref: React.createRef() }));
return (
<>
<Avatars
ref={refs}
urlList={URLS}
/>
</>
);
const Avatars = React.forwardRef((props, ref) => {
let urlList = props.urlList.map((url, index) => {
return (
<Avatar
ref={ref[index].ref}
url={url}
/>
)
})
return (
<ul className="Avatars" >
{urlList}
</ul>
)
});
const Avatar = React.forwardRef((props, ref) => {
return (
<li className="Avatar">
<img
src={props.url}
ref={ref}
/>
</li>
)
});
I get the following warning and the refs array is not updated when all the components are mounted.
index.js:1 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 h (at Avatar.js:11)
in li (at Avatar.js:10)
in ForwardRef (at Avatars.js:10)
in ul (at Avatars.js:24)
in ForwardRef (at AvatarBuilder.js:189)
in AvatarBuilder (at App.js:19)
in div (at App.js:14)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
Any idea how should this be fixed? Thanks!
For a functional component, you must use useRef and not React.createRef since a new instance of refs will be created on in render.
If you use React.createRef, then make use of useMemo to memoize the refs
const AvatarBuilder = props => {
// in this dummy example i would have 5 avatars
const URLS=[1,2,3,4,5];
const refs = React.useMemo(() =>URLS.map(item => ({ ref: React.createRef() })), []); // create refs only once
React.useEffect(() => {
console.log(refs);
},[])
return (
<Avatars
ref={refs}
urlList={URLS}
/>
);
}
const Avatars = React.forwardRef((props, ref) => {
let urlList = props.urlList.map((url, index) => {
return (
<Avatar
ref={ref[index].ref}
url={url}
/>
)
})
return (
<ul className="Avatars" >
{urlList}
</ul>
)
});
const Avatar = React.forwardRef((props, ref) => {
return (
<li className="Avatar">
<img
src={props.url}
ref={ref}
/>
</li>
)
});
ReactDOM.render(<AvatarBuilder/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>
Try this one.
const AvatarBuilder = props => {
...
// in this dummy example i would have 5 avatars
const URLS=[1,2,3,4,5];
const refs = URLS.map(item => React.createRef());
return (
<>
<Avatars
refs={refs}
urlList={URLS}
/>
</>
);
// you don't need to make it with `fowardRef()`
const Avatars = (props) => {
const {refs} = props;
let urlList = props.urlList.map((url, index) => {
console.log(url, index, typeof (index), ref);
return (
<Avatar
ref={refs[index]}
url={url}
/>
)
})
return (
<ul className="Avatars" >
{urlList}
</ul>
)
};
const Avatar = React.forwardRef((props, ref) => {
return (
<li className="Avatar">
<img
src={props.url}
ref={ref}
/>
</li>
)
});

Ref from Dropdown.Button (antd)

How can I get a ref to a Dropdown.Button (it is antd), when I set a ref to it, it returns a wrapper ?
parent = null;
parentRef = node => (this.parent = node);
render(){
return (
<Dropdown.Button
ref={this.parentRef}
overlay={menu}
type="primary"
>
content
</Dropdown.Button>
)
}
You need to use reference API, useRef or React.createRef.
Refer to Refs and the DOM in React:
class App extends React.Component {
buttonRef = React.createRef();
componentDidMount() {
console.log(this.buttonRef);
}
render() {
return (
<Dropdown.Button ref={this.buttonRef} overlay={menu} type="primary">
content
</Dropdown.Button>
);
}
}
With hooks:
const App = () => {
const buttonRef = useRef();
useEffect(() => {
console.log(buttonRef);
}, []);
return (
<FlexBox>
<Dropdown.Button ref={buttonRef} menu={menu}>
drop down
</Dropdown.Button>
</FlexBox>
);
};
Hei,
Check out the React.forwardRef() here.
It's a function which you can wrap your 'child' component and it's specifically designed to make possible parent to have a reference to its children. Should solve your problem.
Cheers!
you can by passing a cb function on the ref attrib:
parent = null;
parentRef = ref => (this.parent = ref);
render(){
return (
<Dropdown.Button
ref={parentRef}
overlay={menu}
type="primary"
>
content
</Dropdown.Button>
)
}
Or with useRef hook:
for functional components no classes
const parentRef =React.useRef(null);
return (
<Dropdown.Button
ref={parentRef}
overlay={menu}
type="primary"
>
content
</Dropdown.Button>
)

Categories

Resources