how do I convert classic reactjs to hooks? - javascript

I am trying to covert the following to hooks, but however I am getting some issues to translate this line this.fetchPlaces = debounce(this.fetchPlaces, 200); what is the exact match for hooks?
state = {
q: '',
places: [],
}
fetchPlaces(q) {
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
this.setState({places: response.data.places});
});
}
constructor(props) {
super(props);
this.fetchPlaces = debounce(this.fetchPlaces, 200);
}```

I'd recommend you to go through React Hooks' official guide, by the end of the guide lies a full example of how to use react hooks.
You should use useState hook to manage states and use useEffect hook to load data from network.
const Foo = () =>{
// all hooks should be used in top level of the function component.
// orders matter.
// https://www.reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
const [q, setQ] = useState(''); // use hooks to declare q
const [places, setPlaces] = useState([]); // use hooks to declare places
// load data from network after React Nodes are rendered
useEffect(()=>{
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
setPlaces(response.data.places); // call setPlaces to set state
});
}
,[1]); // passing a constant to the second Array parameter to strict the hook be called only for once, if left undefined, useEffect will be called after every render.
return // React Nodes
}
I've used react hooks for a while, you can checkout my full example here.
https://github.com/ShinChven/bootcamp/blob/master/console/src/pages/Admin/Users/Users.tsx
I do not see why you should call network with debounce, but here's a post for that.
https://www.freecodecamp.org/news/debounce-and-throttle-in-react-with-hooks/

const fetchPlaces = (q) => {
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
setPlaces(response.data.places);
});
}
const deboucedFetchPlaces = debounce(fetchPlaces, 200)
Then in your code you can call deboucedFetchPlaces

You can do it like this.
const fetchMore = _.debounce(fetchPlaces, 500);
fetchPlaces(q) {
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
this.setState({places: response.data.places});
});
}
fetchMore();

Related

react component constantly fetching and updating data for no reason

I have a react component that I'm fetching data with to visualize.
The data fetching is happening constantly, instead of just once as is needed. I was wondering if there is a way to reduce this happening.
The component is like this,
export default function Analytics() {
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
return (
I'm wondering if I should be using something like componentDidMount() here with componentDidUpdate() ?
UseAnalytics is another component specifically for fetching data, basically just a series of fetches.
There are different buttons to click on the site that can change the data requested, so I do want to be able to change the state of these data objects / request more, i.e., I filter the data based on dates. But confused how to stop it just constantly requesting data.
Thanks in advance,
Update to share the function being called.
export default function useAnalytics() {
let d = new Date();
d.setMonth(d.getMonth() - 1);
const [dateRange, setDateRange] = useState([d.getTime(), Date.now()]);
const [startDate, endDate] = dateRange;
const { data: sentimentData } = useSWR(
`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`,
fetchSentiment
);
const { data: expressionsData } = useSWR(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);
return {
sentimentData,
expressionsData,
overall,
handleChange,
setDateRange,
sentimentStatistical,
startDate,
endDate,
};
}
The apirequest is like this,
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
With the answers,
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
This seems about right, I'm wondering how to substantiate the constants outside of useAnalytics?
In the functional component we don't have componentDidMount() or componentDidUpdate () but we have the workaround for the same by using the useEffect react hook.
For implementing the functionality of componentDidMount() we can utilize the following code snippet
useEffect(()=>{
// this is the callback function that needs to be called only once (when the component has mounted)
},[])
And for implementing the functionality of componentDidUpdate() we can utilize the following code snippet
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
First of all, You are using function component, here you cannot use ComponentDidMount() or ComponentDidUpdate() as they only work in class Components. You will have to use useEffect() but you haven't provided any additional code to understand the situation here.
Anyway if you are setting some state on button click when you fetch data and it is occurring again and again it's probably because you haven't used second argument on useEffect() which states that this useEffect() will only run when the second argument changes.
So to answer your question, pass second argument to useEffect() that you are setting when you click a button.
useEffect(() => {
//
}, [state])
As per your code it's functional component and you can't use componentDidMount() and componentDidUpdate() method as this functions are used in class component.
If you want prevent constantly updating component then there are different ways for both class and functional component.
For Class Component: Only executes if old and new data doesn't match. this function does comparison between old and new value.
componentDidUpdate(prevProps, prevState) { if (prevState.data !== this.state.data) { // Now fetch the new data here. } }
For Functional Component: This is only execute when previous state change.
useEffect(() => { // your data }, [stateName])

Is there a way to put react hooks in a function that's not function component?

I'm trying to create a function that is not a component nor hooks that's callable on speficic event. Let's say i have a simple function that post a data using axios and i want to use navigate after the post is successfull. Here's the example
export const authLogin = (email, password) => {
const config = {
withCredentials: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': Cookies.get('csrftoken')
}
}
let navigate = useNavigate();
return dispatch => {
console.log('Masuk ke dalam auth file');
dispatch(authStart());
axios.post('/log_in/', {
email: email,
password: password
}, config)
.then(res => {
if (res.data.error) {
alert(res.data.error)
dispatch(authFail(res.data.error))
}
else {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeout(3600));
alert('login berhasil')
navigate("/", { replace: true });
}
})
.catch(err => {
alert(err);
dispatch(authFail(err))
})
}
}
I have an error that says
but when i try to change the function name with an uppercase letter, another problem occured, how do i resolve this problem?
A hook must be attached to a fiber which is directly attached to the React component tree. You CANNOT use hooks outside the component tree because React can't keep track of them (this is why hooks must always be run in the same order, and can't be conditional, because React keeps track of their state internally).
The only time you can use a hook outside of a component, is from another hook.
In short, you must be able to draw a straight line back from the hook call to React rendering the component tree. If you cannot, then it's an invalid hook call.
THE SOLUTION
...in your case - is to simply pass in the navigate function to your action as a parameter:
const MyComponent = () => {
const navigate = useNavigate();
const doSomethingHandler = () => {
dispatch(authLogin(email,password,navigate))
}
}
const authLogin = (email,password,navigate) => {
// ...do your action and call the `navigate` parameter
// when you need to
}

Why is my custom hook called so many times?

I'm trying to implement a custom hook to provide the app with a guest shopping cart. My hook wraps around the useMutation hook from Apollo and it saves the shopping cart id in a cookie while also providing a function to "reset" the cart (basically, to remove the cookie when the order is placed).
Code time! (some code omitted for brevity):
export const useGuestCart = () => {
let cartId;
const [createCart, { data, error, loading }] = useMutation(MUTATION_CREATE_CART);
console.log(`Hook!`);
if (!cartId || cartId.length === 0) {
createCart();
}
if (loading) {
console.log(`Still loading`);
}
if (data) {
console.log(`Got cart id ${data.createEmptyCart}`);
cartId = data.createEmptyCart;
}
const resetGuestCart = useCallback(() => {
// function body here
});
return [cartId, resetGuestCart];
};
In my component I just get the id of the cart using let [cartId, resetCart] = useGuestCart(); .
When I run my unit test (using the Apollo to provide a mock mutation) I see the hooked invoked several times, with an output that looks something like this:
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:59
Still loading
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:62
Got cart id guest123
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
I'm only getting started with hooks, so I'm still having trouble grasping the way they work. Why so many invocations of the hook?
Thank you for your replies!
Think of hooks as having that same code directly in the component. This means that every time the component renders the hook will run.
For example you define:
let cartId;
// ...
if (!cartId || cartId.length === 0) {
createCart();
}
The content inside the statement will run on every render as cartId is created every time and it doesn't have any value assigned at that point. Instead of using if statements use useEffect:
export const useGuestCart = () => {
const [cartId, setCartId] = useState(0);
const [createCart, { data, error, loading }] = useMutation(
MUTATION_CREATE_CART
);
const resetGuestCart = () => {
// function body here
};
useEffect(() => {
if(!cartId || cartId.length === 0){
createCart();
}
}, [cartId]);
useEffect(() => {
// Here we need to consider the first render.
if (loading) {
console.log(`Started loading`);
} else {
console.log(`Finished loading`);
}
}, [loading]);
useEffect(() => {
// Here we need to consider the first render.
console.log(`Got cart id ${data.createEmptyCart}`);
setCartId(data.createEmptyCart);
}, [data]);
return [cartId, resetGuestCart];
};
Also notice that there is no actual benefit from using useCallback if the component which is receiving the function is not memoized.

Store data from useQuery with useState

I'm using React hooks both to fetch GraphQL data with react-apollo and to store local state:
const [userData, setUserData] = useState({})
const { loading, error, data } = useQuery(USER_QUERY)
However, I'm wondering how to store data to userData. Is this how it's supposed to work:
useEffect(() => {
setUserData(data)
}, [Object.entries(data).length])
Looks like what you have probably works. There is also a onCompleted option available in the options parameter. it takes a callback of type:
(data: TData | {}) => void
so this is another way of doing it:
const { loading, error, data } = useQuery(USER_QUERY, {onCompleted: setUserData})
What are you trying to do with the returned data that you are unable to accomplish by simply using it as destructured from the query hook? In most use cases it can be used immediately, as it will update itself when refetched.
If it is necessary (and it could be), as the other answer says, the useEffect hook you posted should work, but I would replace the dependency with simply data, to prevent an edge case where the response has an equal length consisting of different data and does not update:
useEffect(() => {
setUserData(data)
}, [data])
I think something like this would work - you will need to create the initial state with useState, could be empty array and then onComplete in the useQuery would setTranscationsData... it is triggered every render when state or props change. Could of course add an inital state inside useState which insn't an empty array.
const [transactionsData, setTransactionsData] = React.useState([]);
const { error, data } = useQuery(GET_TRANSACTIONS, {
onCompleted: () => {
setTransactionsData(data.transactions);
},
});
another example
const [getLegalStatement] = useLazyQuery(GET_LEGAL_STATEMENT, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
setTempLegalStatement(data.getLegalStatement);
},
onError: () => {
setTempLegalStatement({
consentedLegalStatementHash: '',
consentedSuppliersHash: '',
statement: '',
suppliersModal: '',
});
setTimeout(() => {
setRefetchNeeded(true);
}, 10000);
},
});
Use onSuccess
const [userData, setUserData] = useState({})
const { data, isLoading, error } = useQuery('QueryKey', QueryFunction, { onSuccess: setUserData })
This onSuccess callback function will fire setUserData(data) for you automatically any time the query successfully fetches new data.
To elaborate above, you can't use onSuccess/onSettled because those will not rerun if the data is cached, so if you leave the component and come back before the query expires your data won't get set.

Trying call useQuery in function with react-apollo-hooks

I want to call useQuery whenever I need it,
but useQuery can not inside the function.
My trying code is:
export const TestComponent = () => {
...
const { data, loading, error } = useQuery(gql(GET_USER_LIST), {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
...
...
const onSaveInformation = async () => {
try {
await updateInformation({...})
// I want to call useQuery once again.
} catch (e) {
return e
}
}
...
How do I call useQuery multiple times?
Can I call it whenever I want?
I have looked for several sites, but I could not find a solutions.
From apollo docs
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to
events other than component rendering
I suggest useLazyQuery. In simple terms, useQuery will run when your component get's rendered, you can use skip option to skip the initial run. And there are some ways to refetch/fetch more data whenever you want. Or you can stick with useLazyQuery
E.g If you want to fetch data when only user clicks on a button or scrolls to the bottom, then you can use useLazyQuery hook.
useQuery is a declarative React Hook. It is not meant to be called in the sense of a classic function to receive data. First, make sure to understand React Hooks or simply not use them for now (90% of questions on Stackoverflow happen because people try to learn too many things at once). The Apollo documentation is very good for the official react-apollo package, which uses render props. This works just as well and once you have understood Apollo Client and Hooks you can go for a little refactor. So the answers to your questions:
How do I call useQuery multiple times?
You don't call it multiple times. The component will automatically rerender when the query result is available or gets updated.
Can I call it whenever I want?
No, hooks can only be called on the top level. Instead, the data is available in your function from the upper scope (closure).
Your updateInformation should probably be a mutation that updates the application's cache, which again triggers a rerender of the React component because it is "subscribed" to the query. In most cases, the update happens fully automatically because Apollo will identify entities by a combination of __typename and id. Here's some pseudocode that illustrates how mutations work together with mutations:
const GET_USER_LIST = gql`
query GetUserList {
users {
id
name
}
}
`;
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $name: String!) {
updateUser(id: $id, update: { name: $name }) {
success
user {
id
name
}
}
}
`;
const UserListComponen = (props) => {
const { data, loading, error } = useQuery(GET_USER_LIST);
const [updateUser] = useMutation(UPDATE_USER);
const onSaveInformation = (id, name) => updateUser({ variables: { id, name });
return (
// ... use data.users and onSaveInformation in your JSX
);
}
Now if the name of a user changes via the mutation Apollo will automatically update the cache und trigger a rerender of the component. Then the component will automatically display the new data. Welcome to the power of GraphQL!
There's answering mentioning how useQuery should be used, and also suggestions to use useLazyQuery. I think the key takeaway is understanding the use cases for useQuery vs useLazyQuery, which you can read in the documentation. I'll try to explain it below from my perspective.
useQuery is "declarative" much like the rest of React, especially component rendering. This means you should expect useQuery to be called every render when state or props change. So in English, it's like, "Hey React, when things change, this is what I want you to query".
for useLazyQuery, this line in the documentation is key: "The useLazyQuery hook is perfect for executing queries in response to events other than component rendering". In more general programming speak, it's "imperative". This gives you the power to call the query however you want, whether it's in response to state/prop changes (i.e. with useEffect) or event handlers like button clicks. In English, it's like, "Hey React, this is how I want to query for the data".
You can use fetchMore() returned from useQuery, which is primarily meant for pagination.
const { loading, client, fetchMore } = useQuery(GET_USER_LIST);
const submit = async () => {
// Perform save operation
const userResp = await fetchMore({
variables: {
// Pass any args here
},
updateQuery(){
}
});
console.log(userResp.data)
};
Read more here: fetchMore
You could also use useLazyQuery, however it'll give you a function that returns void and the data is returned outside your function.
const [getUser, { loading, client, data }] = useLazyQuery(GET_USER_LIST);
const submit = async () => {
const userResp = await getUser({
variables: {
// Pass your args here
},
updateQuery() {},
});
console.log({ userResp }); // undefined
};
Read more here: useLazyQuery
You can create a reusable fetch function as shown below:
// Create query
const query = `
query GetUserList ($data: UserDataType){
getUserList(data: $data){
uid,
first_name
}
}
`;
// Component
export const TestComponent (props) {
const onSaveInformation = async () => {
// I want to call useQuery once again.
const getUsers = await fetchUserList();
}
// This is the reusable fetch function.
const fetchUserList = async () => {
// Update the URL to your Graphql Endpoint.
return await fetch('http://localhost:8080/api/graphql?', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
}).then(
response => { return response.json(); }
).catch(
error => console.log(error) // Handle the error response object
);
}
return (
<h1>Test Component</h1>
);
}
Here's an alternative that worked for me:
const { refetch } = useQuery(GET_USER_LIST, {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
}
);
const onSaveInformation = async () => {
try {
await updateInformation({...});
const res = await refetch({ variables: { ... }});
console.log(res);
} catch (e) {
return e;
}
}
And here's a similar answer for a similar question.
Please use
const { loading, data, refetch } = useQuery(Query_Data)
and call it when you need it i.e
refetch()

Categories

Resources