useEffect is running when any function is running - javascript

First of all, I researched the question a lot, but I could not find a solution. I would appreciate if you help.
functional component
I add the code briefly below. this is not full code
state and props
// blog id
const { id } = props.match.params;
// state
const initialState = {
title: "",
category: "",
admin_id: "",
status: false
};
const [form, setState] = useState(initialState);
const [adminList, setAdminList] = useState([]);
const [articleText, setArticleText] = useState([]);
const [me, setMe] = useState([]);
const [locked, setLocked] = useState(true);
const timerRef = useRef(null);
// queries and mutations
const { loading, error, data } = useQuery(GET_BLOG, {
variables: { id }
});
const { data: data_admin, loading: loading_admin } = useQuery(GET_ADMINS);
const [editBlog, { loading: loadingUpdate }] = useMutation(
UPDATE_BLOG
);
const [lockedBlog] = useMutation(LOCKED_BLOG);
multiple useEffect and functions
useEffect(() => {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
}, [loading, data]);
useEffect(() => {
if (!loading_admin && data_admin) {
const me = data_admin.admins.filter(
x => x.id === props.session.activeAdmin.id
);
setAdminList(data_admin);
setMe(me[0]);
}
}, [data_admin, loading_admin]);
useEffect(() => {
const { id } = props.match.params;
lockedBlog({
variables: {
id,
locked: locked
}
}).then(async ({ data }) => {
console.log(data);
});
return () => {
lockedBlog({
variables: {
id,
locked: false
}
}).then(async ({ data }) => {
console.log(data);
});
};
}, [locked]);
// if loading data
if (loading || loading_admin)
return (
<div>
<CircularProgress className="loadingbutton" />
</div>
);
if (error) return <div>Error.</div>;
// update onChange form
const updateField = e => {
setState({
...form,
[e.target.name]: e.target.value
});
};
// editor update
const onChangeEditor = text => {
const currentText = articleText.text;
const newText = JSON.stringify(text);
if (currentText !== newText) {
// Content has changed
if (timerRef.current) {
clearTimeout(timerRef.current);
}
setArticleText({ text: newText });
if (!formValidate()) {
timerRef.current = setTimeout(() => {
onSubmitAuto();
}, 10000);
}
}
};
// auto save
const onSubmitAuto = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
editBlog({
variables: {
id,
admin_id,
title,
text: articleText.text,
category,
status
}
}).then(async ({ data }) => {
console.log(data);
});
};
// validate
const formValidate = () => {
const { title, category } = form;
return !title || !articleText.text || !category;
};
// clear state
const resetState = () => {
setState({ ...initialState });
};
return (
// jsx
)
first issue, when call onSubmitAuto, first useEffect is running again. i dont want this.
because I just want it to work on the first mount.
second issue, if the articleText state has changed before, when mutation it does not mutate the data in the form state. but if the form state changes first, it mutated all the data. I think this issue is the same as the first issue.
I hope I could explain the problem. :/

Ciao, I have an answer to the first issue: when onSubmitAuto is triggered, it calls editBlog that changes loading. And loading is on first useEffect deps list.
If you don't want that, a fix could be something like that:
const isInitialMount = useRef(true);
//first useEffect
useEffect(() => {
if(isInitialMount.current) {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
if (data !== undefined) isInitialMount.current = false;
}
}, [loading, data]);

Related

How to make observer unchecked when page is first rendered

I am implementing infinity scroll.
When i first enter the screen observerRef will be checked
The page State goes straight to 1 Send two axios requests
I tried many ways, but couldn't solve it, please help
here is my code
function SearchCookRoom() {
const [page, setPage] = useState(0);
const observerRef = useRef(true);
const preventObserverRef = useRef(true);
const endRef = useRef(false);
const observerHandler = entries => {
// console.log(entries);
const target = entries[0];
if (
!endRef.current &&
target.isIntersecting &&
preventObserverRef.current
) {
preventObserverRef.current = false;
setPage(prev => prev + 1);
}
};
useEffect(() => {
const observer = new IntersectionObserver(observerHandler, {
threshold: 0.5,
});
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
observer.disconnect();
};
}, []);
const onSaveEnteredItem = item => {
setEnterdItme(item);
};
const getData = useCallback(async () => {
try {
const allCookRoom = await axios({
url: `${
!enterdItme
? `${LIST_URL}?page=${page}&size=15`
: `${SEARCH_URL}/${enterdItme}?page=${page}&size=15`
}`,
});
if (page === allCookRoom.data.totalPages) {
endRef.current = true;
}
if (enterdItme) {
setCookRoom([]);
setPage(0);
}
setCookRoom(prev => {
return [...new Set([...prev, ...allCookRoom.data.content])];
});
preventObserverRef.current = true;
} catch (error) {
console.log(error);
}
}, [page, enterdItme]);
useEffect(() => {
getData();
}, [enterdItme, page]);
console.log(cookRoom);
return (
<>
// Some code
<li ref={observerRef}/>
</>
);
}
observerRef checks if the scroll is at the bottom of the page
The page State starts at 0 As soon as it is rendered it changes to 1 and sends an axios request

