react/ state not being updated because of cleanup - reactjs

I have custom hook that adds user info for posts created. And if I don't add cleanup it works as intended. I create new post, press post and it gets added to screen, but with if(mounted.current)setState() it does not update, only on refresh. What could be the problem and how could I fix it?
const AllPostsAssign = () => {
const { userPosts, allUsers } = useData();
const [posts, setPosts] = useState();
const mounted = useRef(true);
// map existing posts and add user object and post id into post object.
useEffect(() => {
const postsWithUsers = allUsers.map((y) => {
const usersAssignedToPosts = userPosts.map((x) => {
if (y.userId === x.data().userId) {
const q = Object.assign(x.data(), { id: x.id });
const z = Object.assign(q, { user: y });
return z;
}
});
return usersAssignedToPosts;
});
const noUndefined = postsWithUsers.flat().filter((post) => post);
// without mounted.current it works.
if (noUndefined && mounted.current) setPosts(noUndefined);
console.log(mounted.current);
console.log(posts);
return () => (mounted.current = false);
}, [userPosts, allUsers]);
// sort by time created and then reverse, so newest posts would be on top.
posts &&
posts.sort((a, b) => {
return a.createdAt.seconds - b.createdAt.seconds;
});
posts && posts.reverse();
return posts;
};
export default AllPostsAssign;

Have your mounted check declared directly inside your useEffect, as such:
useEffect(() => {
let mounted = true;
const postsWithUsers = allUsers.map((y) => {
const usersAssignedToPosts = userPosts.map((x) => {
if (y.userId === x.data().userId) {
const q = Object.assign(x.data(), { id: x.id });
const z = Object.assign(q, { user: y });
return z;
}
});
return usersAssignedToPosts;
});
const noUndefined = postsWithUsers.flat().filter((post) => post);
// without mounted.current it works.
if (noUndefined && mounted) setPosts(noUndefined);
console.log(mounted);
console.log(posts);
return () => (mounted = false);
}, [userPosts, allUsers]);

Related

How to use useNavigate outside react hook?

