简体   繁体   English

React 和 Redux 中的过滤和排序问题

[英]Problems with filter and sorts in React and Redux

I'm creating an app with the Pokemon API. I have the back ready, and on the front I work with redux and react.我正在使用 Pokemon API 创建一个应用程序。我已经准备好背面,在前面我使用 redux 并做出反应。

What I do in the reducer I pass to a Filter.js file, which I import into Pokemons.js.我在 reducer 中所做的工作传递给 Filter.js 文件,我将其导入 Pokemons.js。 In that last file, the only thing I do is map the array with all the pokemons.在最后一个文件中,我唯一做的就是 map 包含所有口袋妖怪的数组。

The problem is that the filters and sorts are not working well.问题是过滤器和排序不能正常工作。 When I click on one of the Type options, it shows me the pokemons with that type, but then I can't choose another one.当我单击其中一个类型选项时,它会显示该类型的口袋妖怪,但我无法选择另一个。 In the Created options, if I choose Data Base it shows me the ones created in the DB, but if I click on Api the page is reloaded and it shows me all the pokemons.在“已创建”选项中,如果我选择“数据库”,它会显示在数据库中创建的那些,但如果我单击 Api,页面会重新加载并显示所有的口袋妖怪。 And if I click on All, it does nothing.如果我点击全部,它什么也不做。

I also couldn't get the sorts to work and if I touch the Clear Filter button everything breaks.我也无法让排序工作,如果我触摸清除过滤器按钮,一切都会中断。

I think the problem is how the information passes from one side to the other, because I check the logic and I don't find any errors, but I'm no longer sure of anything.我认为问题是信息如何从一侧传递到另一侧,因为我检查了逻辑并没有发现任何错误,但我不再确定任何事情。

This is the reducer:这是减速器:

import {
  GET_POKEMONS,
  GET_POKEMON_DETAIL,
  CREATE_POKEMON,
  DELETE_POKEMON,
  GET_TYPES,
  SORT_BY_ALPHABET,
  SORT_BY_ATTACK,
  FILTER_BY_CREATED,
  FILTER_BY_TYPE,
  SEARCH_POKEMON,
  CLEAN_FILTER,
} from "./actions";

const initialState = {
  pokemons: [],
  pokemonDetail: {},
  types: [],
  filterByType: null,
};

const rootReducer = (state = initialState, action) => {
  switch (action.type) {
    case GET_POKEMONS:
      return {
        ...state,
        pokemons: action.payload,
      };
    case GET_POKEMON_DETAIL:
      return {
        ...state,
        pokemonDetail: action.payload,
      };

    case GET_TYPES:
      return {
        ...state,
        types: action.payload,
      };
    case CREATE_POKEMON:
      const name = action.payload.data.name;
      const speed = action.payload.data.speed;
      const hp = action.payload.data.hp;
      const height = action.payload.data.height;
      const weight = action.payload.data.weight;
      const attack = action.payload.data.attack;
      const defense = action.payload.data.defense;
      const createdInDB = action.payload.data.createdInDB;
      const types = action.payload.data.types;
      const img = action.payload.data.img;

      return {
        ...state,
        //pokemons: state.pokemons.concat({action.payload.data.name, action.payload.data.speed })
        pokemons: state.pokemons.concat({
          name,
          speed,
          hp,
          height,
          weight,
          attack,
          defense,
          createdInDB,
          types,
          img,
        }),
      };
    case DELETE_POKEMON:
      return {
        ...state,
        pokemons: state.pokemons.filter(
          (pokemon) => pokemon.id !== action.payload
        ),
      };

      //SORTS Y FILTERS
    case SORT_BY_ALPHABET:
      const sortAlpha =
        action.payload === "a-z"
          ? state.pokemons.sort((a, b) => {
              return a.name.toLowerCase() > b.name.toLowerCase();
            })
          : state.pokemons.sort((a, b) => {
              return a.name.toLowerCase() < b.name.toLowerCase();
            });
      return {
        ...state,
        pokemons: sortAlpha,
      };

    case SORT_BY_ATTACK:
      const sortAsc = state.pokemons.sort((a, b) => a.attack > b.attack);
      const sortDes = state.pokemons.sort((a, b) => a.attack < b.attack);
      const sortAttack = action.payload;
      if (sortAttack === "- to +")
        return {
          ...state,
          pokemons: sortAsc,
        };
      else if (sortAttack === "+ to -")
        return {
          ...state,
          pokemons: sortDes,
        };
      break;

    case FILTER_BY_TYPE:
      let type = action.payload;
      let pokemonFiltered = state.pokemons.filter((poke) => poke.types.includes(type))  
      console.log("filterByType", action.payload);
      if(pokemonFiltered.length > 0){
        return {
          ...state,
          pokemons: pokemonFiltered,
        };
      } else {
        return {
          ...state,
          pokemons: state.pokemons
        }
      }

    case FILTER_BY_CREATED:
      let created = state.pokemons.filter((p) => typeof p.id === "string");
      let api = state.pokemons.filter((p) => typeof p.id === "number");
      if (action.payload === "Data Base") {
        console.log("action.payload is", action.payload);
        return {
          ...state,
          pokemons: created,
        };
      } else if (action.payload === "API") {
        console.log("action.payload is", action.payload);
        return {
          ...state,
          pokemons: api,
        };
      } else if (action.payload === "All") {
        console.log("action.payload is", action.payload);
        return {
          ...state,
          pokemons: state.pokemons,
        };
      }
      break;

    case SEARCH_POKEMON:
      return {
        ...state,
        pokemons: action.payload,
      };

    case CLEAN_FILTER: 
    return{
      ...state,
      pokemons: state.pokemons,
    }
    default:
      return { ...state };
  }
};

