UseEffect to update navbar when user is logged in? - javascript

I'm working on a navbar that changes whenever it detects there is a user in local storage using useState and useEffect. Here's my logic:
const [user, setUser] = useState("")
function fetchData(){
const item = JSON.parse(localStorage.getItem('name'))
if(item) {
setUser(item)
}
}
useEffect(() => {
fetchData()
});
return({user? (<LoggedIn />) : (<ClientBar />})
The code essentially begins with no user, and in the fetchData() function it checks whether a user exists in local storage and sets the user based on what's been found in local storage. I can tell the logic works because when I refresh it, it changes from <ClientBar> to <LoggedIn>. However, the problem is it doesn't work upon login - rather, it requires a refresh to update. Is there a way to make it update immediately upon login?

Your useEffect should be:
useEffect(() => {
fetchData()
}, [user])
So when user changes it will check each time. Because you're fetching localstorage I'd also encourage you to add a loading component while that's being done.
Checking localstorage Should also be using async/await:
const fetchData = async () => {
const item = await JSON.parse(localStorage.getItem('name'))
if(item) setUser(item)
}
While this it's waiting for the check you should render a loading with another useState.

As Abhi Patil stated, you need to check localStorage changes. It has nothing to do with useEffect as it is only triggered when the component is mounted.
You need to wrap fetchData() inside storage event listener like so:
useEffect(() => {
window.addEventListener('storage', () => {
const item = JSON.parse(localStorage.getItem('name'))
if(item) {
setUser(item)
}
})
})

Related

onSnapshot in firebase fires multiple times [duplicate]

I use a lot of firestore snapshots in my react native application. I am also using React hooks. The code looks something like this:
useEffect(() => {
someFirestoreAPICall().onSnapshot(snapshot => {
// When the component initially loads, add all the loaded data to state.
// When data changes on firestore, we receive that update here in this
// callback and then update the UI based on current state
});;
}, []);
At first I assumed useState would be the best hook to store and update the UI. However, based on the way my useEffect hook is set up with an empty dependency array, when the snapshot callback gets fired with updated data and I try to modify the current state with the new changes, the current state is undefined. I believe this is because of a closure. I am able to get around it using useRef with a forceUpdate() like so:
const dataRef = useRef(initialData);
const [, updateState] = React.useState();
const forceUpdate = useCallback(() => updateState({}), []);
useEffect(() => {
someFirestoreAPICall().onSnapshot(snapshot => {
// if snapshot data is added
dataRef.current.push(newData)
forceUpdate()
// if snapshot data is updated
dataRef.current.find(e => some condition) = updatedData
forceUpdate()
});;
}, []);
return(
// JSX that uses dataRef.current directly
)
My question is am I doing this correct by using useRef along with a forceUpdate instead of useState in a different way? It doesn't seem right that I'm having to update a useRef hook and call forceUpdate() all over my app. When trying useState I tried adding the state variable to the dependency array but ended up with an infinite loop. I only want the snapshot function to be initialized once and the stateful data in the component to be updated over time as things change on the backend (which fires in the onSnapshot callback).
It would be better if you combine useEffect and useState. UseEffect will setup and detach the listener, useState can just be responsible for the data you need.
const [data, setData] = useState([]);
useEffect(() => {
const unsubscribe = someFirestoreAPICall().onSnapshot(snap => {
const data = snap.docs.map(doc => doc.data())
this.setData(data)
});
//remember to unsubscribe from your realtime listener on unmount or you will create a memory leak
return () => unsubscribe()
}, []);
Then you can just reference "data" from the useState hook in your app.
A simple useEffect worked for me, i don't need to create a helper function or anything of sorts,
useEffect(() => {
const colRef = collection(db, "data")
//real time update
onSnapshot(colRef, (snapshot) => {
snapshot.docs.forEach((doc) => {
setTestData((prev) => [...prev, doc.data()])
// console.log("onsnapshot", doc.data());
})
})
}, [])
I found that inside of the onSnapshot() method I was unable to access state(e.g. if I console.log(state) I would get an empty value.
Creating a helper function worked for, but I'm not sure if this is hack-y solution or not but something like:
[state, setState] = useState([])
stateHelperFunction = () => {
//update state here
setState()
}
firestoreAPICall.onSnapshot(snapshot => {
stateHelperFunction(doc.data())
})
use can get the currentState using callback on set hook
const [state, setState] = useState([]);
firestoreAPICall.onSnapshot(snapshot => {
setState(prevState => { prevState.push(doc.data()) return prevState; })
})
prevState will have Current State Value

React: Await for API fetch and LocalStorage set before render children components

I have the following app entry component:
React.useEffect(() => {
const fetchData = async () => {
try {
const libraries: unknown[] = await sendRequest('/libraries');
const softwareComponents: unknown[] = await sendRequest('/softwareComponents');
localStorage.setItem('libraries', JSON.stringify(arraySetup(libraries, 'libraries')));
localStorage.setItem('softwareComponents', JSON.stringify(arraySetup(softwareComponents, 'software-components')));
} catch (err) {
console.error(err);
}
};
isAuthenticated() && fetchData();
}, []);
I am fetching Arrays from two endpoints and then set the result in the Local Storage, so I can read from it in other components.
A child component is using the data like this:
const [data, setData] = React.useState<Array<any>>([]);
React.useEffect(() => {
const libraries = getLocalStorageItem('libraries');
const softwareComponents = getLocalStorageItem('softwareComponents');
const condition = libraries && softwareComponents;
if (condition) {
setData([...libraries, ...softwareComponents]);
}
}, []);
const getDataLength = (category: string) => {
return (data || []).filter((item: any) => item.category === category).length;
};
return (
<React.Fragment>
<OwcGrid item xs={12} s={4}>
<LibrariesCard numberOfElements={getDataLength('libraries')} /> // rendering here the length of the localStorage item.
</OwcGrid>
The following bug now exists:
Opening the app for the first time, the API is called and the localstorage is set.
But the child components that are using localStorage are rendered at the same time, so if (condition) setData([...libraries, ...softwareComponents]); is never met and the numberOfElements prop is always empty at the first time.
Only at the second refresh, localStorage is in place and I can count the elements out from it and render it.
Can somebody give me a hint to wait for localStorage.setItem in the App.layout or if I can wait and check in the child components as long as the storage is set and then render again?
Do you have to use local storage? You could solve your problem by using context api, where you can store all required values and use them in any component that you need to. If you have to use local storage, you can manage it from context.
Please checkout documentation for context: https://reactjs.org/docs/context.html

How do you constantly refresh a react component every certain amount of time

This question relates a lot with loading and fetching. So I have this react components that loads comments from an API
function App (){
const [com, setCom] = useState([])
const [isLoading, setLoading] = useState(true)
useEffect(() =>{
fetch('https://jsonplaceholder.typicode.com/comments?postId=5').then(ddd => ddd.json())
.then(data => {
setCom(data)
setLoading(false)
})
})
const coms = com.map(data => <h3>{data.body} <br/></h3>)
if(isLoading){
return <h1>Loading</h1>
}
else if(isLoading === false){
return (
<div className="con">
{coms}
</div>
)
}
}
so in this components I have two states one to store the comments and other to store a loading value that will change after it's done fetching the state in the useEffect.
The problem with this code is that let's say the server went down or my internet went out, even if it comes back this component is going to stay in the loading phase forever until the user refreshes the page manually. So how do I make the components re-fetch the data or rather just refresh the components ?.
Thank you in advance
Here are a few improvements on how you can handle above logic.
function App() {
const [com, setCom] = useState([]);
const [isLoading, setisLoading] = useState(false); //Set initial value to false to avoid your component in loading state if the first call fails
const refreshTime = 2000 //How frequently you want to refresh the data, in ms
const fetchComments = async () => {
setisLoading(true) //set to true only when the api call is going to happen
const data = await fetch('https://jsonplaceholder.typicode.com/comments?postId=5').then(ddd => ddd.json()).then(data => {
if(Array.isArray(data)) setCom(data);
})
setisLoading(false); //make sure to set it to false so the component is not in constant loading state
}
useEffect(() => {
const comInterval = setInterval(fetchComments, refreshTime); //This will refresh the data at regularIntervals of refreshTime
return () => clearInterval(comInterval) //Clear interval on component unmount to avoid memory leak
},[])
const coms = com.map(data => <h3>{data.body} <br/></h3>)
if(isLoading){
return <h1>Loading</h1>
}
else if(isLoading === false){
return (
<div className="con">
{coms}
</div>
)
}
}
You could just use setInterval() to update the state with fetched data every second or however long is required. Here is an example:
const [fetchedData, setFetchedData] = useState(/* data type */);
setInterval(() => setFetchedData(/* call to the method */), 1000);
This way, the component will fetch data every second and re-render the component.
Keep in mind though, this should ONLY be used if you know you are going to get an update every so often. If you are uselessly re-rendering and re-fetching data, that will be a constant hinderance towards performance.

How to prevent unnecessary API call in React useEffect?

My HomeScreen component contains an API call to get the current user. Is there a way to have it make an API call ONLY if the user has changed. As of now, if I move away from the screen and then come back it makes an API call and displays the loader which I think is not perfect user experience not to mention that the API request is completely redundant since the user has not changed.
useEffect(() => {
dispatch(userRequest({ userId, userRole }))
}, [dispatch, userId, userRole])
I also tried to use reselect to get the userId and the userRole from Redux and wrapper my HomeScreen component in React.memo but it seems to get rerendered every time
Here is how I get the data from Redux
const userId = useSelector(selectUserId)
const userRole = useSelector(selectUserRole)
My selectors:
const loginInfoSelector = (state: AppStateType) => state.loginInfo
export const selectUserId = createSelector(
loginInfoSelector,
(loginInfo) => loginInfo.id
)
export const selectUserRole = createSelector(
loginInfoSelector,
(loginInfo) => loginInfo.role
)
I would try to remove dispatch from dependency array as:
useEffect(() => {
dispatch(userRequest({ userId, userRole }))
}, [userId, userRole])
In this way it's only triggered once userId or userRole are changing.

how to save array with useEffect React Native

I want save array data using react useEffect. Follow Example with class:
async componentDidMount() {
const users = await AsyncStorage.getItem('users');
if (users) {
this.setState({ users: JSON.parse(users) });
}
}
componentDidUpdate(_, prevState) {
const { users } = this.state;
if (prevState.users !== users) {
AsyncStorage.setItem('users', JSON.stringify(users));
}
}
how to implement the logic with React Hooks?
For componentDidMount logic you can use useEffect hook:
useEffect(() => {
const asyncFetch = async () => {
const users = await AsyncStorage.getItem("users");
if (users) {
// setter from useState
setUsers(JSON.parse(users));
}
};
asyncFetch();
}, []);
For componentDidMount use useEffect with dep array and useRef reference.
const prevUsers = useRef();
useEffect(() => {
const prevUsers = prevUsers.current;
// Some equal check function
if (!areEqual(prevUsers, users)) {
AsyncStorage.setItem("users", JSON.stringify(users));
}
prevUsers.current = users;
}, [users]);
Notice that in your current code, prevState.users !== users is always truley, you comparing two objects and in JS {} !== {} always results true.
You can try like below and you can use hooks in functional based component not class based component
//state declaration similar to class based component
const [usersdata,setUsers] = useState([]);
const users = await JSON.parse(AsyncStorage.getItem('users'));
//whenever the value of users changes useEffect will reset the value of users in state useEffect handle the lifecycle in function based component
useEffect(()=>{
if(users){
setUsers(JSON.parse(users));
}
},[users])
For hooks the logic changes slightly, you would have to "hook" your effect with a state in order to update the component, so the component would update (componentDidUpdate) when the hooked state has been updated, you can obviously hook multiple states.
If you choose to not hook any state, the effect would execute only at the mounting of the component just like (componentDidMount())
I don't see the logic that makes you decide when to update the user state since you always get it from the storage, so I will assume that you have some kind of a trigger that makes you verify if the users value has changed in the storage.
so you can refactor your code like this:
const [users, setUsers] = useState([]);
const [userHasChanged, setUserHasChanged] = useState(false);
usEffect(async () => {
// comparing the old users with the new users is not useful since you always fetch the users from the storage, so the optimal is to always set the new array/ object to users, this way you avoid comparing the two objects which is a bit costly.
const newUsers = await AsyncStorage.getItem("users");
setUsers(JSON.parse(newUsers));
setUserHasChanged(false);
}, [userHasChanged])
// some code that triggers userHasChanged, you use setUserHasChaned(true)

Categories