Gets list of emails from firestore and checks if current user is registered and then redirects them to sign up if they are new user.
The code is functional(it redirects succesfully) but get the following error:
arning: Cannot update a component (BrowserRouter) while rendering a different component You should call navigate() in a React.useEffect(), not when your component is first rendered.
const navigate = useNavigate();
let hasEmail = false;
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
const emailCheck = (emails) => { //checks if email exists
hasEmail = emails.some((e) => e.email === auth.currentUser.email);
};
const direct = () => { // redirects to required page
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
emailCheck(emailList);
direct();
Move the email checking logic into a useEffect hook with a dependency on the emailList state.
const navigate = useNavigate();
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
useEffect(() => {
if (emailList.length) {
const hasEmail = emailList.some((e) => e.email === auth.currentUser.email);
navigate(hasEmail ? "/index" : "/enterdetails");
}
}, [auth, emailList, navigate]);
This might not run without the proper firebase config but check it out
https://codesandbox.io/s/elated-bell-kopbmp?file=/src/App.js
Things to note:
Use useMemo for hasEmail instead of emailCheck. This will re-run only when emailList changes
const hasEmail = useMemo(() => {
//checks if email exists
return emailList.some((e) => e.email === auth.currentUser.email);
}, [emailList]);
There isn't really a point in having this in a react component if you are just redirecting away. Consider having the content of 'index' at the return (</>) part of this component. Only redirect if they aren't authorized
useEffect(() => {
if (!hasEmail) {
navigate("/enterdetails");
}
//else {
// navigate("/index");
//}
}, [hasEmail, navigate]);

Warning: Cannot update a component from inside the function body of a different component in React Native

i have loading screen for call all the data function.i used async function for all function call.
//NOTE: this screen loads all the data and store it in store so user will have a smother experience
const LoadingScreen = (props) => {
const gotToHomeScreen = () => {
props.navigation.replace("Home", { screen: HOME_SCREEN });
};
//NOTE: loading data here for home screen journey
const getRequiredAPIDataInStore = async () => {
GetAllFieldProp();
GetAllSalaryAPIResponse();
GetSalaryAPIResponse();
let { spinnerStateForm101 } = GetForm101API();
let { spinnerStateForm106 } = GetForm106API();
GetMessagesCountAPI();
GetMessagesAPI(props);
GetAllFormAPIResponse();
GetAllSpecificSalaryAPIResponse();
let { spinnerStateMonthly } = GetMonthlyAbsenceAPI(props);
let { spinnerStateWeekly } = GetWeeklyAbsenceAPI(props);
if (
spinnerStateMonthly &&
spinnerStateWeekly &&
spinnerStateForm106 &&
spinnerStateForm101
) {
gotToHomeScreen();
}
};
getRequiredAPIDataInStore();
export default LoadingScreen;
but i am getting warning messages for this.
Warning: Cannot update a component from inside the function body of a different component.
at src/screens/loading-screen.js:19:26 in gotToHomeScreen
at src/screens/loading-screen.js:37:6 in getRequiredAPIDataInStore
How to solve this warning messsage?
Here's the approach I would take.
const Loading = () => {
const [spinnerStateMonthly, setSpinnerStatMonthly] = useState(null);
const [spinnerStateWeekly, setspinnerStateWeekly] = useState(null);
const [spinnerStateForm106, setspinnerStateForm106] = useState(null);
const [spinnerStateForm101, setSpinnerStateForm101] = useState(null);
const gotToHomeScreen = () => {
props.navigation.replace("Home", { screen: HOME_SCREEN });
};
useEffect(() => {
// async callback to get all the data and set state
(async () => {
await GetAllFieldProp();
await GetAllSalaryAPIResponse();
await GetSalaryAPIResponse();
const { spinnerStateForm101: local101 } = GetForm101API();
const { spinnerStateForm106: local106 } = GetForm106API();
setSpinnerStateForm101(local101);
setSpinnerStateForm106(local106);
await GetMessagesCountAPI();
await GetMessagesAPI(props);
await GetAllFormAPIResponse();
await GetAllSpecificSalaryAPIResponse();
const { spinnerStateMonthly: localMonthly } = GetMonthlyAbsenceAPI(props);
const { spinnerStateWeekly: localWeekly } = GetWeeklyAbsenceAPI(props);
setSpinnerStateMonthly(localMonthly);
setSpinnerStateWeekly(localWeekly);
})();
}, []);
// effect to check for what the state is and if all the states are satisfied
// then go to the home screen
useEffect(() => {
if (spinnerStateMonthly
&& spinnerStateWeekly
&& spinnerStateForm106
&& spinnerStateForm101) {
gotToHomeScreen();
}
}, [spinnerStateMonthly, spinnerStateWeekly, spinnerStateForm101,
spinnerStateForm106]);
};

How to get the current state inside socket.io on callback function

const useChat = () => {
const [messages, setMessages] = useState([]);
const socketRef = useRef();
const { chatId } = useSelector(state => state.chatin)
const { chatList } = useSelector(state => state.chatin)
const dispatch = useDispatch()
useEffect(() => {
socketRef.current = io(socketClient);
socketClient.on('chat', (data) => {
const targetMessage = (messages) => messages.findIndex(item => item.message_number === data.message_number);
console.log('targetMessage', targetMessage)
if (targetMessage !== -1) {
messages[targetMessage].is_hide = true;
}
setMessages((messages) => [...messages, data]);
});
return () => {
socketRef.current.disconnect();
};
}, []);
whenever I got new socket data, I wanna change 'messages' data, but can't access it, because it always shows initial data value.After that I have a question about how can I set it?
You can move the if condition inside setMessages function, this way you will get access to the current state:
socketClient.on('chat', (data) => {
setMessages((messages) => {
const targetMessage = messages.findIndex(item => item.message_number === data.message_number);
if (targetMessage !== -1) {
messages[targetMessage].is_hide = true;
}
return [...messages, data]
});
});

How to filter out data that is set to a state in react

I'm trying to build a search function that displays items that match the searchTerm. The data I am getting is from an API, I want filter out all items apart from the searchedTerm item, the initial API call runs once, with useEffect and [] callback
const changeFilterItem = (values) => {
const data = [...item];
const index = data.indexOf(values);
if (index > -1) {
data.splice(index, 1);
} else {
data.push(values);
}
setItem([...data]);
};
useEffect(() => {
if (item !== null) {
setLoading(true);
let pokemonList = [];
async function fetchData() {
for (let i = 0; i < item.length; i++) {
let response = await getAllPokemonByType(initialURLType, item[i]);
const pokemons = [...response.pokemon, ...pokemonList];
pokemonList = pokemons.slice(0);
}
console.log(pokemonList);
await loadPokemonByFilter(pokemonList);
}
fetchData().then();
}
}, [item]);
const loadPokemonByFilter = async (data) => {
let _pokemonData = await Promise.all(
data.map(async (pokemon) => {
return await getPokemon(pokemon.pokemon);
})
);
setPokemonData(_pokemonData);
setLoading(false);
};
const renderSelected = (type) => {
if (item.indexOf(type) === -1) {
return "";
}
return classes.selected;
};
What i've understood so far is that you want to filter out data from an array that has some data. For that you can use matchSorter.
Here is the link for its docs:
https://github.com/kentcdodds/match-sorter
const [master, setMaster] = React.useState([]);
const [filtered, setFiltered] = React.useState([]);
React.useEffect(() => {
makeApiCall().then(response => {
setMaster(response);
setFiltered(response);
});
}, []);
function filterData() {
setFiltered(master.filter(n => n.name === 'valueFromTextInput' && n.age === 'valueFromTextInput'));
}
Use filtered to your data grid where you want to show data. Call filterData function from your function where you want to do filtering.
Hope it helps.

How can I initialize in useState with the data from custom hooks?

I'm learning to React Hooks.
And I'm struggling initialize data that I fetched from a server using a custom hook.
I think I'm using hooks wrong.
My code is below.
const useFetchLocation = () => {
const [currentLocation, setCurrentLocation] = useState([]);
const getCurrentLocation = (ignore) => {
...
};
useEffect(() => {
let ignore = false;
getCurrentLocation(ignore);
return () => { ignore = true; }
}, []);
return {currentLocation};
};
const useFetch = (coords) => {
console.log(coords);
const [stores, setStores] = useState([]);
const fetchData = (coords, ignore) => {
axios.get(`${URL}`)
.then(res => {
if (!ignore) {
setStores(res.data.results);
}
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
let ignore = false;
fetchData(ignore);
return () => {
ignore = true;
};
}, [coords]);
return {stores};
}
const App = () => {
const {currentLocation} = useFetchLocation();
const {stores} = useFetch(currentLocation); // it doesn't know what currentLocation is.
...
Obviously, it doesn't work synchronously.
However, I believe there's the correct way to do so.
In this case, what should I do?
I would appreciate if you give me any ideas.
Thank you.
Not sure what all the ignore variables are about, but you can just check in your effect if coords is set. Only when coords is set you should make the axios request.
const useFetchLocation = () => {
// Start out with null instead of an empty array, this makes is easier to check later on
const [currentLocation, setCurrentLocation] = useState(null);
const getCurrentLocation = () => {
// Somehow figure out the current location and store it in the state
setTimeout(() => {
setCurrentLocation({ lat: 1, lng: 2 });
}, 500);
};
useEffect(() => {
getCurrentLocation();
}, []);
return { currentLocation };
};
const useFetch = coords => {
const [stores, setStores] = useState([]);
const fetchData = coords => {
console.log("make some HTTP request using coords:", coords);
setTimeout(() => {
console.log("pretending to receive data");
setStores([{ id: 1, name: "Store 1" }]);
}, 500);
};
useEffect(() => {
/*
* When the location is set from useFetchLocation the useFetch code is
* also triggered again. The first time coords is null so the fetchData code
* will not be executed. Then, when the coords is set to an actual object
* containing coordinates, the fetchData code will execute.
*/
if (coords) {
fetchData(coords);
}
}, [coords]);
return { stores };
};
function App() {
const { currentLocation } = useFetchLocation();
const { stores } = useFetch(currentLocation);
return (
<div className="App">
<ul>
{stores.map(store => (
<li key={store.id}>{store.name}</li>
))}
</ul>
</div>
);
}
Working sandbox (without the comments) https://codesandbox.io/embed/eager-elion-0ki0v

Resources