Home > Enterprise >  React : how to pass a state from child to parent component
React : how to pass a state from child to parent component

Time:09-06

I'd like to pass filteredData in the child Component PokemonSearchBar.jsx so that I can use it in the parent component PokemonList.jsx to map only filtered Pokemon. In other words, when I do a search, the search component will compare the characters from the e.target.value of my input to the ones of the data array (filled with all the Pokemon names), which is passed as a prop from the parent component PokemonList.jsx. If there are some characters in common between the e.target.value and the data array, the filteredData will receive only the Pokemon with characters in common with e.target.value from the input.

Is there any way that I can pass the filteredData state to the parent component as a prop ?

Here is the child component PokemonSearchBar.jsx:

import React, { useState } from 'react';
import { Link } from 'react-router-dom';

import './PokemonSearchBar.css';

import SearchIcon from '@mui/icons-material/Search';
import CloseIcon from '@mui/icons-material/Close';

export default function PokemonSearchBar({ placeholder, data }) {
    const [filteredData, setFilteredData] = useState([]);
    const [wordEntered, setWordEntered] = useState("");

    const handleFilter = (e) => {
        const searchWord = e.target.value;
        setWordEntered(searchWord);
        const newFilter = data.filter(value => {
            return (value.name.toLowerCase().includes(searchWord.toLowerCase()));
        });
        
        if(searchWord === "") {
            setFilteredData([]);
        } else {
            setFilteredData(newFilter);
        }
    }

    const clearInput = (e) => {
        setWordEntered("");
        setFilteredData([]);
    }

  return (
    <div className='search'>
        <div className="row" style={{ display: 'flex', justifyContent: 'center' }}>
            <div className="col-3"></div>
            <div className="col-6">
                <div className="searchInputs" style={{ display: 'flex', justifyContent: 'center'}}>
                    <input type="text" className="form-control rounded" placeholder={placeholder} onChange={handleFilter} value={wordEntered} />
                    {filteredData.length === 0 ? <button type="button" className="btn btn-primary"><SearchIcon /></button> : <button type="button" className="btn btn-warning"><CloseIcon onClick={clearInput} /></button>}
                </div>
            </div>
            <div className="col-3"></div>
        </div>
        {filteredData.length !== 0 &&
            <div className="dataResult mx-auto" style={{ display: 'flex', justifyContent: 'center' }}>
                <ul>
                    {filteredData.slice(0, 15).map((pokemon) => {
                        return (<li style={{ listStyleType: 'none', textDecoration: 'none'}} className="dataItem"><Link to="/" className="a" style={{ textDecoration: 'none', color: 'black' }}>{pokemon.name}</Link></li>)
                    })}
                </ul>
            </div>
        }
    </div>
  )
}

Here is the parent component PokemonList.jsx:

import React, { Component } from 'react'
import axios from 'axios'
import ReactPaginate from 'react-paginate';

import PokemonCard from './PokemonCard';
import PokemonSearchBar from './PokemonSearchBar';


export default class PokemonList extends Component {
    state = {
        url: "https://pokeapi.co/api/v2/pokemon?limit=100000&offset=0",
        pokemon: [], // on créé un objet (qui va devenir un tableau) pokemon null c'est où on va enregistrer le json
        pageNumber: 0,
        pokemonsPerPage: 16
    };

    async componentDidMount() {
        const res = await axios.get(this.state.url); // on attend de récupérer les pokemon avant d'exectuer la suite de la fonction
        console.log(res.data['results']);
        this.setState({pokemon: res.data['results']}); // on passe un objet dans le state et on affecte à pokemon le tableau résultats de la data de l'url
        // setState re-render seulement les éléments qu'il faut re-rendre
    };

