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

Anyone figure out why this React code isn't working the way I intend?

What I'd like to do is use the performSearch function inside the NavMenu component passed to it as the prop "onSearch". This will then be invoked when a Navlink is clicked using the onClick method to send the query of either "dog, cat or sloth" back up to the function so that it can use it as a query for the API, which will then update the images state. I seem to be getting an error saying that the "onSearch" prop is undefined. Perhaps I'm just a little confused in the way I'm conducted things. Any help will be much appreciated.

class App extends Component {

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

  performSearch = (query) => {
    axios.get(`https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&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 onSearch = {this.performSearch} />

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

</BrowserRouter>
    );
  }
}
import React from "react";
import { NavLink } from "react-router-dom";




const NavMenu = props => {

 return(

    <nav className="main-nav">
      <ul>
        <li><NavLink to="/cats" onClick={this.props.onSearch("cats") }>Cats</NavLink></li>
        <li><NavLink to='/dogs'onClick={this.props.onSearch("dogs") }>Dogs</NavLink></li>
        <li><NavLink to='/sloths'onClick={this.props.onSearch("sloths") }>Sloths</NavLink></li>
      </ul>
    </nav>


 )
};

export default NavMenu

9 Answers

If you define NavMenu as a stateless component, you don't need to use this.props, just simply props. There is another issue when invoking the onSearch function in the NavMenu component, you have to pass a function to the onClick property and not call it.

    <nav className="main-nav">
      <ul>
        <li><NavLink to="/cats" onClick={()=>props.onSearch("cats") }>Cats</NavLink></li>
        <li><NavLink to='/dogs'onClick={()=>props.onSearch("dogs") }>Dogs</NavLink></li>
        <li><NavLink to='/sloths'onClick={()=>props.onSearch("sloths") }>Sloths</NavLink></li>
      </ul>
    </nav>
james south
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
james south
Front End Web Development Techdegree Graduate 33,271 Points

one thing i notice is that in NavMenu you are using this.props. this.props is only for class components, not functional components. the proper reference is just props.

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

Thank you so much both of you :) I realised after about adding props. Using a function to invoke the onClick didn't even cross my mind and it's what was really throwing me off.

I just hope I'm doing this coursework correctly - it's producing the desired results now (thank you!), but I'm not sure if they wanted it doing in a way that would load the Sloth component in when the URL said Sloth, or the Cat component in when the URL said Cat.

As it stands I just have the lowest level component as a "photo" component (not a cat component, dog component and sloth component) that changes depending on the button you press (either dog,cat or sloth), I'm not sure how I could create separate components for each button and then be able to use the url as a query to be passed up to the app component so it could use it in the api call to flickr. As it stands, I have the URL changing when the buttons are pressed, but it's a bit redundant , even if the URL didn't change it wouldn't matter, the api would still work. Any thoughts on this? My brain can't seem to see it any other way then how I've currently done it. Here's the rest of the components if you'd like to have a look (PhotoContainer and the Picture Component) :) :

import React from "react";
import PictureComponent from "./PictureComponent";
import {Route} from "react-router-dom";


const PhotoContainer = props => {

  const results = props.data;

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


 return(

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

   <Route exact path="/cats" render={() => Pictures} />
   <Route exact path= "/dogs" render={() => Pictures} />
   <Route exact path= "/sloths" render={() => Pictures} />
   </ul>

</div>
)};

export default PhotoContainer
import React from "react";

const PictureComponent = props => {

 return(


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

)
};

export default PictureComponent

You're right, it seems to be redundant this way, since you'll get the same set of pictures regardless of the url. A way to solve this could be to create separate components called Cats, Dogs and Sloths and calculate the url on the lowest level from the props.data property.

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

I was thinking of doing a "Animal" state and passing the API componentDidMount function the "this.state.animal" to use as the string to conduct the API search in the App component like this:

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);
  });
 }

Then Photocontainer the prop, like this:

render() {

    return (
<BrowserRouter>
  <div className="container">
    <NavMenu onSearch = {this.performSearch}  />

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

</BrowserRouter>
    );
  }
}

Then in photocontainer have the code structured like this:

const PhotoContainer = props => {

  const results = props.data;
  let typeOfAnimal = 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} imagequery={typeOfAnimal} />)

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

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


return(

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

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


   </ul>

I'm not sure though how I can update the state of "Animal" to = the endpoint of the URL (so if it's dog the animal state updates to dog and then the API function till use this and go search for images of dogs). I made a variable called "typeOfAnimals" which passes down the animal state as a prop. I then put this as a prop into each catcomponent, dog component and sloth component called "imagequery". I'm not sure though how to update the state inside of each of the dog, cat and sloth component so that the state then matches the URL or simply the word "dog","cat","sloth", I tried the setState function in each component but it seems to give me errors. Any help with this, I seem to be at my wits end haha.

Thanks a bunch :)

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

Essentially I really need to scrap this way of doing it

<nav className="main-nav">
      <ul>
        <li><NavLink to="/cats" onClick={() => props.onSearch("cats") } >Cats</NavLink></li>
        <li><NavLink to='/dogs'onClick={() => props.onSearch("dogs") }>Dogs</NavLink></li>
        <li><NavLink to='/sloths'onClick={() => props.onSearch("sloths") }>Sloths</NavLink></li>
      </ul>
    </nav>

As this provides results on the trigger of clicking and not of whether the URL is a certain somthing.

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

I need a way of updating the state from lower components, so that when the dogs li is clicked, the the dogs component is loaded and the URL becomes /dogs - it then tells the animal state that it should now equal "dogs". "dogs" is then passed into the API search and produces a bunch of dog images. The same must be done for cats and sloths, but for the life of me can't figure this one out :(

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

Is there anyway to change the state of something higher up in the chain from a low level component such as the dog or cat component changing the animal state to it's respective animal text?