React - how can I make the return of JSX wait until my useEffect() ended [duplicate]

I have fetch method in useEffect hook:
export const CardDetails = () => {
const [ card, getCardDetails ] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => getCardDetails(data))
}, [id])
return (
<DetailsRow data={card} />
)
}
But then inside DetailsRow component this data is not defined, which means that I render this component before data is fetched. How to solve it properly?
Just don't render it when the data is undefined:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data));
}, [id]);
if (card === undefined) {
return <>Still loading...</>;
}
return <DetailsRow data={card} />;
};
There are 3 ways to not render component if there aren't any data yet.
{data && <Component data={data} />}
Check if(!data) { return null } before render. This method will prevent All component render until there aren't any data.
Use some <Loading /> component and ternar operator inside JSX. In this case you will be able to render all another parts of component which are not needed data -> {data ? <Component data={data} /> : <Loading>}
If you want to display some default data for user instead of a loading spinner while waiting for server data. Here is a code of a react hook which can fetch data before redering.
import { useEffect, useState } from "react"
var receivedData: any = null
type Listener = (state: boolean, data: any) => void
export type Fetcher = () => Promise<any>
type TopFetch = [
loadingStatus: boolean,
data: any,
]
type AddListener = (cb: Listener) => number
type RemoveListener = (id: number) => void
interface ReturnFromTopFetch {
addListener: AddListener,
removeListener: RemoveListener
}
type StartTopFetch = (fetcher: Fetcher) => ReturnFromTopFetch
export const startTopFetch = function (fetcher: Fetcher) {
let receivedData: any = null
let listener: Listener[] = []
function addListener(cb: Listener): number {
if (receivedData) {
cb(false, receivedData)
return 0
}
else {
listener.push(cb)
console.log("listenre:", listener)
return listener.length - 1
}
}
function removeListener(id: number) {
console.log("before remove listener: ", id)
if (id && id >= 0 && id < listener.length) {
listener.splice(id, 1)
}
}
let res = fetcher()
if (typeof res.then === "undefined") {
receivedData = res
}
else {
fetcher().then(
(data: any) => {
receivedData = data
},
).finally(() => {
listener.forEach((cb) => cb(false, receivedData))
})
}
return { addListener, removeListener }
} as StartTopFetch
export const useTopFetch = (listener: ReturnFromTopFetch): TopFetch => {
const [loadingStatus, setLoadingStatus] = useState(true)
useEffect(() => {
const id = listener.addListener((v: boolean, data: any) => {
setLoadingStatus(v)
receivedData = data
})
console.log("add listener")
return () => listener.removeListener(id)
}, [listener])
return [loadingStatus, receivedData]
}
This is what myself needed and couldn't find some simple library so I took some time to code one. it works great and here is a demo:
import { startTopFetch, useTopFetch } from "./topFetch";
// a fakeFetch
const fakeFetch = async () => {
const p = new Promise<object>((resolve, reject) => {
setTimeout(() => {
resolve({ value: "Data from the server" })
}, 1000)
})
return p
}
//Usage: call startTopFetch before your component function and pass a callback function, callback function type: ()=>Promise<any>
const myTopFetch = startTopFetch(fakeFetch)
export const Demo = () => {
const defaultData = { value: "Default Data" }
//In your component , call useTopFetch and pass the return value from startTopFetch.
const [isloading, dataFromServer] = useTopFetch(myTopFetch)
return <>
{isloading ? (
<div>{defaultData.value}</div>
) : (
<div>{dataFromServer.value}</div>
)}
</>
}
Try this:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
if (!data) {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data))
}
}, [id, data]);
return (
<div>
{data && <DetailsRow data={card} />}
{!data && <p>loading...</p>}
</div>
);
};

REACT, Help me understand prevState

What is the difference between defining this counterHandler like this
counterHandler = () => {
this.setState(() => {
return { times: this.state.times + 1 }
});
}
And this?
counterHandler = () => {
this.setState((prevState) => {
return { times: prevState.times + 1 }
});
}
Does the state from a component always gets passed to setState automatically?
If you only use One data inside state, you dont need to make callback of prevState,
but if your state more than one, you need to callback of prevstate because this will make your other and previous data will not be lost.
for example
const [state, setState] = useState({
loading: false,
data: []
})
const handleLoading = () => {
setState({
loading: true
})
}
const handleData = () => {
setState({
data: [a,b,c] // you will lost your loading = true
})}
const handleData = () => {
setState((prevState) => {
...prevState,
data: [a,b,c] // you still have loading = true
})
}

Cleaning component states useEffect

