mttetc
mttetc

Reputation: 745

How to pass variables when calling components

I'm building a mini app and I want to get it cleaner. So basically I want to have 3 components : App, List and Person.

Here is the code : App.js

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

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      results: [],
      search: '',
      currentPage: 1,
      todosPerPage: 3
    };
    this.handleClick = this.handleClick.bind(this);
  }

  componentWillMount() {
    this.fetchData();
  }

  fetchData = async () => {
    const response = await fetch(API);
    const json = await response.json();
    this.setState({ results: json.results });
  };

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

  updateSearch(event) {
    this.setState({ search: event.target.value.substr(0, 20) });
  }

  render() {
    return (
      <List />
    );
  }
}

export default App;

List.js

import React, { Component } from 'react';
import Person from './Person';

class List extends Component {
    render() {
        const { results, currentPage, todosPerPage } = this.state;
    const indexOfLastTodo = currentPage * todosPerPage;
    const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
    const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
      return item.name.toLowerCase().indexOf(this.state.search) !== -1;
    });

    const renderTodos = currentTodos.map((item, index) => {
      return (
        <Person item={this.state.item} index={this.state.index}/>
      );
    });

    const pageNumbers = [];
    for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
      pageNumbers.push(i);
    }

    const renderPageNumbers = pageNumbers.map(number => {
      return (
        <li className="page-link" key={number} id={number} onClick={this.handleClick} style={{cursor: "pointer"}}>{number}</li>
      );
    });

    return (
      <div className="flex-grow-1">
        <h1>Personnages de Star Wars</h1>
        <form className="mb-4">
          <div className="form-group">
            <label>Rechercher</label>
            <input 
            className="form-control" 
            type="text"
            placeholder="luke skywalker..."
            value={this.state.search}
            onChange={this.updateSearch.bind(this)}
            />
          </div>
        </form>
        <div className="row mb-5">{renderTodos}</div>
        <nav aria-label="Navigation">
          <ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
        </nav>
      </div>
    );
  }
}

export default List;

Person.js

import React, { Component } from 'react';

function Person(item, index) {
    return (
        <div className="col-lg-4 mb-4" key={index}>
      <div className="card">
        <div className="card-header">
          <h4 className="mb-0">{item.name}</h4>
        </div>
        <div className="card-body">
          <h5 className="card-title">Caractéristiques</h5>
          <ul>
            <li>Année de naissance : {item.birth_year}</li>
            <li>Taille : {item.height} cm</li>
            <li>Masse : {item.mass}</li>
            <li>Couleur des yeux : {item.eye_color}</li>
            <li>Couleur de cheveux : {item.hair_color}</li>
            <li>Couleur de peau : {item.skin_color}</li>
          </ul>
          <a href={item.url} className="btn btn-primary">Sa fiche</a>
        </div>
      </div>
    </div>
    )
}

export default Person;

My issue is that I get TypeError: Cannot read property 'results' of null when rendering.

Is it possible to have variable go into every file if I define them all in App.js ?

Upvotes: 0

Views: 68

Answers (2)

snaksa
snaksa

Reputation: 577

You are not passing the data the correct way. Try this:

In App.js pass to List component the needed data:

render() {
    return (
      <List data={this.state}/>
    );
  }

Then in render() method in List.js get the passed data prop, then extract the data from there:

render() {
     const { data } = this.props;
     const { results, search, currentPage, todosPerPage } = data;
     // ...
     // in currentTodos function dont use this.state.search but just "search", that we got above from the data variable
     // ...
     // also your renderTodos should look like this - use the item and index variables
     const renderTodos = currentTodos.map((item, index) => {
      return (
        <Person item={item} index={index}/>
      );
    });

    // ...
}

So your List.js should look like this:

import React, { Component } from 'react';
import Person from './Person';

class List extends Component {
    render() {
    // get the data
    const { data } = this.props;
    // get the properties
    const { results, search, currentPage, todosPerPage } = data;
    const indexOfLastTodo = currentPage * todosPerPage;
    const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
    const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
      // use "search" variable
      return item.name.toLowerCase().indexOf(search) !== -1;
    });

    const renderTodos = currentTodos.map((item, index) => {
      return (
        // use item and index
        <Person item={item} index={index}/>
      );
    });

    const pageNumbers = [];
    for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
      pageNumbers.push(i);
    }

    const renderPageNumbers = pageNumbers.map(number => {
      return (
        <li className="page-link" key={number} id={number} onClick={this.handleClick} style={{cursor: "pointer"}}>{number}</li>
      );
    });

    return (
      <div className="flex-grow-1">
        <h1>Personnages de Star Wars</h1>
        <form className="mb-4">
          <div className="form-group">
            <label>Rechercher</label>
            <input 
            className="form-control" 
            type="text"
            placeholder="luke skywalker..."
            value={search} // use search variable here too
            onChange={this.updateSearch.bind(this)}
            />
          </div>
        </form>
        <div className="row mb-5">{renderTodos}</div>
        <nav aria-label="Navigation">
          <ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
        </nav>
      </div>
    );
  }
}

export default List;

And your function in Person.js should have the following declaration, because the parameters are extracted from the passed props:

function Person({item, index}) {
     // ...
}

Upvotes: 1

Rishabh Rawat
Rishabh Rawat

Reputation: 859

You can use pass variables in your props of <List /> component by passing state inside render function of App.js while calling <List /> like this

render() {
  //Passing Data inside props
  <List data={this.state} />
}

and inside your List.js, You can access the data variable

const { results, currentPage, todosPerPage } = this.props.data;

Upvotes: 1

Related Questions