0

I'm new to React/using API json data in a project so I'm having a little trouble. I've created a function where a user can type in a search query and a list of devices associated with their query will show up. These device names are fetched from an API. I'm trying to make it so that when the plus sign next to a device is clicked, it adds this device to a new array that is then displayed to the screen.

I'm not 100% familiar with the concept of state in React and I think that's where my issue is (in the addDevice function). It's partially working, where I click the device and it displays at the bottom, but when I click another device, instead of adding to the list, it just replaces the first device.

class App extends React.Component {
  state = {
    search: "",
    devices: [],
    bag: []
  };

  addDevice = (e, data) => {
    console.log(data);
    const newData = [this.state.devices.title];
    this.setState({
      bag: newData.concat(data)
    });
  };

  onChange = e => {
    const { value } = e.target;
    this.setState({
      search: value
    });

    this.search(value);
  };

  search = search => {
    const url = `https://www.ifixit.com/api/2.0/suggest/${search}?doctypes=device`;

    fetch(url)
      .then(results => results.json())
      .then(data => {
        this.setState({ devices: data.results });
      });
  };

  componentDidMount() {
    this.search("");
  }

  render() {
    return (
      <div>
        <form>
          <input
            type="text"
            placeholder="Search for devices..."
            onChange={this.onChange}
          />
          {this.state.devices.map(device => (
            <ul key={device.title}>
              <p>
                {device.title}{" "}
                <i
                  className="fas fa-plus"
                  style={{ cursor: "pointer", color: "green" }}
                  onClick={e => this.addDevice(e, device.title)}
                />
              </p>
            </ul>
          ))}
        </form>
        <p>{this.state.bag}</p>
      </div>
    );
  }
}

I want it to display all the devices I click one after another, but right now each device clicked just replaces the previous one clicked

2 Answers 2

2

I think you're close. It appears that you are getting the devices array and the bag array mixed up.

I'd suggest using Array.from to create a copy of your state array. Then push the new item into the array. Concat is used to merged two arrays.

addDevice = (e, data) => {
  // create new copy of array from state
  const newArray = Array.from(this.state.bag);

  // push new item into array
  newArray.push(data);

  // update the state with the new array
  this.setState({
    bag: newArray
  });
}

Then if you want to show the device titles as a comma separated string, you could just do:

<p>{this.state.bag.join(', ')}</p>

Hope this helps.

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

3 Comments

Hey thanks so much! When I do it this way, is there a way to put each newly added device on it's own line? I only ask because eventually I want to be able to create a delete button where each device will have a red X the user can click on to remove from the list.
That's basically just formatting when you lay out your page in JSX. You could do something like: this.state.bag.map((device, index) => <p>{device}<button onClick={() => this.remove(index)>remove</button></p>); Then your remove function handler would use the array splice function to remove the element at that index. Check out this page: reactjs.org/docs/lists-and-keys.html
Another really good resource for React: egghead.io/courses/the-beginner-s-guide-to-react ... if you're interested! :)
0

The issue is with your addDevice method and specifically with how you create newData. You set newData to [this.state.devices.title], which evaluates to [undefined] since this.state.devices is an array and therefore has no attribute called title. Therefore, the updated value of state.bag will be [undefined, data], and only render as data which is the title of the most recently clicked device.

I think what you mean to do here is append the title of the clicked device to the array state.bag. You can do this with an addDevice method like this:

addDevice = (e, data) => {
  console.log(data);
  const newBag = this.state.bag.concat(data);
  this.setState({
    bag: newBag
  });
};

Though a better practice way of updating state.bag would make use of the functional form of setState, and the spread operator (...) is more common for this sort of stuff than using concat. Also renaming data to something more explanatory (like deviceTitle) would be helpful here. Example:

addDevice = (e, deviceTitle) => {
  this.setState(prevState => ({
    bag: [...prevState.bag, deviceTitle],
  });
}

Edit:

If you want to add functionality to remove devices from state.bag, you can create a method called removeDevice and add a button next to each bag item when rendering.

For example:

removeDevice = (e, deviceTitle) => {
  this.setState(prevState => ({
    bag: prevState.bag.filter(d => d !== deviceTitle),
  });
}

Then in your render method you would have something like this:

<ul>
  {this.state.bag.map(deviceTitle => (
    <li>
      <span>{ deviceTitle }</span>
      <button onClick={ e => this.removeDevice(e, deviceTitle) }>remove</button>
    </li>
  ))}
</ul>

3 Comments

Thank you for the response! I noticed both of these methods are adding the devices to the same line, is it possible to do it this way but have each device on it's own line? I'd like to add a delete button next to each device so that when clicked on, it will remove that device from the array.
@Emily I made an edit to the answer to explain how removing items from the bag could be done. Let me know if that helps! In the future, you might also consider putting the whole device or the id in the bag array depending on what you want to do with the bag.
@Emily the only thing I can think of based on the code I posted is if you put a == or === instead of !== in the filter callback function. Also I realized I made a mistake in that function when I wrote d.title !== deviceTitle, it should instead be d !== deviceTitle since d will already be a device's title since it is an element of state.bag.

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.