I have states :
const { id } = useParams<IRouterParams>();
const [posts, setPosts] = useState<IPost[]>([]);
const [perPage, setPerPage] = useState(5);
const [fetchError, setFetchError] = useState("");
const [lastPostDate, setLastPostDate] = useState<string | null>(null);
// is any more posts in database
const [hasMore, setHasMore] = useState(true);
and useEffect :
// getting posts from server with first render
useEffect(() => {
console.log(posts);
fetchPosts();
console.log(hasMore, lastPostDate);
return () => {
setHasMore(true);
setLastPostDate(null);
setPosts([]);
mounted = false;
return;
};
}, [id]);
When component change (by id), I would like to clean/reset all states.
My problem is that all states are still the same, this setState functions in useEffect cleaning function doesn't work.
##UPDATE
// getting posts from server
const fetchPosts = () => {
let url;
if (lastPostDate)
url = `http://localhost:5000/api/posts/getPosts/profile/${id}?limit=${perPage}&date=${lastPostDate}`;
else
url = `http://localhost:5000/api/posts/getPosts/profile/${id}?limit=${perPage}`;
api
.get(url, {
headers: authenticationHeader(),
})
.then((resp) => {
if (mounted) {
if (resp.data.length === 0) {
setFetchError("");
setHasMore(false);
setPosts(resp.data);
return;
}
setPosts((prevState) => [...prevState, ...resp.data]);
if (resp.data.length < perPage) setHasMore(false);
setLastPostDate(resp.data[resp.data.length - 1].created_at);
setFetchError("");
}
})
.catch((err) => setFetchError("Problem z pobraniem postów."));
};
if your component isnt unmounted, then the return function inside useEffect will not be called.
if only the "id" changes, then try doing this instead:
useEffect(() => {
// ... other stuff
setHasMore(true);
setLastPostDate(null);
setPosts([]);
return () => { //...code to run on unmount }
},[id]);
whenever id changes, the codes inside useEffect will run. thus clearing out your states.
OK, I fixed it, don't know if it is the best solution, but works...
useEffect(() => {
setPosts([]);
setHasMore(true);
setLastPostDate(null);
return () => {
mounted = false;
return;
};
}, [id]);
// getting posts from server with first render
useEffect(() => {
console.log(lastPostDate, hasMore);
hasMore && !lastPostDate && fetchPosts();
}, [lastPostDate, hasMore]);

multiple custom react hooks in the same component

Is it an acceptable practice to have two custom react hooks in the same component, one after another?
The issue I am dealing with is as follows:
The first custom hook useBudgetItems will load, but the subsequent one will be undefined. I think I understand why it's happening (my budgetSettings property inside my useBudgetSettings loads after the console.log() statement), but I am not sure how to get around this and whether this is the right approach.
const BudgetCost ({ projectId }) => {
const { budgetCost, loaded } = useBudgetCost({ key: projectId });
const { budgetSettings } = useBudgetSettings({ key: projectId });
const [totalBudget, setTotalBudget] = useState(budgetCost.totalBudget);
const [budgetCosts, setbudgetCosts] = useState(budgetCost.items);
// This will be undefined
console.log(budgetSettings)
if(!loaded) return <div/>
return (
...
...
)
});
My useBudgetCost custom hook is as follow (the useBudgetSettings isn't much different in the mechanics.
const useBudgetCost = ({ key, notifyOnChange }) => {
const [loaded, setIsLoaded] = useState(false)
const { budgetCost, setBudgetCost } = useContext(ProjectContext)
useEffect(() => {
if(key)
return getBudgetCost(key);
},[key]);
const getBudgetCost = (key) => {
let { budgetCosts, loaded } = UseBudgetCostsQuery(key);
setBudgetCost(budgetCosts);
setIsLoaded(loaded);
}
let onBudgetCostChange = (update) => {
let tempBudgetCostItems = copyArrayReference(budgetCost);
tempBudgetCostItems = {
...tempBudgetCostItems,
...update
}
setBudgetCost(tempBudgetCostItems)
if (notifyOnChange)
notifyOnChange(update)
}
return {
loaded,
budgetCost,
onBudgetCostChange
}
}
useBudgetSettings component:
const useBudgetSetting = ({ key }) => {
const [loaded, setIsLoaded] = useState(false)
const { budgetSettings, setBudgetSettings } = useContext(ProjectCondext)
const globalContext = useContext(GlobalReferenceContext);
useEffect(() => {
if(key)
return getBudgetSetting(key);
},[key]);
const getBudgetSetting = (key) => {
let { budgetSettings, loaded } = UseBudgetSettingsQuery(key);
console.log(budgetSettings);
setBudgetSettings(budgetSettings);
setIsLoaded(loaded);
}
const getBudgetReferences = (overrideWithGlobal = false) => {
if(overrideWithGlobal)
return globalContext.getBudgetReferences();
return budgetSettings.map((item) => { return { value: item.key, label: item.costCode } });
}
const getCategoryText = (key) => _.get(_.find(getBudgetReferences(), (bc) => bc.value === key), 'label');
return {
loaded,
budgetSettings,
getCategoryText,
getBudgetReferences
}
}

Categories

Resources