ericb ericb - 28 days ago 13
React JSX Question

map through nested children components in react

I'm trying to add a new property to a nested child element in a top level component.

My higher level component is called Photo.js

import React, {Component} from 'react';
import PropTypes from 'prop-types';

export default class Photo extends Component{

constructor(props){
super(props);
this.onHandleLoad = this.onHandleLoad.bind(this);

this.state = {
isLoaded:false
}
}

onHandleLoad(event){
this.setState({
isLoaded:true
})
}

render(){
const {children} = this.props;
return(
<div className="photo-container">
{children}
</div>
)
}
}


Here is how I add children to it.

<Photo >
<Link to='/bla' className="slide-show-link">
<img src={photo.photo} alt={`${photo.title} photo`}/>
</Link>
</Photo>


Its not always the same children added. Sometimes it might be more levels to the nesting but it always has an img element.
I want a way to be able to map through all the nested elements and find the img element and add custom props to it.

The prop I want to add is onLoad={this.onHandleLoad) so it will call the onHandleLoad inside Photo with the right scope.

I've looked at React.Children.map but that only returns the first element and not the nested elements. img could be 1 level down or more.
How can I do this. was looking into recursion mapping but not sure thats the way to go. Is there a built in method that handles this ?

Answer Source

The best way to do this might be to use something like redux and dispatch an action for every image load. But it would require a lot of refactoring if you are not currently using it.

There is another way that fits here pretty well which is context. There are many warnings about using context, but since you're only providing a callback there isn't very much risk.

Context will allow your parent component to pass down the callback any number of levels without having to pass props through each one.

-

First add childContextTypes and getChildContext to your parent component:

export default class Photo extends Component {
  static childContextTypes = {
    onHandleLoad: PropTypes.func
  };

  constructor(props) {
    super(props);

    this.state = {
      isLoaded: false
    }
  }

  getChildContext() {
    return {
      onHandleLoad: this.onHandleLoad
    };
  }

  onHandleLoad = (event) => {
    this.setState({
      isLoaded: true
    })
  }

  render() {
    const {children} = this.props;
    return(
      <div className="photo-container">
        {children}
      </div>
    )
  }
}

Then you will need a child component wrapper around your <img>s to hook into the parent context by using contextTypes:

class PhotoImage extends Component {
  static contextTypes = {
    onHandleLoad: PropTypes.func
  }

  static propTypes = {
    photo: PropTypes.string,
    title: PropTypes.string
  }

  render() {
    return (
      <img
        src={photo}
        alt={`${title} photo`}
        onLoad={this.context.onHandleLoad}
      />
    )
  }
}