I am trying to dispatch an action to fecth an API when the home screen appears but I have an infinite loop.
My project :
React Native app
Redux
redux-axios-middleware
React Navigation (I am using a Drawer)
What I tried:
const dispatch = useDispatch();
useEffect(() => {
const fetchNewMails = () =>
return dispatch(ACTIONS.mailActions.fetchMails());
};
fetchNewMails();
}, [dispatch]);
-------
const dispatch = useDispatch();
const fetchNewMails = useCallback(() => {
return dispatch(ACTIONS.mailActions.fetchMails());
}, [dispatch]);
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
fetchNewMails();
});
return unsubscribe;
}, [fetchNewMails, navigation]);
A simple console.log is not producing infinite loop but the dispatch action yes ..
Thanks in advance if you have any idea.
The answer is specific to my project but here it is:
I displayed a LoadingScreen in the App.js depending of Redux variable value, it was causing re-rendering.
Solution : Keep the LoadingScreen independent and delete it from App.js.
Issue solved.
Related
tldr: the await call inside a useEffect hook doesn't resolve itself until after the component starts to unmount, it just hangs until that happens. Not sure why this is happening or how to debug it. This is in a react-native expo project. Swapping the functional component out with a class based one works as expected.
given the following useEffect calls in an expo project
useEffect(() => {
console.log('mount');
return () => {
console.log('unmount');
};
}, []);
useEffect(() => {
const fetch = async () => {
console.log('fetching')
const stuff = await fetchStuff();
console.log('fetched');
};
fetch();
}, [depA, depB]);
What I'm seeing in the console when the component is mounted is
'mount'
'fetching'
then when the component is unmounted I see
'unmount'
'fetched'
For some reason, the await call doesn't resolve until the component is unmounted. I've used this pattern in other parts of my code seemingly without issue so I can't figure out why this is happening here. When I swap the functional component out with a class it's working as expected. Any ideas on why this is happening? It looks like the fetchStuff call is being deferred until the component is about to unmount. Swapping fetchStuff out with await new Promise((res) => res(null)); doesn't seem to make any difference
Full component looks something like
function WhatIsHappening({depA, depB}) {
const [stuff, setStuff] = useState([])
useEffect(() => {
console.log('mount');
return () => {
console.log('unmount');
};
}, []);
useEffect(() => {
const fetch = async () => {
console.log('fetching')
const stuff = await fetchStuff(depA, depB);
console.log('fetched');
setStuff(stuff)
};
fetch();
}, [depA, depB]);
return (
<View>
<ListStuff stuff={stuff}></ListStuff>
<View>
)
}
There is something wrong with fetchStuff. This is a working version.
async function fetchStuff() {
return new Promise((resolve) => resolve("fetched"));
}
Working Sandbox
I have created a custom hook that fetches setting from an api that uses Async-Storage.
// Takes the key/name of the setting to retrieve
export const useSettings = (key) => {
// stores the json string result in the setting variable
const [setting, setSetting] = useState("");
const deviceStorage = useContext(DeviceStorageContext);
useEffect(() => {
getValue()
.then(value => setSetting(value));
}, []);
// gets value from my custom api that uses Async-Storage to handle r/w of the data.
const getValue = async () => await deviceStorage.getValueStored(key);
const setValue = async (value) => {
await deviceStorage.setValueStored(key, value);
getValue().then(value => setSetting(value));
};
const removeValue = async () => { }
return [setting, { setValue,removeValue }];
};
This works as expected in Main.jsx without any problem.
const Main = () => {
const [units, operations] = useSettings('units');
useEffect(() => {
const initSettings = async () => {
if (units) {
console.log(units)
return;
}
await operations.setValue({ pcs: 1, box: 20 });
};
initSettings();
}, []);
However, when I even just call the useSetting hook in Form.jsx and visit the page, it freezes my entire app to just that page.
const FormView = ({ handleReset, handleSubmit }) => {
const [setting,] = useSettings('units');
Removing the useState and useEffect fixes it and calling these methods directly works but I really don't want to call getValue() throughout my project and use async/await code to handle it.
Stuck on this for hours now. Any help will be appreciated.
Thank you.
It was a dropdown component library inside FormView that was messing it up. Removing that library fixed it.
I'm new to React Native code building.
Below is my React Native code to get data from Firebase.
const page_one = () => {
const [isLoading, setIsLoading] = useState(true)
const [placeList, setPlaceList] = useState([])
const [message, setMessage] = useState(false)
const db = firebase.firestore().collection('places')
const onLoad = async () => {
const place_ref = await firebase.firestore().collection('places').get()
if (place_ref.empty) {
setMessage(true)
return
}
const places = []
try {
place_ref.forEach(doc => {
const entity = doc.data()
entity.id = doc.id
places.push(entity)
})
setPlaceList(places)
setMessage(false)
setIsLoading(false)
return
} catch (error) {
console.log("Error:\n", error.message)
return
}
}
}
useEffect(() => {
onLoad()
console.log('place List')
}, [isLoading])
return (<View></View>)
}
I need to refresh the current component every time I render, to get newly added data from firebase. How to make possible this.
As of now component is not loading when I rendering the component 2nd time. it fetches the old data only, not loading the latest data still I refreshing the whole application.
I need to fetch the latest data whenever I render the component.
I tried with below useEffect hook:
useEffect(() => {
onLoad()
console.log('place List')
}, [isLoading, placeList])
But it calls the firebase request n number of times till I existing the current component.
I want to call the firebase request only once when ever I visiting the current component.
please help me..
As far as I understand you need to refresh whenever this component gets focused
So for that, write like this
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
onLoad() // Gets fired whenever this screen is in focus
});
return unsubscribe;
}, [navigation]);
Also don't forget to destructure the props to get the navigation prop
Like this
const page_one = ({ navigation }) => {
...Code Inside
}
I have this action
export function fetchBranches() {
return async dispatch => {
const result = await axios.get('https://localhost:5002/Branches')
dispatch({ type: FETCH_BRANCHES, payload: result.data.value })
}
}
and such reducer
export const branchReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_BRANCHES: {
return { ...state, branches: action.payload }
}
default: return state
}
}
In my component, I'm try to do such thing
const dispatch = useDispatch()
dispatch(fetchBranches())
const branches = useSelector(state => state.branches.branches)
return <>
some component that uses branches
</>
So my problem is i'm getting infinite number of request trying to fetch (I can see them in redux dev tools).
My page are not getting updated, but if go to other page and then return to one that tries perform this fetch, I'm can see values in store and at the page. So questions are:
Where and how should I dispatch actions to fetch some data to render it then?
Why I'm getting that much requests and how can I fix it?
UPD:
Thanks a lot for your answers, but I still see behavior that my component rendered before I received data from api. I wanted to try useState and set state in useEffect, but I can't use useSelector. What should I do to re-render component as soon as my data loaded?
UPD2: now my component look like
function BranchList() {
const [isLoaded, setIsLoaded] = useState(false)
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchBranches())
setIsLoaded(true)
}, [])
const branches = useSelector(state => state.branches.branches)
const headerNames = ["Id", "Name"]
if (!isLoaded) {
return <>Loading...</>
}
return (
<EditableTable
data={branches}
headerNames={headerNames}
onEditConfirm={(row) => dispatch(updateBranch(row))}
onDelete={(id) => dispatch(deleteBranch(id))} />
)
}
Dispatching generally should never be done directly in render, but in a useEffect or an event callback.
In your case,
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchBranches());
},
[dispatch]
)
const branches = useSelector(state => state.branches.branches)
Also please note that you are writing a pretty old style of redux here - please read the official tutorials to learn the recommended "modern" style of redux we are officially recommending. You'll end up writing a lot less code.
Perhaps if you try this:
function BranchList() {
const [isLoaded, setIsLoaded] = useState(false)
const dispatch = useDispatch()
useEffect(() => {
if(!isLoaded) {
dispatch(fetchBranches())
.then(() => setIsLoaded(true))
}
}, [isLoaded])
const branches = useSelector(state => state.branches.branches)
const headerNames = ["Id", "Name"]
if (!isLoaded) {
return <>Loading...</>
}
return (
<EditableTable
data={branches}
headerNames={headerNames}
onEditConfirm={(row) => dispatch(updateBranch(row))}
onDelete={(id) => dispatch(deleteBranch(id))} />
)
}
Your HTTP request action causes side effects in the component. Every state change causes re-rendering the component. To avoid side effects, you should use useEffect hook in your component.
In your component,
const dispatch = useDispatch()
const onFetchBranches = useCallback(() => dispatch(fetchBranches()), [dispatch]);
const branches = useSelector(state => state.branches.branches)
useEffect(() => {
onFetchBranches();
}, [onFetchBranches]);
return <>
some component that uses branches
</>
You should check Reactjs documentation to understand useEffect hook better.
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
dispatch an action into useEffect hook to solve your issue.
Try:
useEffect(() => dispatch(fetchBranches()), [])
You should try putting your dispatch action into an useEffect, so the code will be only executed once like so:
const dispatch = useDispatch()
React.useEffect(() => {
dispatch(fetchBranches())
}, [])
Documentation here.
I'm using react-native new version ( v: 0.62.2 ).
I take action on the page I created and go to another page with react-navigation
function Register({ route, navigation }) {
const [loading, setLoading] = useState(false);
const [name, setName] = useState('');
const _go = () => {
navigation.navigate('Home', { });
}
const _firebase = () => {
firebase.messaging().getToken(firebase.app().options.messagingSenderId).then(x => _token(x)).catch(e => console.log(e));
}
useEffect(() => {
_firebase();
return () => {
_go, _firebase
};
}, [navigation, loading, name]);
}
I call the _go function after doing the above code
What I want to ask is, is the page open when I go to another page with the _go function?
The following code is enough to close the page to improve performance
return () => {
_go, _firebase
};
}, [navigation, loading, name]);
componentWillUnmount is used when you are going to remove event listeners, timers and etc.
But you didn't declare such things in your code.
Anyway if you are going to implement cWU effect then you need to define it on componentDidMount like this
useEffect(() => {
return () {
// remove events and timers
}
}, []);
If you pass emptry array as second parameter it is same as componentDidMount.
Hope this helps you to understand.
In addition please refer to this article about lifeCycles - https://reactjs.org/docs/hooks-effect.html