export default rootReducer;

This is actions.js:这是 actions.js:

import axios from 'axios';

export const GET_POKEMONS = "GET_POKEMONS";
export const GET_POKEMON_DETAIL = "GET_POKEMON_DETAIL";
export const CREATE_POKEMON = "CREATE_POKEMON";
export const DELETE_POKEMON = "CREATE_POKEMON";
export const GET_TYPES = "GET_TYPES";

//Filtos y ordenamiento
export const FILTER_BY_TYPE = "FILTER_BY_TYPE";
export const FILTER_BY_CREATED = "FILTER_BY_CREATED";
export const SORT_BY_ALPHABET = "SORT_BY_ALPHABET";
export const SORT_BY_ATTACK = "SORT_BY_ATTACK";
export const CLEAN_FILTER = "CLEAR_FILTER"

export const SEARCH_POKEMON = "SEARCH_POKEMON";


export const getPokemons = () => {
    return function(dispatch) {
        return fetch('http://localhost:3001/pokemons')
        .then(res => res.json())
        .then(pokemons => dispatch(
            {type: GET_POKEMONS, payload: pokemons}
        ))
    }
};

export const getPokemonDetail = (id) => {
    return function(dispatch) {
        return fetch(`http://localhost:3001/pokemons/${id}`)
        .then(res => res.json())
        .then(data => dispatch(
            {type: GET_POKEMON_DETAIL, payload: data[0]},
            console.log('data[0] in actions', data[0])
        ))
    }
};

export const createPokemon = (pokemon) => {
    // const options = {
    //     method: 'POST',
    //     headers: {
    //     'Content-Type': 'application/json',
    //     },
    //     body: JSON.stringify(pokemon),
    //     };
    return async function(dispatch){
        const newPokemon = await axios.post(`http://localhost:3001/pokemons/`, pokemon)
       dispatch({type: CREATE_POKEMON, payload: newPokemon})
    }
    //return { type: CREATE_POKEMON, payload: pokemon}
};

export const deletePokemon = (id) => {
    return { type: DELETE_POKEMON, payload: id}
};

export const getTypes = () => {
    return function(dispatch){
        return fetch('http://localhost:3001/types')
        .then(res => res.json())
        .then(pokemons => {
            let types = [];
            pokemons.map((pokemon) => types.push(pokemon.types))
            dispatch ({type: GET_TYPES, payload: types})
        }
        )
    }
};

export const sortByAlphabet = (order) => {
    return ({type: SORT_BY_ALPHABET, payload: order});
}

export const sortByAttack = (order) => {
    return {type: SORT_BY_ATTACK, payload: order}
}

export const filterByType = (type) => {
    return {type: FILTER_BY_TYPE, payload: type}
}

