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])
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}
/>
}
In following codes, eslint will give a warning.
Line 24:6: React Hook useEffect has a missing dependency: 'fetchPosts'. Either include it or remove the dependency array react-hooks/exhaustive-deps
import { useState, useEffect } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
};
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I fixed the warning by adding fetchPosts. Then I followed eslint instructions using useCallback and adding variables used in fetchPosts to deps. This change causes a loop. How should I fix the loop and eslint warning?
import { useState, useEffect, useCallback } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = useCallback(async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}, [page, posts]);
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location, fetchPosts]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I highly recommend this article to really understand what's going on when you use the useEffect hook. It talks, among other things, about your exact problem and ways to solve it. That said, you should move the function inside the useEffect callback, something like:
export const TimelinePage = () => {
/* ... */
useEffect(() => {
if (location.pathname !== '/') return;
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}
fetchPosts();
}, [location]);
/* ... */
};
It seems like react-query is a quiet popular so, I trying to add react-query to my exist codes.
the code below is the exist codes. it uses hooks (useEffect & useState), axios and returns response data.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
export const useCommonApi = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
useEffect(() => {
try {
const getState = async () => {
const result: any = await apiProvider.get('common/' + url, params);
let resultData = result.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return resultData;
};
getState();
} catch (e) {
console.error(e);
}
}, []);
return State;
};
Here is the my new codes for react-query. I am trying to convert code above into react-query as below.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
if (isLoading) return 'Loading...'
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return State;
};
the my new codes(react-query) prints "too many render" when it is executed.
What did I wrong with it? any help please
You are calling your state update function setState outside of an useEffect. This will run on the first render, update the state, which in turn triggers a rerender, update the state again and you end up in an endless loop. You probably want to wrap that logic into useEffect and only run it if data changes.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
useEffect(() => {
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
}, [data])
if (isLoading) return 'Loading...'
return State;
};
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
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>
)
}