useEffect with many dependencies - javascript

I am trying to fetch employees and here is what I am trying to do using useEffect
function AdminEmployees() {
const navigate = useNavigate();
const dispatch = useDispatch();
// fetching employees
const { adminEmployees, loading } = useSelector(
(state) => state.adminFetchEmployeeReducer
);
useEffect(() => {
dispatch(adminFetchEmployeeAction());
if (adminEmployees === "unAuthorized") {
navigate("/auth/true/false");
}
}, [adminEmployees, navigate,dispatch]);
console.log("Here i am running infinie loop");
console.log(adminEmployees);
return (
<>
{loading ? (
<Loader></Loader>
) : adminEmployees === "no employees" ? (
<h1>No Employees</h1>
) : (
<>
{adminEmployees &&
adminEmployees.map((employee) => {
return (
<div className="admin__employee__container" key={employee.id}>
<AdminSingleEmployee
employee={employee}
></AdminSingleEmployee>
</div>
);
})}
</>
)}
</>
);
}
Here I want to achieve 2 goals:
fetch adminEmployees
if (adminEmployees==='unAuthorized') then go to loginPage
but when doing this as in the code, it creates infinite loop.
How can I achieve the desired functionality?

Easy dirty path: split useEffect into 2
useEffect(() => {
if (adminEmployees === "unAuthorized") {
navigate("/auth/true/false");
}
}, [adminEmployees, navigate]);
useEffect(() => {
dispatch(adminFetchEmployeeAction());
}, [dispatch]);
Better way: handle that case in reducer or action creator to flip flag in the store and then consume it in component:
const { shouldNavigate } = useSelector(state => state.someSlice);
useEffect(() => {
if(shouldNavigate) {
// flipping flag back
dispatch(onAlreadyNavigated()));
navigate("/yourPath...");
},
[navigate, dispatch, shouldNavigate]
);

Related

How to apply Infinite Scroll with react-intersection-observer

I'm using react-intersection-observer npm package for my CRA project.
I want to achieve this Infinite Scroll effect whenever users reach the bottom of the page or click on the Load More button -> It will fetch 8 more items.
Currently, the getMoreData() will only execute when I click on the Load More button.
Also, I put posts state in the dependency array so it will display the first 8 items for activePosts on start. But this leads to status code 429: over rate limit in the Network tab.
My Questions:
How to also apply the getMoreData() function to fetch out more items when we scroll to the end of the page? (Infinity scroll)
There might be a bug if I remove the posts from the dependency array, it won't display the first 8 items on initial page load. How to fix this the right way?
Screenshots:
My Code:
Posts.js
function Posts() {
const url = "https://6264f60294374a2c506b97c9.mockapi.io/posts";
const [posts, setPosts] = useState([]);
const [activePosts, setActivePosts] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const getData = async () => {
try {
let response = await axios(url);
let result = response.data;
setPosts(result);
setActivePosts(posts.slice(0, 8));
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getData();
}, [posts]);
const getMoreData = () => {
setIsFetching(true);
setTimeout(() => {
setActivePosts((prev) => {
return [...prev, ...posts.slice(prev.length + 1, prev.length + 9)];
});
setIsFetching(false);
}, 2000);
};
useEffect(() => {
if (!isFetching) return;
getMoreData();
}, [isFetching]);
return (
<>
<div className="posts">
{activePosts.map((post, index) => (
<Post post={post} key={post.id} index={index} />
))}
</div>
<button onClick={getMoreData}>
{isFetching ? "Loading..." : "Load more"}
</button>
</>
);
}
export default Posts;
Post.js
import { useInView } from "react-intersection-observer";
function Post({ post }) {
const { ref, inView } = useInView({
initialInView: true,
triggerOnce: true,
threshold: 1,
});
return (
<div className="post" ref={ref}>
{inView ?
<img src={post.imgUrl} alt={post.title} className="post__img" loading="lazy" />
: <div className="post__img" />
}
<h1 className="post__title">{post.title}</h1>
</div>
)
}
export default Post

Rendering nested object properties from API call in react

