3

There's an array of react components inside parent component. On some event on parent component I'd like to run through the array of child components and call a method for each. How can I achieve that? It seems it doesn't work as in vanilla JS where you have an instance for the class and simply call a method. So here's the simplified code:

const Item = props => {
  const value = () => 'result!';
  return (
        <div></div>
  )
}
 
const App = props => {
  const items = props.items.map(item => <Item key={uuid()} item={item} />)
  const run = e => {
    items.map(item => console.log(item.value()))
  }
  return (
    <button onClick={run} type="button">Run!</button>
  ) 
}

const items = [
  'Lorem ipsum dolor sit amet',
  'consectetur adipiscing elit'
];

ReactDOM.render(<App items={items} />, document.querySelector("#app"))

I've created a jsfiddle with this code - https://jsfiddle.net/jkLq26pg/37/ On the 11th string the method value() is called and it raises an error. But logging item.props works and outputs props for each item. Why methods are not called? Please provide me with an advice how to make it work.

3
  • You are trying to call value like it's a class method item.value(), which it isn't. You're writing plain functions so value is only available within your Item function. Commented Jul 11, 2020 at 14:35
  • You could use a ref: reactjs.org/docs/…. But it's recommended to avoid this whenever possible. So a better question here is why you'd want that and if there's a way to avoid it and solve the problems with props. If you want the value, the child should accept an onChange prop to communicate the changed value, and the parent then keeps the state of values. Commented Jul 11, 2020 at 14:40
  • items will contains list of RENDERED ITEM component. Commented Jul 11, 2020 at 14:43

2 Answers 2

3

In your case you have to map thru the array and attach a ref using callback ref. Render the child component using forwardRef and use the useImperativeHandle hook to expose your function to the parent.

Working demo

Code Snippet

const Item = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    value: () => "result!"
  }));

  return <div ref={ref} />;
});

const itemsz = ["Lorem ipsum dolor sit amet", "consectetur adipiscing elit"];
const App = props => {
  const ref = useRef({});

  const items = itemsz.map((item, index) => {
    return (
      <Item
        ref={r => (ref.current[index] = r)}
        key={index}
        item={item}
      />
    );
  });
  const run = e => {
    items.map((item, index) => console.log(ref.current[index].value()));
  };
  return (
    <>
      <h3>click run and see console log</h3>
      <button onClick={run} type="button">
        Run!
      </button>
      {items}
    </>
  );
};

Note - Try avoiding this kind of pattern.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the response. I think that using refs adds complexity to the code. I wonder why it's implemented that way. Anyway I've got an advice to move state from child components and store it in parent. Seems weird but much more understandable. I'm going to try this approach first.
0

One way to solve this problem is to write useEffect and call your item.value() logic inside that useEffect. Your function will then be automatically called once your list has mounted to the DOM. That is probably the right place to call your function because your list would have rendered inside the DOM and side effects can then be handled.

const Item = props => {
  const value = () => 'result!';

  // Handle your side effects here
  useEffect(() => {
    value();
  }, []);

  return (
        <div></div>
  )
}

This useEffect will work after a component will mount into DOM Node. You can always put your function in one of the lifecycle methods in class based components or hooks in functional components and call them from there according to your situation.

2 Comments

Thank you, but It won't work since I need to run child component's method after it is already mounter and rendered. And it could be run even several times on parent event.
If you want it handled on a button click, then you can toggle a state on button click, and pass that state as a prop to children, and add it as a dependancy to useEffect. Or use componentDidUpdate to compare state change in child component and trigger the function after that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.