-1

I have this script below that is working successfully. Also, you can see this script running in this link: https://codepen.io/claudio-bitar/pen/VERORW but I changed something that didn't work that you can see in the previous explanation below

class TodoApp extends React.Component {
  constructor() {
    super();
    this.state = {
      todos: {            
        "elements": ['a','b','c','d','e','f','g','h','i','j','k']          
  },

      currentPage: 1,
      todosPerPage: 3
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(event) {
    this.setState({
      currentPage: Number(event.target.id)
    });
  }

  render() {
    const { todos, currentPage, todosPerPage } = this.state;

    // Logic for displaying current todos
    const indexOfLastTodo = currentPage * todosPerPage;
    const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
    const currentTodos = todos.elements.slice(indexOfFirstTodo, indexOfLastTodo);

    const renderTodos = currentTodos.map((todo, index) => {
      return <li key={index}>{todo}</li>;
    });

    // Logic for displaying page numbers
    const pageNumbers = [];
    for (let i = 1; i <= Math.ceil(todos.elements.length / todosPerPage); i++) {
      pageNumbers.push(i);
    }

    const renderPageNumbers = pageNumbers.map(number => {
      return (
        <li
          key={number}
          id={number}
          onClick={this.handleClick}
        >
          {number}
        </li>
      );
    });

    return (
      <div>
        <ul>
          {renderTodos}
        </ul>
        <ul id="page-numbers">
          {renderPageNumbers}
        </ul>
      </div>
    );
  }
}


ReactDOM.render(
  <TodoApp />,
  document.getElementById('app')
);

So, I would like to do the same thing but using an object from DBJson replacing the todos' array. So I did that changes in the code below and it didn't work. There have appeared this error message:

TypeError: Cannot read property 'slice' of undefined

import React, { Component } from 'react'
import axios from 'axios'

const URL_TODOS = 'http://localhost:3001/todos';

    class Todos extends Component {
      constructor(props) {
        super(props);
        this.state = {    
          todos: [],        
          currentPage: 1,
          todosPerPage: 3
        };
        this.handleClick = this.handleClick.bind(this);
      }

      handleClick(event) {
        this.setState({
          currentPage: Number(event.target.id)
        });
      }



      componentDidMount() {
        axios.get(URL_TODOS)
          .then(res => {
            this.setState({ todos: res.data })
          })       
      }



      render() {
        const { todos, currentPage, todosPerPage } = this.state;

        // Logic for displaying current todos
        const indexOfLastTodo = currentPage * todosPerPage;
        const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
        const currentTodos = todos.elements.slice(indexOfFirstTodo, indexOfLastTodo); 


        const renderTodos = currentTodos.map((todo, index) => {
          return <li key={index}>{todo}</li>;
        });

        // Logic for displaying page numbers
        const pageNumbers = [];
        for (let i = 1; i <= Math.ceil(todos.elements.length / todosPerPage); i++) {
          pageNumbers.push(i);
        }

        const renderPageNumbers = pageNumbers.map(number => {
          return (
            <li
              key={number}
              id={number}
              onClick={this.handleClick}
            >
              {number}
            </li>
          );
        });

        return (
          <div>
            <ul>
              {renderTodos}
            </ul>
            <ul id="page-numbers">
              {renderPageNumbers}
            </ul>
          </div>
        );
      }
    }


    export default Todos 

Here is my DBJson file:

 "todos": [
    {
      "elements": [
        "a",
        "b",
        "c",
        "d",
        "e",
        "f",
        "g",
        "h",
        "i",
        "j",
        "k"
      ]
    }
  ]

I accept suggests to how do it more easilly too. Maybe change the json structure? I don't know.

claudiopb
  • 1,056
  • 1
  • 13
  • 25

3 Answers3

1

It seems that you are receiving an array, with an object, with an array, according to your DBJson file:

"todos": [
  {
    "elements": [
      "a",
      "b",
      "c",
      "d",
      "e",
      "f",
      "g",
      "h",
      "i",
      "j",
      "k"
    ]
  }
]

So, get the first element of todos:

todos[0]

With Async/Await

When using Axios, I always try to use async await, like this:

async componentDidMount() {
  try {
    let res = await axios.get(URL_TODOS);
    let response = await res.data;
    this.setState({ todos: response.todos[0] });
  } catch(error) {
    console.error(error);
  }
}

Before you set your data you have to wait for the promise to resolve.

Then, get the elements from that object.

const { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying current todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.elements.slice(indexOfFirstTodo, indexOfLastTodo);

Without Async/Await

If you don't want to use async/await:

componentDidMount() {
  axios.get(URL_TODOS)
    .then(response => response.data)
    .then(data => {
      this.setState({ todos: data[0] })
    })
    .catch(error => { console.error(error) });
}

More info and example in axios

In this answer I added some info and differences about fetch and axios, and there is some code on using axios that might help you if you are not getting the correct type of response. Have a look and see if it helps as well.

Why use componentDidMount

From the official docs:

componentDidMount() is invoked immediately after a component is mounted (inserted into the tree). Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.

This method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in componentWillUnmount().

You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state. Use this pattern with caution because it often causes performance issues. In most cases, you should be able to assign the initial state in the constructor() instead. It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position.

Why not use componentWillMount

From the official docs

UNSAFE_componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering. Generally, we recommend using the constructor() instead for initializing state.

Avoid introducing any side-effects or subscriptions in this method. For those use cases, use componentDidMount() instead.

c-chavez
  • 7,237
  • 5
  • 35
  • 49
  • it didn't work. is still showing the same error message: TypeError: Cannot read property 'slice' of undefined – claudiopb Oct 29 '18 at 20:55
  • @claudiobitar I updated the answer, you have to wait for your promise (`res.data`) to resolve before you can get your `todos`. Try it with this code, or try to use it without async/await, by doing an extra `then` after getting the first `res`. Async/await is always easier to read in my opinion. – c-chavez Oct 29 '18 at 21:01
  • Line 33: 'res' is not defined no-undef – claudiopb Oct 29 '18 at 21:05
  • 1
    @claudiobitar sorry, forgot this: `let res = await axios.get(URL_TODOS);` I updated the answer. – c-chavez Oct 30 '18 at 07:11
0

Your original render is trying to slice an empty array. Maybe do your fetch in componentWillMount instead, that way the array will be full.

componentWillMount() {
    axios.get(URL_TODOS)
      .then(response => response.json())
      .then(res => {
        this.setState({ todos: res.data[0] })
      })       
  }

something like that

molebox
  • 585
  • 1
  • 8
  • 17
  • Ive edited my answer and wrote it in my mobile so it might be a little wild on the indentation! :) – molebox Oct 29 '18 at 21:20
  • `response.json()` only works on `fetch` not in `axios`, have a look [here](https://stackoverflow.com/a/50326744/1042409). Also, don't use `componentWillMount`, use `componentDidmount`, have a read [here](https://reactjs.org/docs/react-component.html#unsafe_componentwillmount) – c-chavez Oct 30 '18 at 07:29
  • @c-chavez i didnt know that about axios, i dont use it actually. And about Will over Did you are totally correct and i realised a while after i wrote it. Didnt see the point in changing my answer. I guess i could edit it or just delete it but your comment should be enough to put people off taking that advice! :) – molebox Oct 30 '18 at 07:32
  • @molebox exactly, I think it's also ok to have these discussions, so that people who read this in the future know what's happening and don't make mistakes when implementing these functions. And about `axios`, use it, the advantages are great, and you might get into some situations like I did when the solution is just to change from `fetch` to `axios` because of how it's implemented. – c-chavez Oct 30 '18 at 07:35
0

I did this code change and now it works!

componentDidMount() {
    axios.get(URL_TODOS)
      .then(res => {
        this.setState({ todos: res.data[0].elements })
      })
  }

also

const currentTodos = todos.slice(indexOfFirstTodo, indexOfLastTodo);

and

 for (let i = 1; i <= Math.ceil(todos.length / todosPerPage); i++) {
claudiopb
  • 1,056
  • 1
  • 13
  • 25