I am trying to render text from an API call, text or numbers that are directly accesible from the axios.data object can render normally, nevertheless when inside the axios.data there is another object with its own properties I cannot render because an error shows, the error is 'undefined is not an object (evaluating 'coin.description.en')', over there description is an object; my code is
function SINGLE_COIN(props) {
const { id } = useParams()
console.log(id);
const SINGLE_API = `https://api.coingecko.com/api/v3/coins/${id}?tickers=true&market_data=true&community_data=true&developer_data=true&sparkline=true`
const [coin, setCoin] = useState({})
useEffect(() => {
axios
.get(SINGLE_API)
.then(res => {
setCoin(res.data)
console.log(res.data)
})
.catch(error => {
console.log(error)
})
}, [])
return (
<div>
<h2>{coin.name}</h2>
<div>{coin.coingecko_score}</div>
<div>{coin.liquidity_score}</div>
<div>{coin.description.en}</div>
<SINGLE_COIN_DATA coin={coin} />
</div>
)
}
Thanks!
For the initial render (data is not fetched yet), it will be empty. so nested property would be undefined.
so note the changes:
Example 1:
const [coin, setCoin] = useState(null);
..
return (
<div>
{coin ? (
<>
<h2>{coin.name}</h2>
<div>{coin.coingecko_score}</div>
<div>{coin.liquidity_score}</div>
<div>{coin.description.en}</div>
</>
) : null}
</div>
);
Example:2: Use the optional chaining while accessing nested property
return (
<div>
<h2>{coin?.name}</h2>
<div>{coin?.coingecko_score}</div>
<div>{coin?.liquidity_score}</div>
<div>{coin?.description?.en}</div>
</div>
);
And the complete code with : working example
export default function SINGLE_COIN() {
const { id } = useParams()
const SINGLE_API = `https://api.coingecko.com/api/v3/coins/${id}?tickers=true&market_data=true&community_data=true&developer_data=true&sparkline=true`;
const [coin, setCoin] = useState(null);
useEffect(() => {
axios
.get(SINGLE_API)
.then((res) => {
setCoin(res.data);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div>
{coin ? (
<>
<h2>{coin.name}</h2>
<div>{coin.coingecko_score}</div>
<div>{coin.liquidity_score}</div>
<div>{coin.description.en}</div>
</>
) : null}
</div>
);
}

How to I wrap a useState variable in a if statment, but still have it's value be available outside the if reactjs

I have the following code I have a cards state variable using useState, I have atttempted to add my array above to it, but it just adds an empty array, I wasn't able to put the state inside of the if becuase then my variable was undefined. I tried wrapping everything beflow the state and the state in the if , but the then I get some return issues. So the focus is passing into the useState(stateReplace)
Any help would be great
import React, { useState, useCallback, useEffect, useMemo } from "react";
import { Card } from "./Card";
import update from "immutability-helper";
import { LeadsBuilderCollection } from "../../api/LeadsCollection";
import { useTracker } from "meteor/react-meteor-data";
const style = {
width: 400,
};
export const Container = ({ params }) => {
const { leadsBuilder, isLoading } = useTracker(() => {
const noDataAvailable = { leadsBuilder: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leadsBuilder");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leadsBuilder = LeadsBuilderCollection.findOne({ _id: params._id });
return { leadsBuilder };
});
const [cards, setCards] = useState([]);
let stateReplace = useMemo(() => {
if (!isLoading && leadsBuilder?.inputs?.length) {
leadsBuilder.inputs.map((leadInput, i) => {
({ id: i, text: leadInput.name });
});
}
return [];
}, [isLoading, leadsBuilder]);
useEffect(() => {
setCards(stateReplace);
}, [setCards, stateReplace]);
const moveCard = useCallback(
(dragIndex, hoverIndex) => {
const dragCard = cards[dragIndex];
setCards(
update(cards, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard],
],
})
);
},
[cards]
);
const renderCard = (card, index) => {
return (
<>
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<Card
key={card.id}
index={index}
id={card.id}
text={card.text}
moveCard={moveCard}
/>
</>
)}
</>
);
};
return (
<>
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<div style={style}>{cards.map((card, i) => renderCard(card, i))}</div>
</>
)}
</>
);
};
Update: I can get it to run if I place a setState in a useEffect but then I get a warning and the drag and drop doesnt work
useEffect(() => {
setCards(stateReplace);
});
Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Update #2
const [cards, setCards] = useState([]);
let stateReplace = useMemo(() => {
console.log("memo");
if (!isLoading && leadsBuilder?.inputs?.length) {
return leadsBuilder.inputs.map((leadInput, i) => {
({ id: i, text: leadInput.name });
});
}
return [];
}, [isLoading]);
console.log(stateReplace);
useEffect(() => {
setCards(stateReplace);
console.log(setCards);
}, [setCards, stateReplace]);
current output
(4) [undefined, undefined, undefined, undefined]
memo
cannot read propery `id`
i would do it like that
//for preveting updates of memo if ledsBulder will changes on each render
const leadsBuilderRef = useRef(leadsBuilder)
let stateReplace = useMemo(()=>{
if (!isLoading && leadsBuilder.current?.inputs?.length) {
return leadsBuilder.current.inputs.map((leadInput, i) => {
return { id: i, text: leadInput.name };
});
}
return []
}, [isLoading, leadsBuilderRef]);
and then
useEffect(() => {
setCards(stateReplace);
}, [setCards, stateRepalce]);