export const filterByCreated = (value) => {
    return {type: FILTER_BY_CREATED, payload: value}
}

export const searchPokemon = (query) => (dispatch, getstate) => {
    const { pokemons } = getstate()
    const result = pokemons.searchPokemon.find((poke) => poke.name.toLowerCase().includes(query.toLowerCase()));
    dispatch({ type: SEARCH_POKEMON, payload: result})
} 

export const cleanFilter = (payload) => {
    return {type: CLEAN_FILTER, payload}
}

This is Filters.js:这是 Filters.js:

import React, { useState } from "react";
import s from "./Filters.module.css";

import { useDispatch, useSelector } from "react-redux";
import * as actions from "../../redux/actions";
import { useHistory } from "react-router-dom";

export default function Filters() {
  const [selectValue, setSelectValue] = React.useState("");
  const [selectValueB, setSelectValueB] = React.useState("");
  const [orden, setOrden] = useState("");
  const [ordenB, setOrdenB] = useState("");
  
  const history = useHistory(); 
  const dispatch = useDispatch();
  const pokemons = useSelector((state) => state.pokemons);

  React.useEffect(() => {
    if (!pokemons[0]) {
      dispatch(actions.getPokemons());
      dispatch(actions.getTypes());
    }
  }, [dispatch, pokemons]);

  function handleClick(e){
    //e.preventDefault();
    const value = e.target.value;
    console.log('resetear filtros')
    dispatch(actions.cleanFilter(value))
    //history.push('/pokemons')
  };

  function handleFilterType(e) {
    //e.preventDefault();
    const value = e.target.value;
    setSelectValue(value); //para mostrarle a usuario lo que eligio
    dispatch(actions.filterByType(value)); //disapara la action del reducer
    history.push("/pokemons");
  }

  function handleFilterCreated(e) {
    e.preventDefault();
    const value = e.target.value;
    setSelectValueB(value);
    console.log('filtrar por creado')
    dispatch(actions.filterByCreated(value));
  }

  function handleSortByAlpha(e) {
    //e.preventDefault();
    dispatch(actions.sortByAlphabet(e.target.value));
    setOrden(`Ordered from ${e.target.value}`)
    console.log('ordenado por alfabeto')
  }

  function handleSortByAttack(e) {
    e.preventDefault();
    dispatch(actions.sortByAttack(e.target.value));
    setOrdenB(`Ordered from ${e.target.value}`)
    console.log('ordenado por attack')
  }

  return (
    <div className={s.filterSection}>
      <div className={s.filters}>
        <h2 className={s.filterTitle}>Filters</h2>
        <div className={s.filterBy}>
          <h3 className={s.filterSubitle}>Filter by type</h3>

          <select
            className={s.select}
            value="default"
              onChange={(e) => handleFilterType(e)}
          >
            <option value="default" disabled hidden>
              Pokemon type
            </option>
            <option value="bug">bug</option>
            <option value="dark">dark</option>
            <option value="dragon">dragon</option>
            <option value="electric">electric</option>
            <option value="fairy">fairy</option>
            <option value="fighting">fighting</option>
            <option value="flying">flying</option>
            <option value="fire">fire</option>
            <option value="ghost">ghost</option>
            <option value="grass">grass</option>
            <option value="ground">ground</option>
            <option value="ice">ice</option>
            <option value="normal">normal</option>
            <option value="poison">poison</option>
            <option value="psychic">psychic</option>
            <option value="rock">rock</option>
            <option value="shadow">shadow</option>
            <option value="steel">steel</option>
            <option value="unknow">unknow</option>
            <option value="water">water</option>
          </select>
          {selectValue && <h3 className={s.showFilter}>{selectValue}</h3>}
      </div>
      
      <div className={s.filterBy}>
        <h3 className={s.filterSubitle}>Created in</h3>
        <select 
          className={s.select} 
          value="default"  
          onChange={e => handleFilterCreated(e)}
          >
          <option value="default" disabled hidden>
            Created in
          </option>
          <option value="All">All</option>
          <option value="API">API</option>
          <option value="Data Base">Data Base</option>
        </select>
        {selectValueB && <h3 className={s.showFilter}>{selectValueB}</h3>}
      </div>
    </div>
      
      <div className={s.filters}>
        <div className={s.filterBy}>
          <h3 className={s.filterSubitle}>Sort by Alphabet</h3>
          <select
            value="default"
            onChange={(e) => handleSortByAlpha(e)}
          >
            <option value="default" disabled hidden>
              Sort by Alphabet
            </option>
            <option value="a-z" onClick={(e) => handleSortByAlpha(e)}>From A to Z</option>
            <option value="z-a" onClick={(e) => handleSortByAlpha(e)}>From Z to A</option>
          </select>
          {orden && <h3 className={s.showFilter}>{orden}</h3>}
        </div>
      </div>

      <div className={s.filters}>
        <div className={s.filterBy}>
          <h3 className={s.filterSubitle}>Sort by Attack</h3>
          <select
            value="default"
            onChange={(e) => handleSortByAttack(e)}
          >
            <option value="default" disabled hidden>
              Sort by Attack
            </option>
            <option value="- to +">From - to +</option>
            <option value="+ to -">From + to -</option>
          </select>
          {ordenB && <h3 className={s.showFilter}>{ordenB}</h3>}
        </div>
      </div>

      <button className={s.filterBtn} onClick={() => handleClick()}>Reset filters</button>
    </div>

  );
}

