Search code examples
javascriptreactjsfunctionrendering

React - api data is being successfully passed to component(per inspector) but not being rendered


My api data is being successfully passed from my api call into the table component but is not being rendered.

If after searching for a playlist I go in and make an edit to the table.js file the data will render correctly. App.js...

const App = (props) => {
  const [playlists, setPlaylists] = useState([])
  const [searchString, setSearchString] = useState() //use instead of onsubmit 
  const isFirstRef = useRef(true);
  
  const search = (value) => {
    setSearchString(value)
  }

  useEffect(
    () => {
      if (isFirstRef.current) {
      isFirstRef.current = false;
      return;
      }
      let spotlist = Spotify.playlistsearch(searchString)
      let tablelist = []
      spotlist.then(val =>{
        val.forEach(element =>{
          tablelist.push(
            { 
              name: element.description,
              track_count: element.tracks.total,
            })
        } 
      )})
      setPlaylists(tablelist)
      }, [searchString] );

  return (
    <div className="App">
      <Searchform search={search}/>
      <Table playlists={playlists}/>
    </div>
  )
};

The playlists prop is being shown as present under the PlayListTable component inspector but is not rendering. I am able to get the data to render IF I edit the file after seeing the data present in the component inspector. Table.js

import React from 'react'
import { Icon, Label, Menu, Table } from 'semantic-ui-react'

const PlayListTable = ({ playlists }) => {
  return(
    <Table celled>
      <Table.Header>
        <Table.Row>
          <Table.HeaderCell>Playlist</Table.HeaderCell>
          <Table.HeaderCell>Track Count</Table.HeaderCell>
        </Table.Row>
      </Table.Header>
      <Table.Body>
       {playlists.map( (playlist) => {
          return (
              <Table.Row>
                <Table.Cell>
                  {playlist.name}
                </Table.Cell>
                <Table.Cell>
                  {playlist.track_count}
                </Table.Cell>
              </Table.Row>
              )
            }
          )
        }
      </Table.Body>
      <Table.Footer>
        <Table.Row>
          <Table.HeaderCell colSpan='3'>
            <Menu floated='right' pagination>
              <Menu.Item as='a' icon>
                <Icon name='chevron left' />
              </Menu.Item>
              <Menu.Item as='a'>1</Menu.Item>
              <Menu.Item as='a'>2</Menu.Item>
              <Menu.Item as='a'>3</Menu.Item>
              <Menu.Item as='a'>4</Menu.Item>
              <Menu.Item as='a' icon>
                <Icon name='chevron right' />
              </Menu.Item>
            </Menu>
          </Table.HeaderCell>
        </Table.Row>
      </Table.Footer>
    </Table>
  )
}

export default PlayListTable

Solution

  • Issue

    You may be receiving correct data but you don't update your state at the correct time. spotlist returns a Promise that you are chaining from, but the setPlaylists(tablelist) call is enqueued before the then block of the promise chain is processed. The useEffect callback is 100% synchronous code.

    let spotlist = Spotify.playlistsearch(searchString);
    let tablelist = [];
    spotlist.then(val => { // <-- (1) then callback is placed in the event queue
      val.forEach(element => { // <-- (3) callback is processed, tablelist updated
        tablelist.push({ 
          name: element.description,
          track_count: element.tracks.total,
        });
      } 
    )});
    setPlaylists(tablelist); // <-- (2) state update enqueued, tablelist = []
    

    Solution - Place state update in Promise chain

    You can forEach into a temp array, but mapping the response data to state values is the more "React" way of handling it. It is also more succinct.

    Spotify.playlistsearch(searchString)
      .then(val => {
        setPlaylists(val.map(element => ({
          name: element.description,
          track_count: element.tracks.total,
        })));
      } 
    )});