0

I'm trying to use Last.fm's API to find similar songs given a song name. Last.fm has a feature that can do this called track.getSimilar, but both "track" and "artist" are required parameters. However, because of the way this works, I can't figure out a way to either a) get both a song name, and the artist from one search bar input, or b) get the song's artist using track.search. Here's the part of my code relating to this:

const API_KEY = (my key);
const DEFAULT_TRACK = 'humble.'
const DEFAULT_ARTIST = 'kendrick lamar'
const RESULT_LIMIT = 5;
const RESULT_PAGE = 1;
const API_GET_SIMILAR_URL = `http://ws.audioscrobbler.com/2.0/?method=track.getSimilar&limit=${RESULT_LIMIT}&format=json&api_key=${API_KEY}`

const initialState = {
  loading: false,
  tracks: [],
  errorMessage: null
}

const reducer = (state, action) => {
  switch (action.type) {
    case "SEARCH_REQUEST":
      return {
        ...state,
        loading: true,
        errorMessage: null
      };
    case "SEARCH_SUCCESS":
      return {
        ...state,
        loading: false,
        tracks: action.payload
      };
    case "SEARCH_FAILURE":
      return {
        ...state,
        loading: false,
        errorMessage: action.error
      };
    default:
      return state;
  }
}

useEffect(() => {
  fetch(`${API_GET_SIMILAR_URL}&page=${RESULT_PAGE}&artist=${DEFAULT_ARTIST}&track=${DEFAULT_TRACK}`)
    .then(response => response.json())
    .then(jsonResponse => {
      const tracks = jsonResponse.similartracks[Object.keys(jsonResponse.similartracks)[0]];
      dispatch({
        type: "SEARCH_SUCCESS",
        payload: tracks
      });
    });
}, []);

const search = (searchValue) => {
  dispatch({
    type: "SEARCH_REQUEST"
  });
  fetch(`${API_GET_SIMILAR_URL}&page=${RESULT_PAGE}&track=${searchValue}`)
    .then(response => response.json())
    .then(jsonResponse => {
      if (!jsonResponse.error) {
        const tracks = jsonResponse.similartracks[Object.keys(jsonResponse.similartracks)[0]];
        dispatch({
          type: "SEARCH_SUCCESS",
          payload: tracks
        });
      } else {
        dispatch({
          type: "SEARCH_FAILURE",
          error: jsonResponse.error
        });
      }
    });
};

let {
  tracks,
  errorMessage,
  loading
} = state;
console.log(state);

In my search function I'm getting an Uncaught (in promise) TypeError: Cannot convert undefined or null to object error in the Object.keys line because I'm not using the artist parameter when calling the API. I tried using track.search to get the artist but I'm not sure where/how to incorporate it into my code.

I appreciate any help or advice. Thanks.

Tyson
  • 3
  • 2

1 Answers1

0

I think your issue may be lack of URL encoding. The reason I think this is because your artist string has a space in it which would need to be encoded to kendrick%20lamar to be processed properly.

Encoding is mentioned on the API's introduction page. UTF-8 is common, so you can use a function like encodeURI() to achieve this (MDN Web Docs).

One way to test this theory would be to use the values from the example URL in the track.getSimilar docs which is artist cher and track believe. These values conveniently don't have spaces or special characters that need escaping. By knowing the expected response it's also a good functional baseline to ensure the rest of your code is functioning as intended.

It's also a good song, make sure to give it a listen :)

Blake Gearin
  • 175
  • 1
  • 10
  • This isn't quite the issue, as it functions properly with the default values. I'm pretty sure my issue is in my search function, as the track.getSimilar function needs a track and an artist parameter, but I can only pass in a track parameter. I have no way of getting the artist parameter – Tyson Feb 19 '22 at 04:10
  • Ok, I think I understand what you mean. The default values are the `DEFAULT_ARTIST` and `DEFAULT_TRACK` variables, and the call to retrieve that track returns the appropriate response. What is calling `search` and what is being passed in as the `searchValue`? You can add `console.dir(searchValue)` to `search` to print it out. – Blake Gearin Feb 19 '22 at 04:40
  • I have a search bar that passes in the value for `searchValue`, and a button that calls `search` when pressed. – Tyson Feb 19 '22 at 04:48
  • Ok, so what you probably need is some input rules/validation and string parsing. For example, decide on a separator like `const separator = " | ";` where the user is expected to input a search string in this format: `{track name} | {artist name}`. Then use a line like `const [track, artist] = searchValue.split(separator);` to separate the track and artist from the input string into variables. Then pass those variables into your fetch call: `&track=${track}&artist=${artist}` – Blake Gearin Feb 19 '22 at 06:19
  • This is a pretty smart idea! Thanks for the help! – Tyson Feb 19 '22 at 07:29
  • Yeah no problem. As far as validation goes it might be easier to change your form to be two text boxes, one for each variable. But that will require some reworking. – Blake Gearin Feb 19 '22 at 21:37
  • Yeah, I tried doing it that way this morning but couldn't pass both variables into my search function. If I tried passing them in as `search = (track, artist) => {function}` for example it would only take the first value. The second variable passed would always come back as undefined if I console logged it. Just left it as is for now because at least it's functional – Tyson Feb 19 '22 at 22:27