This is Pokemons.js这是 Pokemons.js

import React from 'react'
import s from './Pokemons.module.css'
import { useDispatch, useSelector } from "react-redux";

import * as actions from '../../redux/actions'

//importo para poder mapear todas las cards
import PokeCard from '../PokeCard/PokeCard';
import Filters from '../Filters/Filters';

export default function Pokemons() {
  const dispatch = useDispatch();
  const pokemons = useSelector((state)=> state.pokemons);
  //const types = useSelector((state)=> state.types);
  const filterByType = useSelector((state)=> state.filterByType);

  React.useEffect(() => {
    dispatch(actions.getPokemons());
    dispatch(actions.getTypes());
  },[dispatch])

  console.log('filteredBytype', filterByType)
  return (
    <div className={s.pokemonsSection}>
      <Filters />
      <div className={s.allPokemons}>
        {//si hay un filterbytype mostra lo q incluya ese filtro. Si no existe mapea todo pokemons
          pokemons.map(poke =>{
            return <PokeCard 
            key={poke.id} 
            id={poke.id} 
            name={poke.name} 
            image={poke.img} 
            types={poke.types}/>
          }) 

        }
      </div>
    </div>  
  )
}

I tryed diferent ways to code the reducer and the filters, but I get the same results.我尝试了不同的方法来对减速器和过滤器进行编码,但我得到了相同的结果。 I wish you can help me.我希望你能帮助我。 Thanks!谢谢!

The issue I see is that you are effectively mutating your "source of truth", ie the state.pokemons array.我看到的问题是您正在有效地改变您的“真相来源”,即state.pokemons数组。 Each time state.pokemons is filtered the array size is less than or equal to its prior size.每次过滤state.pokemons时,数组大小都小于或等于其先前的大小。 In other words, filtering is a reducing action, that array will never "remember" its prior elements once they are filtered, ie removed, from the array.换句话说,过滤是一种减少操作,该数组将永远不会“记住”其先前的元素,一旦它们被过滤,即从数组中删除。

Filtered data is really derived state in that it is derived from some "source of truth" and a filtering condition.过滤后的数据实际上是 state派生的,因为它来自一些“真实来源”和过滤条件。 You don't want to mutate the original data, but you want to take it and the filter condition and derive a result to display.您不想改变原始数据,但想获取过滤条件导出要显示的结果。

I suggest storing the filtering conditions in the store and apply the filter/sorting when selecting the state. Adding/removing/editing should be done on the source data.我建议将筛选条件存储在商店中,并在选择 state 时应用筛选/排序。添加/删除/编辑应该在源数据上完成。

const initialState = {
  pokemons: [],
  pokemonDetail: {},
  types: [],
  filterByType: null,
  sortBy: {
    alpha: "a-z",
    attack: "- to +",
  },
  filters: {},
  search: "",
};