    render() {
        console.log(this.state.pokemon);

        const pagesVisited = this.state.pageNumber * this.state.pokemonsPerPage;

        const displayUsers = this.state.pokemon
            .slice(pagesVisited, pagesVisited   this.state.pokemonsPerPage)
            .map((pokemon) => {
                return (
                    <PokemonCard 
                        key={pokemon.name} // on utilise le props name comme clé unique car chaque nom de pokemon est unique
                        name={pokemon.name} // c'est le props name dans le pokemonCard
                        url={pokemon.url} // c'est le props url dans le pokemonCard
                    /> // on génère autant de cartes qu'il y a de pokemons dans le tableau results
                )
            });

        const pageCount = Math.ceil(this.state.pokemon.length / this.state.pokemonsPerPage); // ceil() permet d'arrondir au supérieur

        const changePage = ({selected}) => {
            this.setState({pageNumber: selected})
        }

        return (
            <React.Fragment>
                <PokemonSearchBar  data={this.state.pokemon} placeholder="Enter a Pokemon name" />
                <br />
                {this.state.pokemon ? ( // s'il y a quelquechose dans le state
                    <div className='row'>
                        {displayUsers}
                        <div style={{ display: 'flex', justifyContent: 'center'}}>
                            <ReactPaginate 
                                previousLabel={"previous"}
                                nextLabel={"Next"}
                                pageCount={pageCount}
                                onPageChange={changePage}
                                containerClassName={"paginationButtons"}
                                previousLinkClassName={"previousButton"}
                                nextLinkClassName={"nextButton"}
                                disabledClassName={"paginationDisabled"}
                                activeClassName={"paginationActive"}
                            />
                        </div>
                    </div>
                ) : (<h2>Loading Pokemon</h2>)} 
                {/* S'il n'y a rien dans le state afficher que le pokemon est en train de charger */}
            </React.Fragment>
        )
    }
}

CodePudding user response:

In React there are essentially 3 ways to send data up:

  1. Via callback passed as a prop;
  2. Via Store (Redux or Context API, or other)
  3. Via native custom Javascript events

Your use-case can be implemented via prop:

import React, { useState } from 'react';
import { Link } from 'react-router-dom';

import './PokemonSearchBar.css';

import SearchIcon from '@mui/icons-material/Search';
import CloseIcon from '@mui/icons-material/Close';

export default function PokemonSearchBar({ placeholder, data, onDataFiltered }) {
    const [filteredData, setFilteredData] = useState([]);
    const [wordEntered, setWordEntered] = useState("");

    const handleFilter = (e) => {
        const searchWord = e.target.value;
        setWordEntered(searchWord);
        const newFilter = data.filter(value => {
            return (value.name.toLowerCase().includes(searchWord.toLowerCase()));
        });
        
        if(searchWord === "") {
            setFilteredData([]);
            onDataFiltered([])
        } else {
            setFilteredData(newFilter);
            onDataFiltered(newFilter)
        }
    }

    const clearInput = (e) => {
        setWordEntered("");
        setFilteredData([]);
    }

  return (
    <div className='search'>
        <div className="row" style={{ display: 'flex', justifyContent: 'center' }}>
            <div className="col-3"></div>
            <div className="col-6">
                <div className="searchInputs" style={{ display: 'flex', justifyContent: 'center'}}>
                    <input type="text" className="form-control rounded" placeholder={placeholder} onChange={handleFilter} value={wordEntered} />
                    {filteredData.length === 0 ? <button type="button" className="btn btn-primary"><SearchIcon /></button> : <button type="button" className="btn btn-warning"><CloseIcon onClick={clearInput} /></button>}
                </div>
            </div>
            <div className="col-3"></div>
        </div>
        {filteredData.length !== 0 &&
            <div className="dataResult mx-auto" style={{ display: 'flex', justifyContent: 'center' }}>
                <ul>
                    {filteredData.slice(0, 15).map((pokemon) => {
                        return (<li style={{ listStyleType: 'none', textDecoration: 'none'}} className="dataItem"><Link to="/" className="a" style={{ textDecoration: 'none', color: 'black' }}>{pokemon.name}</Link></li>)
                    })}
                </ul>
            </div>
        }
    </div>
  )
}

You will then declare it like this:

 <PokemonSearchBar
    data={this.state.pokemon}
    placeholder="Enter a Pokemon name"
    onDataFiltered={(newData) => {
       console.log("do something with the new data", newData);
    }}
/>

  • Related