反应挂钩/上下文和弹性UI。函数组件

时间:2020-06-04 11:43:24

标签: javascript reactjs axios react-hooks react-context

我对React Hooks / Context很陌生,因此希望获得一些帮助。请不要用锐利的牙齿跳到我身上。我检查了其他解决方案以及以前做过的一些方法,但似乎无法通过“从列表中选择”的方式来实现。

摘要

我需要在Search.js的const'allMunicipios'(对象数组)内部获取名称的市政名称列表,然后显示带有来自所选市政名称的某些数据的卡片。

任务

从eltiempo-net REST API获取数据。 使用Elastic UI中的Combobox异步元素从市政列表中进行选择。 显示卡(也来自弹性UI),其中包含所选市政信息。

必须通过功能组件/挂钩来完成。没有课程。 我将不胜感激。

我做了什么

我已经在上下文文件夹中创建了我的reducer,context和type文件,以使所有数据与这些数据一起使用,然后从组件访问数据。 我已经创建了Search.js文件。然后在App.js中导入Search.js。 我已经使用了REST API,现在将其保存在Search.js中

问题

以某种方式,我无法遍历我得到的数据。 基本上我需要将municipios.NOMBRE从api推送到search.js组件中的数组const allMunicipios数组。但是当我控制台日志时,它给了我未定义的信息。不能弄清楚为什么。

我将在这里分享相关的代码/组件。非常感谢您抽出宝贵的时间。

municipiosReducer.js

import {
  SEARCH_MUNICIPIOS,
  CLEAR_MUNICIPIOS,
  GET_MUNICIPIO,
  GET_WEATHER,
} from "./types";

export default (state, action) => {
  switch (action.type) {
    case SEARCH_MUNICIPIOS:
      return {
        ...state,
        municipios: action.payload,
      };
    case GET_MUNICIPIO:
      return {
        ...state,
        municipio: action.payload,
      };
    case CLEAR_MUNICIPIOS:
      return {
        ...state,
        municipios: [],
      };
    case GET_WEATHER: {
      return {
        ...state,
        weather: action.payload,
      };
    }

    default:
      return state;
  }
};

municipiosContext.js

import { createContext } from "react";

const municipiosContext = createContext();

export default municipiosContext;

MunicipiosState.js

import React, { createContext, useReducer, Component } from "react";
import axios from "axios";
import MunicipiosContext from "./municipiosContext";
import MunicipiosReducer from "./municipiosReducer";
import {
  SEARCH_MUNICIPIOS,
  CLEAR_MUNICIPIOS,
  GET_MUNICIPIO,
  GET_WEATHER,
} from "./types";

const MunicipiosState = (props) => {
  const initialState = {
    municipios: [],
    municipio: {},
  };

  const [state, dispatch] = useReducer(MunicipiosReducer, initialState);
  //Search municipios
  //In arrow functions 'async' goes before the parameter.
  const searchMunicipios = async () => {
    const res = await axios.get(
      `https://www.el-tiempo.net/api/json/v2/provincias/08/municipios`
      // 08 means barcelona province. This should give me the list of all its municipios
    );

    dispatch({
      type: SEARCH_MUNICIPIOS,
      payload: res.data.municipios,
    });
  };

  //Get Municipio
  const getMunicipio = async (municipio) => {
    const res = await axios.get(
      `https://www.el-tiempo.net/api/json/v2/provincias/08/municipios/${municipio.CODIGOINE}`
      //CODIGOINE is in this REST API kind of the ID for each municipio.
      //I intent to use this later to get the weather conditions from each municipio.
    );

    dispatch({ type: GET_MUNICIPIO, payload: res.municipio });
  };

  const dataMunicipiosArray = [searchMunicipios];

  //Clear Municipios
  const clearMunicipios = () => {
    dispatch({ type: CLEAR_MUNICIPIOS });
  };

  return (
    <MunicipiosContext.Provider
      value={{
        municipios: state.municipios,
        municipio: state.municipio,
        searchMunicipios,
        getMunicipio,
        clearMunicipios,
        dataMunicipiosArray,
      }}
    >
      {props.children}
    </MunicipiosContext.Provider>
  );
};

export default MunicipiosState;


Search.js

import "@elastic/eui/dist/eui_theme_light.css";
import "@babel/polyfill";
import MunicipiosContext from "../contexts/municipiosContext";
import MunicipiosState from "../contexts/MunicipiosState";
import { EuiComboBox, EuiText } from "@elastic/eui";
import React, { useState, useEffect, useCallback, useContext } from "react";

