简体   繁体   中英

Call fetch function in functional component React

I'v tried so many way to fetch data only once before rendering but have some issue:

1) I Can't call dispatch in componentDidMount because there is the rule that I can do it in Functional component only

2) If I try to call fetch function in the beginning of a Functional component it starts to rerender infinitely because fetch function calls every time and change a state in a redux store

3) I found a solution with useEffect but it generate exception "Invalid hook call" like in first point

How can I call fetch function only once in this component?

here is my component :

   import React, { useEffect } from "react";
import { useParams as params} from "react-router-dom";
import { VolunteerCardList } from "./VolunteerCardList";
import { AnimalNeeds } from "./AnimalNeeds";
import { AppState } from "../reducers/rootReducer";
import { connect } from "react-redux";
import { Page404 } from "./404";
import { fetchAnimal } from "../actions/animalAction";
import { Dispatch } from "redux";
import { IAnimalCard } from "../interfaces/Interfaces";

const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
    useEffect(() => {   
            fetch(); //invalid hook call????
    }, [])


    return (
        <div className="container">
            some html
        </div>
    )
}

interface RouteParams {
    shelterid: string,
    animalid: string,
}

interface mapStateToPropsType {
    animal: IAnimalCard,
    loading : boolean
}

const mapStateToProps = (state: AppState) : mapStateToPropsType=> {
    return{
        animal: state.animals.animal,
        loading: state.app.loading
    }
}

interface mapDispatchToPropsType {
    fetch: () => void;
}

const mapDispatchToProps = (dispatch: Dispatch<any>) : mapDispatchToPropsType => ({
    fetch : () => {
        const route = params<RouteParams>();
        dispatch(fetchAnimal(route.shelterid, route.animalid));
    }
})

type Props  = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;

export default connect(mapStateToProps, mapDispatchToProps as any)(AnimalCard);

this is my reducer :

export const animalReducer = (state: AnimalReducerType = initState, action: IAction) => {

switch (action.type) {
    case AnimalTypes.FETCH_ANIMAL:
            return {...state, animal: action.payload};
        break;

    default:
        return state;
        break;
}

this is action:

export interface IFetchAnimalAction  {
    type: AnimalTypes.FETCH_ANIMAL,
    payload: IAnimalCard
}

export type IAction = IFetchAnimalAction;

export const fetchAnimal = (shelterId : string, animalId: string) => {
    return async (dispatch: Dispatch)  => {
        const response = await fetch(`https://localhost:44300/api/animals/${animalId}`);
        const json = await response.json();
        dispatch<IFetchAnimalAction>({type: AnimalTypes.FETCH_ANIMAL, payload: json})
    }
} 

This runs as old lifecycle method componentDidMount :

useEffect(() => {   
  fetch(); //invalid hook call????
}, [])

I guess the behaviour you want to replicate is the one iterated by componentWillMount , which you cannot do by any of the standard hooks. My go-to solution for this is to let the acquire some loadingState , most explicitly as:

const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
    const [isLoading, setIsLoading] = useState<boolean>(false);

    useEffect(() => {   
      fetch().then(res => {
        // Do whatever with res
        setIsLoading(true);
      } 
    }, [])

    if(!isLoading){
      return null
    }

    return (
        <div className="container">
            some html
        </div>
    )
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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