Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Neil Bircumshaw
seal-mask
.a{fill-rule:evenodd;}techdegree
Neil Bircumshaw
Full Stack JavaScript Techdegree Student 14,597 Points

A question regarding State in React.

Are you able to change the State of something in a lower level component with setState() and how would this change get pushed back up to the upper most component that holds the constructor in which the State is declared in?

I'm trying to update the the State of "animal" in a lower level component, so that when a particular component is loaded, the value of "animal" is changed, for example if the "DogComponent" is loaded onto the page, the value of "animal" is equal to "dogs". This value is then substituted into the API call I am making to flickr - It will then produce dog photos. If the CatComponent is loaded onto the page (from clicking a nav link entitled "cats") then I'd like "animal" to become equal to the word "cats" - cat photos are then loaded onto the page.

My code for my separate components thus far looks like this (minus import and exports), I'm really not sure how to manipulate the "animal" state once passed down. The only way I could think of getting the code to work is by making 3 separate API componentDidMount calls and then binding these to the NavMenu with onClick events, but this would make the code look DRY, here is the code:

App component:

class App extends Component {



constructor() {
 super();
 this.state =
  { images: [],
    animal: []}
  }



  componentDidMount() {
    axios.get(`https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=
    ${this.state.animal}&per_page=15&format=json&nojsoncallback=1`)
   .then(response => {
   this.setState({
    images: response.data.photos.photo
    });
  })
  .catch(error => {
    console.log("Error fetching and parsing data", error);
  });
 }


render() {

    return (
<BrowserRouter>
  <div className="container">

    <NavMenu/>

    <PhotoContainer data = {this.state.images} Animal = {this.state.animal} />
    </div>

</BrowserRouter>
    );
  }
}

Photo container:

const PhotoContainer = props => {
const results = props.data;
const animal =props.Animal;


  let DogPictures = results.map(Picture =>
  <DogComponent url={`http://farm${Picture.farm}.staticflickr.com/${Picture.server}/${Picture.id}_${Picture.secret}.jpg`} key={Picture.id} />)

  let CatPictures = results.map(Picture =>
  <CatComponent url={`http://farm${Picture.farm}.staticflickr.com/${Picture.server}/${Picture.id}_${Picture.secret}.jpg`} key={Picture.id} />)

  let SlothPictures = results.map(Picture =>
  <SlothComponent url={`http://farm${Picture.farm}.staticflickr.com/${Picture.server}/${Picture.id}_${Picture.secret}.jpg`} key={Picture.id} />)


 return(

<div className = "photo-container">
  <ul>

   <Route  path="/cats" render={() => CatPictures} />
   <Route  path= "/dogs" render={() => DogPictures} />
   <Route  path= "/sloths" render={() => SlothPictures} />


   </ul>

</div>
)};

NavMenu component:

const NavMenu = props => {




 return(

    <nav className="main-nav">
      <ul>
        <li><NavLink to="/cats" >Cats</NavLink></li>
        <li><NavLink to='/dogs'>Dogs</NavLink></li>
        <li><NavLink to='/sloths'>Sloths</NavLink></li>
      </ul>
    </nav>


 )
};

Dog Component: (as well as a sloth and cat component, but these are the same currently, the animal State change would need to travel down to here to be different for each one ( i.e. animal = "dog" in this component when it loads) - but not sure how to implement this.)

const DogComponent = props => {
 return(


  <li>
    <img src={props.url} alt=""/>
  </li>

)
};

3 Answers

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

A parent component can pass data to a child component through props. A child component can make changes to a parent component by having the parent pass a callback function as a prop to the child, and then the child can call that function (closures!).

In this case, I'm not sure if that's necessary. Why not just have a DogPictures component that takes care of the axios request for getting its own dog pictures? Or even, maybe a generic AnimalPictures component that gets the animal name from the route match object and uses that in the request.

Neil Bircumshaw
seal-mask
.a{fill-rule:evenodd;}techdegree
Neil Bircumshaw
Full Stack JavaScript Techdegree Student 14,597 Points