const rootReducer = (state = initialState, action) => {
  switch (action.type) {
    case GET_POKEMONS:
      return {
        ...state,
        pokemons: action.payload,
      };

    case GET_POKEMON_DETAIL:
      return {
        ...state,
        pokemonDetail: action.payload,
      };

    case GET_TYPES:
      return {
        ...state,
        types: action.payload,
      };

    case CREATE_POKEMON:
      const newPokemon = {...action.payload.data};

      return {
        ...state,
        pokemons: state.pokemons.concat(newPokemon),
      };

    case DELETE_POKEMON:
      return {
        ...state,
        pokemons: state.pokemons.filter(
          (pokemon) => pokemon.id !== action.payload
        ),
      };

    //SORTS Y FILTERS
    case SORT_BY_ALPHABET:
      return {
        ...state,
        sortBy: {
          ...state.sortBy,
          alpha: action.payload
        },
      };

    case SORT_BY_ATTACK:
      return {
        ...state,
        sortBy: {
          ...state.sortBy,
          attack: action.payload
        },
      };

    case FILTER_BY_TYPE:
      return {
        ...state,
        filterBy: {
          ...state.filterBy,
          type: action.payload,
        },
      };

    case FILTER_BY_CREATED:
      return {
        ...state,
        filterBy: {
          ...state.filterBy,
          created: action.payload,
        },
      };

    case SEARCH_POKEMON:
      return {
        ...state,
        search: action.payload,
      };

    case CLEAN_FILTER: 
      return {
        ...state,
        sortBy: initialState.sortBy,
        filterBy: initialState.filterBy,
        search: initialState.search,
      }

    default:
      return { ...state };
  }
};

Update the searchPokemon action creator to just store the search query term lowercased.更新searchPokemon动作创建器以仅存储小写的搜索查询词。

export const searchPokemon = (query) => ({
  type: SEARCH_POKEMON,
  payload: query.toLowerCase()
});

Create a pokemon state selector function that does the work of filtering/sorting the state.pokemons array.创建一个 pokemon state 选择器 function 来过滤/排序state.pokemons数组。 Keep in mind that the .sort function callback should return a number ( -1|0|1 ) instead of a boolean to indicate the sorted order between two elements.请记住, .sort function 回调应该返回一个数字 ( -1|0|1 ) 而不是 boolean 以指示两个元素之间的排序顺序。 The .filter function callback returns a boolean if the current element should be included in the result array.如果当前元素应包含在结果数组中, .filter function 回调将返回 boolean。

const pokemonSelector = state => {
  return state.pokemons
    .filter(pokemon => {
      // NOTE: All strings include ""
      return pokemon.name.toLowerCase().includes(state.search);
    })
    .filter(pokemon => {
      return state.filterBy.type
        ? pokemon.types.includes(state.filterBy.type)
        : true
    })
    .filter(pokemon => {
      switch(state.filterBy.created) {
        case "Data Base":
          return typeof pokemon.id === "string";
        case "API":
          return typeof pokemon.id === "number";
        default:
          return true;
      }
    })
    .sort((a, b) => {
      return state.sortBy.alpha === "a-z"
        ? a.name.toLowerCase().localeCompare(b.name.toLowerCase())
        : b.name.toLowerCase().localeCompare(a.name.toLowerCase());
    })
    .sort((a, b) => {
      switch(state.sortBy.attack) {
        case "- to +":
          return a.attack - b.attack;
        case "+ to -":
          return b.attack - a.attack;
        default:
          return 0;
      }
    });
};

Update the Pokemons component to import this new pokemonSelector selector function and pass this to the useSelector hook to compute the derived selected state.更新Pokemons组件以导入这个新的pokemonSelector选择器 function 并将其传递给useSelector挂钩以计算派生的选定 state。

...
import { pokemonSelector } from '../path/to/selectors';

export default function Pokemons() {
  const dispatch = useDispatch();

  const pokemons = useSelector(pokemonSelector); // gets filtered/sorted pokemons

  React.useEffect(() => {
    dispatch(actions.getPokemons());
    dispatch(actions.getTypes());
  },[dispatch])

  return (
    <div className={s.pokemonsSection}>
      <Filters />
      <div className={s.allPokemons}>
        {pokemons.map(poke => (
          <PokeCard 
            key={poke.id} 
            id={poke.id} 
            name={poke.name} 
            image={poke.img} 
            types={poke.types}
          />
        ))}
      </div>
    </div>  
  );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM