How can I use custom method in useEffect??
If I create many components and they use same fetch function, Shoud I declare fetch function in every component's effect?? The function does same work??
As far as I know, If I want to use component's state in useEffect, I should declare and call that function in useEffect likes example 1.
But I want to declare the function other js file. Because it was called other components.
According to Dan Abramov (https://overreacted.io/a-complete-guide-to-useeffect/), If I want to move function, I must use useCallback method.
But I didn't understand well. Please give me any advice this issue.
1. Component.js
const Component = () => {
const [id,setId] = useState(0);
const dispatch = useDispatch();
useEffect(() => {
fetch(`url/${id}`).then(res => dispatch({type: success, payload: res}))
},[id])
}
2. Component.js
const Component = () => {
const [id, setId] = useState(0);
useEffect(()=> {
callApi(id)
},[id])
}
Api.js
const callApi = (id) => {
const dispatch = useDispatch();
return fetch(`url/${id}`).then(res => dispatch({type:success, payload:res})
}
Shoud I declare fetch function in every component's effect?
Extract a custom hook, useFetch(), with the same fetch functionality.
// custom hook
const useFetch = (id) => {
const [data, setData] = useState(null);
useEffect(
() => {
async function fetchData() {
const res = await fetch(`url/${id})
setData(res);
}
fetchData();
}, [id] // id as dependency
)
return data;
}
// sample component using custom hook
const Component = (props) => {
const dispatch = useDispatch();
const data = useFetch(props.id); // use custom hook
useEffect(
() => {
if (data) {
dispatch({type: success, payload: data});
}
}, [data] // dispatch every time data changes
)
}
Since multiple of your components perform the same action within useEffect, you can extract out the code into a custom hook and use it in all the components
useFetch.js
export const useFetch = () => {
const dispatch = useDispatch();
useEffect(() => {
fetch(`url/${id}).then(res => dispatch({type: success, payload: res}))
},[id])
}
Now in the component you can write
const Component = () => {
const [id, setId] = useState(0);
useFetch(id);
}
Related
I have written a function for API calls. I want to reuse this function from a different page.
FetchData.js
export const FetchData = (url, query, variable) => {
const [fetchData, setFetchData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const queryResult = await axios.post(
url, {
query: query,
variables: variable
}
)
const result = queryResult.data.data;
setFetchData(result.mydata)
};
fetchData();
})
return {fetchData, setFetchData}
}
Here is my main page from where I am trying to call the API using the following code
mainPage.js
import { FetchData } from './FetchData'
export const MainPage = props => {
const onClick = (event) => {
const {fetchData, setFetchData} = FetchData(url, query, variable)
console.log(fetchData)
}
}
It is returning the following error -
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
If you need to fetch data on response to an event, you don't need a useEffect.
const useData = (url, query, variable) => {
const [data, setData] = useState([]);
const fetchData = async () => {
const queryResult = await axios.post(url, {
query: query,
variables: variable,
});
setData(queryResult.data.data);
};
return {data, fetchData}
};
export const MainPage = (props) => {
const {data, fetchData} = useData(url, query, variable);
const onClick = (event) => {
fetchData()
};
};
Hooks can't be used inside handler functions.
Do this instead:
import { FetchData } from './FetchData'
export const MainPage = props => {
const {fetchData, setFetchData} = FetchData(url, query, variable)
const onClick = (event) => {
console.log(fetchData)
}
}
I've been studying react hook lately. I found the following in the book I saw.
It is said that the fetchAndSetUser function is updated only when the userId is changed using the useCallback hook.
function Profile({ userId }) {
const [user, setUser] = useState();
const fetchAndSetUser = useCallback(
async needDetail => {
const data = await fetchUser(userId, needDetail);
setUser(data);
},
[userId]
);
useEffect(() => {
fetchAndSetUser(false);
} , [fetchAndSetUser]);
// ...
However, if the fetchAndSetUser function is updated only when the userId is changed using the useCallback hook, I wonder what the difference is from just putting the userId in the dependency array in the useEffect hook. (There is a code below.)
function Profile({ userId }) {
const [user, setUser] = useState();
const fetchAndSetUser = async ( needDetail ) => {
const data = await fetchUser(userId, needDetail);
setUser(data);
};
useEffect(() => {
fetchAndSetUser(false);
} , [userId]);
// ...
I wonder if the two codes are the same or if they are different.
Even though I think both codes would achieve the same result, there is a small difference.
If you wanted to pass that fetchAndSetUser (with no useCallback) function to the children, any time Profile component updated it would also update the children.
You can always combine all with:
function Profile({ userId }) {
const [user, setUser] = useState();
const fetchAndSetUser = useCallback(async ( needDetail ) => {
const data = await fetchUser(userId, needDetail);
setUser(data);
}, [userId]);
useEffect(() => {
fetchAndSetUser(false);
} , [userId]);
Inside thunks.js
export const displayAlert = (text) => () => { alert(`${text}`); }
Inside another file
const dispatch = useDispatch();
const example = () => {
useEffect(
()=>{dispatch(displayAlert('Hello'))}
) }
Shows me
Uncaught Error: Invalid hook call
Move the useDispatch hook into the component, and rename it with capital letter (to not trigger another lint warning):
const Example = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(displayAlert("Hello"));
});
};
See rules of hooks.
I found many similar questions here about React Hook useEffect has a missing dependency. I have already checked them, but I didn't find solutions as I faced. I want to pass redux thunk function as a parameter to React custom hook.
Below is my code and it is working fine. But, I got dependency missing warning, I don't want to add ignore warning eslint. If I add dispatchAction to dependency array list, it is dispatching again and again because redux thunk asyn function has fulfilled, reject, pending.
Custom Hook
const useFetchData = (dispatchAction, page) => {
const dispatch = useDispatch();
const [loadMoreLoading, setLoadMoreLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState();
useEffect(() => {
const fetchData = async () => {
setLoadMoreLoading(true);
const resultAction = await dispatch(dispatchAction);
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message);
}
setLoadMoreLoading(false);
};
fetchData();
}, [dispatch, page]);
return [loadMoreLoading, errorMsg]; // it is asking for adding dispatchAction.
My component
const SomeListing = ({userId}) => {
const [page, setPage] = useState(1);
const [loadMoreLoading, errorMsg] = useFetchData(
fetchPropertyByUserId({userId: userId, page: page}),
page,
);
}
So, is there any way to be able to add redux thunk function in react custom hook?
The function fetchPropertyByUserId, when called i.e. fetchPropertyByUserId({userId: userId, page: page}), returns an "actionCreator" function.
Hence, when you call this function at the place of first parameter of your hook useFetchData, it returns a new "actionCreator" function each time (we know that hooks are called at each render):
In SomeListing.jsx:
const [loadMoreLoading, errorMsg] = useFetchData(
fetchPropertyByUserId({userId: userId, page: page}), // <-- Here: it returns a new "actionCreator" function at call (render)
page,
);
And, as soon as you put this function (first parameter of the hook i.e. dispatchAction) as a dependency of useEffect, it should cause an infinite execution of the effect because, now we know, that dispatchAction is getting created (hence, changed) at every render.
In useFetchData.js:
export const useFetchData = (dispatchAction, page) => {
// ...
useEffect(() => {
const fetchData = async () => {
setLoadMoreLoading(true)
const resultAction = await dispatch(dispatchAction)
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message)
}
setLoadMoreLoading(false)
}
fetchData()
}, [dispatch, dispatchAction, page]) // <-- "dispatchAction" added here
// ...
How to fix it?
Pass a memoized actionCreator function:
In SomeListing.jsx:
export const SomeListing = ({ userId }) => {
const [page, setPage] = useState(1)
// Here: "fetchPropertyByUserIdMemo" is memoized now
const fetchPropertyByUserIdMemo = useMemo(
() => fetchPropertyByUserId({ userId: userId, page: page }),
[page, userId]
)
const [loadMoreLoading, errorMsg] = useFetchData(fetchPropertyByUserIdMemo, page)
// ...
}
How about extracting the fetch method from useEffect?:
const fetchData = async () => {
setLoadMoreLoading(true);
const resultAction = await dispatch(dispatchAction);
if (resultAction.meta.requestStatus === 'rejected') {
setErrorMsg(resultAction.payload.message);
}
setLoadMoreLoading(false);
};
useEffect(() => {
fetchData();
}, [fetchData]);
So I've read these blog posts about using custom hooks to fetch data, so for instance we have a custom hook doing the API call, setting the data, possible errors as well as the spinny isFetching boolean:
export const useFetchTodos = () => {
const [data, setData] = useState();
const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState();
useEffect(() => {
setIsFetching(true);
axios.get('api/todos')
.then(response => setData(response.data)
.catch(error => setError(error.response.data)
.finally(() => setFetching(false);
}, []);
return {data, isFetching, error};
}
And then at the top level of our component we would just call const { data, error, fetching } = useFetchTodos(); and all great we render our component with all the todos fetched.
The thing I don't understand is how would we send dynamic data / parameters to the hook based on the internal state of the component, without breaking the rules of hooks?
For instance, imagine we have a useFetchTodoById(id) hook defined the same way as the above one, how would we pass that id around? Let's say our TodoList component which renders our Todos is the following:
export const TodoList = (props) => {
const [selectedTodo, setSelectedTodo] = useState();
useEffect(() => {
useFetchTodoById(selectedTodo.id) --> INVALID HOOK CALL, cannot call custom hooks from useEffect,
and also need to call our custom hooks at the "top level" of our component
}, [selectedTodo]);
return (<ul>{props.todos.map(todo => (
<li onClick={() => setSelectedTodo(todo.id)}>{todo.name}</li>)}
</ul>);
}
I know for this specific usecase we could pass our selectedTodo through props and call our useFetchTodoById(props.selectedTodo.id) at the top of our component, but I'm just illustrating the issue with this pattern I ran into, we won't always have the luxury of receiving the dynamic data that we need in the props.
Also -- how would we apply this pattern for POST/PUT/PATCH requests which take dynamic data properties?
You should have a basic useFetch hook the accepts a url, and fetches whenever the url changes:
const useFetch = (url) => {
const [data, setData] = useState();
const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState();
useEffect(() => {
if(!url) return;
setIsFetching(true);
axios.get(url)
.then(response => setData(response.data))
.catch(error => setError(error.response.data))
.finally(() => setFetching(false));
}, [url]);
return { data, isFetching, error };
};
Now you can create other custom hook from this basic hook:
const useFetchTodos = () => useFetch('api/todos');
And you can also make it respond to dynamic changes:
const useFetchTodoById = id => useFetch(`api/todos/${id}`);
And you can use it in the component, without wrapping it in useEffect:
export const TodoList = (props) => {
const [selectedTodo, setSelectedTodo] = useState();
const { data, isFetching, error } = useFetchTodoById(selectedTodo.id);
return (
<ul>{props.todos.map(todo => (
<li onClick={() => setSelectedTodo(todo.id)}>{todo.name}</li>)}
</ul>
);
};