I defined a useCallback function in a functional component and it is used in useEffect in the same functional component. in this case, Is the function optimized?
import { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchMetadata } from '../features/metadata/metadataSlice';
import { getChangedItems } from '../app/evergreenAPI';
const useWatchChanges = () => {
const dispatch = useDispatch();
const { isLogged } = useSelector((state) => state.auth);
const handleChangedItems = useCallback((changedItems) => {
console.log('...doing something with', changedItems);
}, []);
const fetchChangedItems = async () => {
if (!isLogged) return false;
try {
const changedItems = await getChangedItems();
changedItems &&
setTimeout(() => {
handleChangedItems(changedItems);
});
fetchChangedItems();
} catch (e) {
console.log(e);
fetchChangedItems();
}
};
useEffect(() => {
fetchChangedItems();
}, [isLogged, fetchChangedItems]);
};
export default useWatchChanges;
You are missing dependencies in your useCallback dependency array.
const handleChangedItems = useCallback((changedItems) => {
console.log('...doing something with', changedItems);
}, [changedItems]);
Moreover it does not make any sense to use useCallback here probably, as useCallback also takes execution time and resources. But your handleChangedItems does not do anything that is worth to put into useCallback because of execution time / resources. Please have a look at:
https://kentcdodds.com/blog/usememo-and-usecallback
Related
I am implementing the paging function, click the next page to get the data again, but the function to get the data is a hook function, what should I do?
import React, { useRef, useEffect, useState, useCallback } from 'react';
import type { PaginationProps } from 'antd';
import { Pagination } from 'antd';
import { useGetArticlesQuery } from '../../store/api/articleApi'
const App = () => {
const onChange: PaginationProps['onChange'] = (page) => {
setCurrent(page);
// I want to get articles data through hook useGetArticlesQuery but fail
// const { data, isSuccess } = useGetArticlesQuery()
};
<Pagination current={current} onChange={onChange} total={total} defaultPageSize={amount} />
}
maybe you should rethink the architecture of your app.
try to use the base of the code below and adjust according to your needs
import React, { useEffect, useState } from 'react';
const App = () => {
useEffect(() => {
const options = //...
fetch('https://xxxxxxxx/api/getAllArticles/page=${page}', options)
.then((response) => response.json())
.then((data) => {
console.log('Success:', data);
setData(data)
})
.catch((error) => {
console.error(error);
});
}, [page, data])
const [page, setPage] = useState(0);
const [data, setData] = useState(null);
//..... other logic
return (
// .. all articles
// .. pagination onClick = setPage(// next page)
)
}
good luck!
Hook Can only be used in top level of one component.
So you can't use hook in components callback function.
You can do like below:
import React, { useRef, useEffect, useState, useCallback } from 'react';
import type { PaginationProps } from 'antd';
import { Pagination } from 'antd';
import { useGetArticlesQuery } from '../../store/api/articleApi'
const App = () => {
const { fetchData } = useGetArticlesQuery()
const onChange: PaginationProps['onChange'] = (page) => {
setCurrent(page);
fetchData(page)
};
<Pagination
current={current}
onChange={onChange}
total={total}
defaultPageSize={amount}
/>
}
I wrote a function to make an API call. Typically, I'd just wrap it in a useEffect and throw it in the same file that needs it, but I'm trying to write my code a little cleaner. So I did the following.
In my component.js file, I have the following:
import { apiCall } from '../../../framework/api.js';
import { useEffect, useState } from 'react';
export const Table = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
console.log(apiCall());
}, []);
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
in my api.js file, I have the following:
import axios from 'axios';
import { useState } from 'react';
export const apiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
This always returns an error (Invalid hook call. Hook calls can only be called inside the body of a function component.)
If I rewrite my component.js and include the axios call directly inside useEffect instead of calling the function apiCall() from the external file, it obviously works with no problems.
I think I know it has to do with the fact that I'm using hooks in my apiCall function, and wrapping that call in a useEffect in my component.js. However, if I don't wrap it in a useEffect, it'll just run continuously and I don't want that either.
You have to follow the custom hook naming convention for this to be able to work. You can check out the documentation for that here: https://reactjs.org/docs/hooks-custom.html
Anyway, I believe in this case this should work:
import axios from 'axios';
import { useState } from 'react';
export const useApiCall = () => {
const [ resp, setResp ] = useState();
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
if(resp) return resp;
}
And then in component.js, you would call useApiCall()
Usually, we do it like this
export const useApiCall = () => {
const [ resp, setResp ] = useState();
useEffect(() => {
axios.get('https://some.domain/api/get').then((response) => {
setResp(response.data);
});
}, []);
return resp;
}
and then use it like so
export const Table = () => {
const resp = useApiCall();
return(
<>
{ resp &&
resp.map(([key, value]) => {
console.log("key: " + key);
return(
<SomeComponent />
);
})
}
</>
);
}
The prefix "use" in the function name is important, this is how we define a custom hook.
React Hook "useState" is called in function "apiCall" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
You can use following methods.
import { useState } from 'react';
export const ApiCall = () => {
const [state, setState] = useState();
};
or
import { useState } from 'react';
export const useApiCall = () => {
const [state, setState] = useState();
};
I Have This Code:
import {useContext, useEffect, useState} from 'react';
import {useHistory} from "react-router-dom";
import {MasterContext} from "../../Context/MasterProvider";
import LoginActions from "../../Context/Actions/LoginActions";
const useLoginForm = () => {
const History = useHistory();
const [login, setLogin] = useState({});
const {AuthState: {Authentication: {Loading, Data, Error}}, AuthDispatch}=useContext(MasterContext);
const FormData = (event) => {
const { target: { value, name } } = event;
setLogin({...login, [name]: value});
};
const FormValid =
!login.email?.length ||
!login.password?.length;
const FormSubmit = () => {
LoginActions(login)(AuthDispatch);
}
useEffect(() => {
if(Data) {
if(Data.user) {
History.push("/");
}
}
}, [Data])
return {login, FormData, FormValid, FormSubmit, Loading, Error, Data};
}
export default useLoginForm;
It's work fine but with warnings.
"React Hook useEffect has a missing dependency: 'History'. Either include it or remove the dependency array react-hooks/exhaustive-deps"
You can add History as a dependency, History wont change unless route is changed. So your useEffect hook wont run unless data or History is changed.
useEffect(() => {
if(Data && Data.user) {
History.push("/");
}
}, [Data, History])
I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}
I have multiple modals and the conditions for showing and hiding them are stored in a Redux store. To avoid typos I export the actions like this...
//reduxStuff.js
export const modalNameHide = () => {
return { type: "MODALNAMEHIDE" };
};
export const modalNameShow = () => {
return { type: "MODALNAMESHOW" };
};
Then when I need the functionality for hiding a modal I do this...
//HideModalButton.js
import { modalNameHide } from "reduxStuff";
import { useDispatch } from "react-redux";
const HideModalNameButton = () => {
const dispatch = useDispatch();
return <button onClick = {() => { dispatch(modalNameHide()) }>Close Modal<button>
}
As you can see for every component that needs to update the store, 2 imports must be done, one for useDispatch and one for the needed action (modalNameHide in this example).
So my question boils down to this: Can I import useDispatch in reduxStuff.js and export useDispatch and modalNameHide together?
Something like this...
//reduxStuff.js
import { useDispatch } from "react-redux";
export const modalNameHide = () => {
let dispatch = useDispatch();
dispatch({ type: "MODALNAMEHIDE" });
};
export const modalNameShow = () => {
let dispatch = useDispatch();
dispatch({ type: "MODALNAMESHOW" });
};
Now when I need to close a modal I would just do one import like this...
//HideModalButton.js
import { modalNameHide } from "reduxStuff";
const HideModalNameButton = () => {
return <button onClick = {() => { modalNameHide() }>Close Modal<button>
}
No, this is not valid.
In your first case, you're doing useDispatch() during render, and then you call dispatch(modalNameHide()) during onClick. This is valid and correct usage of useDispatch.
In your theoretical second case, you're doing modalNameHide() during onClick, and this means that useDispatch() will also be used during onClick instead of render (which is incorrect usage of useDispatch).
useDispatch is a react hook, and you're breaking the Rules of Hooks. Read more here: https://reactjs.org/docs/hooks-rules.html
As grumd said, I was breaking the rules of hooks by doing what I did. However I just figured out what the solution is. All I had to do was make a custom hook.
//reduxStuff.js
import { useDispatch } from "react-redux";
export const useModalNameHide = () => {
const dispatch = useDispatch();
return () => {
dispatch({ type: "MODALNAMEHIDE" });
};
};
export const useModalNameShow = () => {
const dispatch = useDispatch();
return () => {
dispatch({ type: "MODALNAMEHIDE" });
};
};
.
//HideModalButton.js
import { useModalNameHide } from "reduxStuff";
const HideModalNameButton = () => {
const modalNameHide = useModalNameHide()
return <button onClick = {() => { modalNameHide() }>Close Modal<button>
}