React router-firebase private route with reactContext

I have this user context wit useEffect for firebase authentication. The whole app is wrapped around this.
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState()
useEffect(() => {
auth.onAuthStateChanged((user) => {
setCurrentUser(user)
})
}, [])
return (
<UserContext.Provider value={currentUser}>{children}</UserContext.Provider>
)
}
In my component, I check if the user is authenticated, and I use router history to redirect the user if not.
const history = useHistory()
const currentUser = useContext(UserContext)
const handleRedirect = () => {
return history.push('/login')
}
return (
<>
{currentUser ? (
<div>
<MiniDrawer></MiniDrawer>
<Container maxWidth="md">
<h1>
Hello {currentUser.displayName}
</h1>
<Container>
<h2>Team Activity</h2>
</Container>
</Container>
</div>
) : (
handleRedirect()
)}
</>
Whenever I reload the page and I am logged in it will first execute the handleredirect method and then it will go to the login Page for like half a second) specified in this method and it renders the correct component (first in the condition). What to do about it? It seems that it takes a while for the app to realize if it user exists or not.
useEffect(() => {
setTimeout(() => {
fire.auth().onAuthStateChanged((user) => {
this.setState( { stateChanged: true } )
});
}, 1000)
}, [])

How to show 'loading' before all async requests done?

I am using react hooks in my project and I want to show text 'loading' until I get a response from all requests in current page. How to do this?
There are a number of ways to do this but here is one pattern I tend to use:
const ComponentMakingApiCall = () => {
const [ isLoading, setIsLoading ] = useState(true)
useEffect(() => {
axios.post({
// whatever request you are making
}).then(response => {
setIsLoading(false)
})
},[])
if (isLoading){
return <h1>Loading...</h1>
} else {
return (
<div>
// render some content
</div>
)
}
}
If you have multiple requests, you could do something like this:
const ComponentMakingApiCall = () => {
const [ requestALoading, setRequestALoading ] = useState(true)
const [ requestBLoading, setRequestBLoading ] = useState(true)
useEffect(() => {
axios.post({
// request A
}).then(response => {
handleResponse(response)
setRequestALoading(false)
})
},[])
useEffect(() => {
axios.post({
// request B
}).then(response => {
handleResponse(response)
setRequestBLoading(false)
})
},[])
if ( requestALoading || requestBLoading ) {
return <h1>Loading...</h1>
} else {
return (
<div>
// render some content
</div>
)
}
}
const [isLoading, setIsLoading] = useState(false);
async () => {
setIsLoading(true)
// your codes (fetch API, try/catch, etc)
setIsLoading(false)
}
return (
<React.Fragment>
{isLoading && <LoadingComponentUI />}
{!isLoading && yourCondition && (
<YourComponent />
)}
</React.Fragment>
);

Categories

Resources