The reason I can't have the dog requesting it's own pictures at it's level is because apparently it's best practice to have the highest level component make the requests and pass them down to their respective children.

I originally did just have an AnimalPictures component and scrapped the "dogsComponent", "CatsComponent" and "SlothComponent", but I wasn't sure how to extract the route like "/cats" and use that as the request object in the API all, how would I do that, could you possible show me? :)

Neil Bircumshaw
seal-mask
.a{fill-rule:evenodd;}techdegree
Neil Bircumshaw
Full Stack JavaScript Techdegree Student 14,597 Points

And how exactly would the callback function look as a prop to the child so that it can change the data? I never recall learning anything regarding closures, sorry to be a pain for asking, but this kind of thing helps me out a lot with understanding concepts :)

Neil Bircumshaw
seal-mask
.a{fill-rule:evenodd;}techdegree
Neil Bircumshaw
Full Stack JavaScript Techdegree Student 14,597 Points

So I've created a simple function that is passed down to the photocontainer like this:

animalType = (animal) => {
  this.setState({animal : animal});



<PhotoContainer data = {this.state.images} AnimalFunc = {this.animalType} />

This then goes from photo container into the respected dog,sloth and cat components like this:

const dog = props.animal;
dog("dog");


let animalType = props.AnimalFunc;

let DogPictures = results.map(Picture =>
  <DogComponent url={`http://farm${Picture.farm}.staticflickr.com/${Picture.server}/${Picture.id}_${Picture.secret}.jpg`} key={Picture.id} animal={animalType} />)

  let CatPictures = results.map(Picture =>
  <CatComponent url={`http://farm${Picture.farm}.staticflickr.com/${Picture.server}/${Picture.id}_${Picture.secret}.jpg`} key={Picture.id} animal={animalType} />)

  let SlothPictures = results.map(Picture =>
  <SlothComponent url={`http://farm${Picture.farm}.staticflickr.com/${Picture.server}/${Picture.id}_${Picture.secret}.jpg`} key={Picture.id}animal={animalType}/>)

It then goes to the dog,cat and sloth component and is called, which updates the animal state in the App component like this:

const DogComponent = props => {
const dog = props.animal;
dog("dog");

This is then called with the API like this, using the animal State as the paramter for it's search :

  componentWillMount() {
    axios.get(`https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=
   ${this.state.animal}&per_page=15&format=json&nojsoncallback=1`)
   .then(response => {
   this.setState({
    images: response.data.photos.photo
    });
  })
  .catch(error => {
    console.log("Error fetching and parsing data", error);





  });
 }

For some reason though, the state is updating as I can see on the developer React tools, however I get an error message come up after a few seconds saying "Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."

and then everything crashes. Where have I gone wrong haha

Neil Bircumshaw
seal-mask
.a{fill-rule:evenodd;}techdegree
Neil Bircumshaw
Full Stack JavaScript Techdegree Student 14,597 Points

https://github.com/Neilbircumshaw/gallery-app sure, thanks for taking a look, it's driving me nuts haha, I really want to get my head around working with React, I just find working with State and passing props a bit alien.

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

Okay this is what I think is happening: when you click a route, let's say I click on '/cats', it will try and render CatPictures list, by mapping each of the results to a CatComponent. But there aren't any results yet, which means this will be a list of 0 elements, and no code will get called informing the root level App component that there is a new animal and we need new results.

I understand that the idea is to have containers that take care of the data and lower level components that take care of rendering. I don't think that necessarily means pushing everything all the way up to the root level and then down again. You can have mid level containers. I think you should have a generic AnimalPictures container and a generic AnimalComponent since the styling is all the same, it's just the data that changes. Each route can render an AnimalPictures container, it can get information on what kind of animal it's looking for from props (https://teamtreehouse.com/library/using-the-match-object), this container can make an axios request that will fire when the component loads, when someone visits that route, and then render a list of AnimalComponent components for each item in the results. So in that case the AnimalPictures would be the smart component, and the AnimalComponent would be a dumb component.