const Search = () => {
  const municipiosContext = useContext(MunicipiosContext);
  const { searchMunicipios, municipios } = MunicipiosState;

  useEffect(() => {
    return municipiosContext.searchMunicipios();
  }, []);

  const municipiosFromContext = municipiosContext.municipios;
  const bringOneMunicipio = municipiosContext.municipios[0];

  let municipiosNames = municipiosFromContext.map((municipio) => {
    return { label: `${municipio.NOMBRE}` };
  });

  console.log(`municipiosFromContext`, municipiosFromContext);
  console.log(`const bringOneMunicipio:`, bringOneMunicipio);
  console.log(`municipiosNames:`, municipiosNames);

  const allMunicipios = [
    { label: "santcugat" },
    { label: "BARCELONETA" },
    { label: "BARCE" },
  ];

  const [selectedOptions, setSelected] = useState([]);
  const [isLoading, setLoading] = useState(false);
  const [options, setOptions] = useState([]);
  let searchTimeout;

  const onChange = (selectedOptions) => {
    setSelected(selectedOptions);
  };

  // combo-box
  const onSearchChange = useCallback((searchValue) => {
    setLoading(true);
    setOptions([]);

    clearTimeout(searchTimeout);

    // eslint-disable-next-line react-hooks/exhaustive-deps
    searchTimeout = setTimeout(() => {
      // Simulate a remotely-executed search.
      setLoading(false);
      setOptions(
        municipiosNames.filter((option) =>
          option.label.toLowerCase().includes(searchValue.toLowerCase())
        )
      );
    }, 1200);
  }, []);

  useEffect(() => {
    // Simulate initial load.
    onSearchChange("");
  }, [onSearchChange]);

  return (
    <div>
      <EuiComboBox
        placeholder="Search asynchronously"
        async
        options={options}
        selectedOptions={selectedOptions}
        isLoading={isLoading}
        onChange={onChange}
        onSearchChange={onSearchChange}
      />
      <button>Lista de municipios</button>
    </div>
  );
};

export default Search;

也是 Home.js

import React, { useState } from "react";
import { EuiComboBox, EuiText } from "@elastic/eui";
// import { DisplayToggles } from "../form_controls/display_toggles";
import "@babel/polyfill";
import "@elastic/eui/dist/eui_theme_light.css";
import Search from "./Search";
import MunicipioCard from "./MunicipioCard";

const Home = () => {
  return (
    <div>
      <EuiText grow={false}>
        <h1>Clima en la provincia de Barcelona</h1>
        <h2>Por favor seleccione un municipio</h2>
      </EuiText>
      <Search />

      <MunicipioCard />
    </div>
  );
};

export default Home;

App.js

import "@babel/polyfill";
import "@elastic/eui/dist/eui_theme_light.css";
import { EuiText } from "@elastic/eui";
import React from "react";
import Home from "./components/Home";
import MunicipiosState from "./contexts/MunicipiosState";

import "./App.css";

function App() {
  return (
    <MunicipiosState>
      <div className="App">
        <EuiText>
          <h1>App Component h1</h1>
        </EuiText>
        <Home />
      </div>
    </MunicipiosState>
  );
}

export default App;


1 个答案:

答案 0 :(得分:1)

您正在使用forEach并将返回的值分配给变量,但是forEach不返回任何值。您应该改用地图

  let municipiosNames = municipiosFromContext.map((municipio) => {
    return `label: ${municipio.NOMBRE}`;
  });

根据您的评论:

您的数据是异步加载的,因此在第一次渲染时将不可用,并且由于功能组件依赖于闭包,因此onSearchChange函数会在创建时从闭包中获取值,即使其中包含setTimeout更新的值不会反映

这里的解决方案是将municipiosFromContext添加为useEffect的依赖项

const onSearchChange = useCallback((searchValue) => {
    setLoading(true);
    setOptions([]);

    clearTimeout(searchTimeout);

    // eslint-disable-next-line react-hooks/exhaustive-deps
    searchTimeout = setTimeout(() => {
      // Simulate a remotely-executed search.
      setLoading(false);
      setOptions(
        municipiosNames.filter((option) =>
          option.label.toLowerCase().includes(searchValue.toLowerCase())
        )
      );
    }, 1200);
  }, [municipiosFromContext]);

  useEffect(() => {
    // Simulate initial load.
    onSearchChange("");
  }, [onSearchChange]);