Alex
Alex

Reputation: 151

Issue Cycling through Array

I'm trying to Cycle through my data I'm getting back from an API Call. If I use dummy data in an array, it will cycle fine. But once I attempt to place my api data in its place, it just shows all the items at once instead of cycling one at a time. I console.log(items.length) and get 1 which tells me that this is an array of an object and is just reading it as a whole. Could use some help getting this displaying one item at a time instead of the whole thing at once.

API Call(Title.js):

import { Component, React } from "react";
//import TitleCycle from '../components/TitleCycle';

let headers = {
  "QB-Realm-Hostname": "XXXXXXXXXX.quickbase.com",
  "User-Agent": "FileService_Integration_V2.1",
  "Authorization": "QB-USER-TOKEN XXXXXXX_XXXX_XXXXXXXX",
  "Content-Type": "application/json"
};

class Title extends Component {
    state = {
      data: null
    };
  
  componentDidMount() {
    this.fetchData();
  }

  fetchData = () => {
    let body = {"from":"bpz99ram7","select":[3,6,40],"where": "{40.CT. 'In Progress'}","sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}};

    fetch("https://api.quickbase.com/v1/records/query", {
      method: "POST",
      headers: headers,
      body: JSON.stringify(body)
    })
      .then((response) => response.json())
      .then((data) => this.setState({ data }));
  };

  render() {
    const { data } = this.state;
    if (data === null) return "Loading Job Data...";

    return (
      <div className="Title">
        {Object.keys(data["data"]).map(item => (
          <div key={item}>
            {data["data"][item][6].value}
          </div>
        ))}
      </div>
    );
  }
}

export default Title;

TitleCycle.js:

import React from "react";
import Title from "./header/Title";

const items = [
  // {name: "Job Name One"}, {name: "Job Name Two"}, {name: "Job Name Three"},
   <Title/>       //Problem is here because this is showing as an object of objects? Needs to be an array
];

console.log(items.length)
//console.log(items.length) this shows 1, meaning only one item with multiple objects...
export default function TitleCycle() {
  const [titleData, setTitleData] = React.useState(items[0]);
  const [index, setIndex] = React.useState(0);

  // Set Timer and Interval based on Index
  React.useEffect(() => {
    const timerId = setInterval(
      () => setIndex((i) => (i + 1) % items.length),
      2000 // 2 seconds.
    ); 
    return () => clearInterval(timerId);
  }, []);

  // Set TitleData with Index
  React.useEffect(() => {
    setTitleData(items[index]);
  }, [index]);

  return (
    <div className="TitleCycle">
      <h3>{titleData}</h3>
    </div>
  );
}
{
  "data": [
    {
      "3": {
        "value": 177
      },
      "6": {
        "value": "2220 Three Kings "
      },
      "40": {
        "value": "In Progress"
      }
    },
    {
      "3": {
        "value": 302
      },
      "6": {
        "value": "529 Woodsview"
      },
      "40": {
        "value": "In Progress"
      }
    },
    {
      "3": {
        "value": 9
      },
      "6": {
        "value": "7948 Red Tail"
      },
      "40": {
        "value": "In Progress"
      }
    }

Any help would be appreciated!

The issue is that it is reading as 1 item, but because my api is reading as an object of objects, instead of an array. I'm ultimately trying to send this data over to TitleCycle.js to cycle through and display each one in a duration at a time.

UPDATE: My issue seems to be how I'm attempting to send <Title /> from Title.js to TitleCycle.js to use the API Response as the items array in question.

I'm trying to do the API call in one file, send that array over to TitleCycle.js to cycle based on duration.

Upvotes: 1

Views: 102

Answers (2)

Drew Reese
Drew Reese

Reputation: 203466

If you've an array of objects then you need to pick out the object properties you want rendered.

For a given element object:

{
  "3": {
    "value": 177
  },
  "6": {
    "value": "2220 Three Kings "
  },
  "40": {
    "value": "In Progress"
  }
}

Select one of the "3"|"6"|"40" property's value property to render:

function TitleCycle() {
  const [titleData, setTitleData] = React.useState(items[0]);
  const [index, setIndex] = React.useState(0);

  // Set Timer and Interval based on Index
  React.useEffect(() => {
    const timerId = setInterval(
      () => setIndex((i) => (i + 1) % items.length),
      2000 // 2 seconds.
    ); 
    return () => clearInterval(timerId);
  }, []);

  // Set TitleData with Index
  React.useEffect(() => {
    setTitleData(items[index]);
  }, [index]);

  return (
    <div className="TitleCycle">
      <h3>{titleData["6"].value}</h3>
    </div>
  );
}

You could also save the extra state and render cycle by consuming the updated index instead.

function TitleCycle() {
  const [index, setIndex] = React.useState(0);

  // Set Timer and Interval based on Index
  React.useEffect(() => {
    const timerId = setInterval(
      () => setIndex((i) => (i + 1) % items.length),
      2000 // 2 seconds.
    ); 
    return () => clearInterval(timerId);
  }, []);

  return (
    <div className="TitleCycle">
      <h3>{items[index]["6"].value}</h3>
    </div>
  );
}

Demo

Edit issue-cycling-through-array

Update

It seems you need to render the TitleCycle component in Title instead of the divs with the titles.

class Title extends Component {
  state = {
    data: null,
  };
  
  componentDidMount() {
    this.fetchData();
  }

  fetchData = () => {
    let body = {"from":"bpz99ram7","select":[3,6,40],"where": "{40.CT. 'In Progress'}","sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}};

    fetch("https://api.quickbase.com/v1/records/query", {
      method: "POST",
      headers: headers,
      body: JSON.stringify(body)
    })
      .then((response) => response.json())
      .then(({ data }) => this.setState({ data })); // <-- destructure data from response
  };

  render() {
    const { data } = this.state;
    if (data === null) return "Loading Job Data...";

    return (
      <div className="Title">
        <Titlecycle data={data} /> // <-- pass data
      </div>
    );
  }
}

Access the passed data prop and render. Conditionally render the titles only if the data array has a truthy length (i.e. non-zero length).

function TitleCycle({ data }) { // <-- destructure data
  const [index, setIndex] = React.useState(0);

  // Set Timer and Interval based on Index
  React.useEffect(() => {
    const timerId = setInterval(
      () => setIndex((i) => (i + 1) % data.length),
      2000 // 2 seconds.
    ); 
    return () => clearInterval(timerId);
  }, [data]); // <-- handle if data array prop changes

  return data.length ? (
    <div className="TitleCycle">
      <h3>{data[index]["6"].value}</h3>
    </div>
  ) : null;
}

Demo

Edit issue-cycling-through-array (forked)

Upvotes: 1

sloont
sloont

Reputation: 204

I don't think you should or can pass the prop as key.

Try doing your Array.map with two parameters (item, index)

Then make the key={index} and pass the item as a prop to the actual component not a div:

       <TitleCycle item={item} ....

EDIT

Additionally you can destructure the props object that gets passed to TitleCycle:

export default const TitleCycle = ({ item }) => {

    //Now item === the individual top-level object for each component

}

Upvotes: 0

Related Questions