David Jay
David Jay

Reputation: 353

How to update a function in react when firing off onChange event

I have a function that filters the customers based on their levels (intermediate, beginner ), I'm passing this function through a component that has React select to filter my Data(async)

The filter is working only when I filter the first time but when I choose another value to filter it gave me a blank page?

I tried useEffect to keep it updated but it not working

Do you have any suggestions?

//APP.js

import React,{useState, useEffect} from "react";
import YogaCourses from "./components/YogaCourses/YogaCourses";
import Loading from "./components/IsLoading/Loading";
import LevelsFilter from './components/LevelsFilter/LevelsFilter';

//API to fetch the data
const url = 'https://gist.githubusercontent.com/Tayarthouail/8fb14fe117fdd718ceabd6ee05ed4525/raw/8c86c4bb89fc51667ba0578b2dcba14a0b21f08c/Yoga-courses-api.json';

function App() {
  
  //states
  const [yogaCourses, setYogaCourses] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [levels, setLevels] = useState([]);


  //Filter by Levels 
  const filterLevels = (level) => {
    const getLevels = yogaCourses.filter((singleLevel)=> singleLevel.level === level.value);
    setYogaCourses(getLevels);
  }

  //Function to fetch the data from the API
  const GetCourses = async () => {
    const response = await axios.get(url)
    const {data} = response;
    return data;
  }

//UseEffect to run the function on every render
  useEffect(()=> {
    const GetCoursesYoga = async () => {
      const result = await GetCourses();
      setYogaCourses(result);
      console.log(result);
      setLevels(Array.from(new Set(result.map((result)=> result.level))));
    } 
    GetCoursesYoga();
  }, []);

  //check if the we got response
  useEffect(()=> {
    if(yogaCourses.length > 0) {
      setIsLoading(false);
    }
  }, [yogaCourses])
  

  if(isLoading) {
    return (
      <Loading/>
    )
  } 
  else {
    return (
      <main>
        <div className="title">
                <h2>YOUR PRACTICE REIMAGINED</h2>
            </div>
        <LevelsFilter levels={levels} filterLevels={filterLevels}/>
        <YogaCourses yogaCourses= {yogaCourses}/>
      </main>
      );
  }

  
}

export default App;

//LevelsFilter component

 import React from 'react';
    import Select from 'react-select';
    
    import './LevelsFilter.css';
    
    const LevelsFilter = ({levels, filterLevels}) => {
        const option = levels.map((level)=> ({value : level, label: level}));
        return (
        <div>
            <Select
            options ={option}
            className="select-option"
            placeholder={"Type..."}
            onChange={filterLevels}
            />
        </div>
        )
    } 
    
    
    export default LevelsFilter;

Upvotes: 1

Views: 1294

Answers (2)

Drew Reese
Drew Reese

Reputation: 202836

Issue

You are replacing your state with the filtered data and subsequent filtering filters from there, so you only ever reduce your data.

Solution

I suggest storing an active filter state (i.e. level) and do the filtering inline when rendering so you skip the issue of stale/bad state.

function App() {
  //states
  const [yogaCourses, setYogaCourses] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [levels, setLevels] = useState([]);
  const [level, setLevel] = useState('');

  const levelChangeHandler = ({ value }) => {
    setLevel(value);
  }

  //Filter by Levels 
  const filterLevels = (level) => {
    return yogaCourses.filter(
      (singleLevel) => level ? singleLevel.level === level : true
    );
  }

  ...
  

  if(isLoading) {
    return (
      <Loading/>
    )
  } 
  else {
    return (
      <main>
        <div className="title">
          <h2>YOUR PRACTICE REIMAGINED</h2>
        </div>
        <LevelsFilter levels={levels} onChange={levelChangeHandler}/>
        <YogaCourses yogaCourses={filterLevels(level)}/>
      </main>
      );
  }
}

LevelsFilter

import React from 'react';
import Select from 'react-select';

import './LevelsFilter.css';

const LevelsFilter = ({ levels, onChange }) => {
    const option = levels.map((level)=> ({value : level, label: level}));
    return (
    <div>
        <Select
        options ={option}
        className="select-option"
        placeholder={"Type..."}
        onChange={onChange}
        />
    </div>
    )
} 

Upvotes: 1

Farhan Asif
Farhan Asif

Reputation: 158

You need a copy state. Your code is replacing the data source with filtered data. When you first time selects the option then your state replaces it with that one and you no longer have previous state data. On the second time, you don't have data that why it's blank on-screen.

Just copy and replace the below app.js code:

import React,{useState, useEffect} from "react";
import YogaCourses from "./components/YogaCourses/YogaCourses";
import Loading from "./components/IsLoading/Loading";
import LevelsFilter from './components/LevelsFilter/LevelsFilter';

//API to fetch the data
const url = 'https://gist.githubusercontent.com/Tayarthouail/8fb14fe117fdd718ceabd6ee05ed4525/raw/8c86c4bb89fc51667ba0578b2dcba14a0b21f08c/Yoga-courses-api.json';

function App() {
  
  //states
  const [yogaCourses, setYogaCourses] = useState([]);
  const [filteredYogaCourses, setFillteredYogaCourses] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [levels, setLevels] = useState([]);

  //Filter by Levels 
  const filterLevels = (level) => {
    const getLevels = yogaCourses.filter((singleLevel)=> singleLevel.level === level.value);
    setFillteredYogaCourses(getLevels);
  }

  //Function to fetch the data from the API
  const GetCourses = async () => {
    const response = await axios.get(url)
    const {data} = response;
    return data;
  }

//UseEffect to run the function on every render
          useEffect(()=> {
            const GetCoursesYoga = async () => {
              const result = await GetCourses();
              setYogaCourses(result);
              setLevels(Array.from(new Set(result.map((result)=> result.level))));
            } 
            GetCoursesYoga();
          }, []);
    
  //check if the we got response
  useEffect(()=> {
    if(yogaCourses.length > 0) {
      setIsLoading(false);
    }
  }, [yogaCourses])
  

  if(isLoading) {
    return (
      <Loading/>
    )
  } 
  else {
    return (
      <main>
        <div className="title">
                <h2>YOUR PRACTICE REIMAGINED</h2>
            </div>
        <LevelsFilter levels={levels} filterLevels={filterLevels}/>
        <YogaCourses yogaCourses= {filteredYogaCourses}/>
      </main>
      );
  }

  
}

export default App;

I hope it will work, if not then please debug it because I haven't tested it but the idea will be same. :)

Upvotes: 1

Related Questions