Not updating useState() custom hook when I use setState(somedata) - reactjs

code
import React from "react";
import useUsersData from "./useUsersData";
export const User = ({ _id, name, age, email }) => {
const [userData, setUserData] = useUsersData();
const handleDelete = (id) => {
const proceed = window.confirm("Are you sure?");
if (proceed) {
const url = `http://localhost:5000/users/${id}`;
fetch(url, {
method: "DELETE",
})
.then((res) => res.json())
.then((data) => {
console.log(data);
}); }
const remaining = userData.filter((userData) => userData._id !== id);
console.log(userData);
console.log(remaining);
setUserData(remaining);
console.log(userData);
};
return (
<p onClick={() => handleDelete(_id)}>
X
</p>
</div>
);
};
the custom hook:
import { useEffect, useState } from "react";
const useUsersData = () => {
const [userData, setUserData] = useState([]);
useEffect(() => {
fetch("http://localhost:5000/users")
.then((res) => res.json())
.then((data) => setUserData(data));
}, []);
return [userData, setUserData];
};
export default useUsersData;
not updating useState() custom hook when I use setState(somedata) the custom hook useState does not get updated. I don't know what I did wrong.
You can see in the console log that set user data is not changing.
here is a screenshot
console.log

Whenever you call the dispatch function of useState, its effects on the state will only be noticeable on the next function call.
So if you console.log or otherwise try to use the state immediately after a dispatch, it will not have the latest data.
If you want to use the latest state as soon as it is updated, you need to useEffect while passing the state in the dependency array.
const Example = () => {
const [userData, setUserData] = React.useState(['1', '2', '3']);
const handleDelete = () => {
const remaining = userData.filter((id) => id !== '3');
console.log(userData); // will print ['1', '2', '3']
console.log(remaining); // will print ['1', '2']
setUserData(remaining);
console.log(userData); // will still print ['1', '2', '3']
};
React.useEffect(() => {
console.log('useEffect', userData); // will print "useEffect ['1', '2']"
}, [userData]);
return <p onClick={handleDelete}>X</p>;
};
This is the same issue as posted in The useState set method is not reflecting a change immediately

Related

Hi, i'm retrieving data from firestore, and checking whether to direct the user to index page or to enter details for a new user But not able to do so

React code
import React, { useEffect, useState } from "react";
import { getDocs, collection } from "firebase/firestore";
import { auth, db } from "../firebase-config";
import { useNavigate } from "react-router-dom";
function Load() {
const navigate = useNavigate();
const [accountList, setAccountList] = useState([]);
const [hasEmail, setHasEmail] = useState(false);
const accountRef = collection(db, "accounts");
Am i using useEffect correctly?
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
checking whether email exists
const emailCheck = () => {
if (accountList.filter((e) => e.email === auth.currentUser.email)) {
setHasEmail(true);
} else {
setHasEmail(false);
}
};
Redirecting based on current user
const direct = () => {
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
return <div></div>;
}
The code compiles but doesn't redirect properly to any of the pages.
What changes should I make?
First question posted excuse me if format is wrong.
There are two problems here:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
In order:
Since getAccounts is asynchronous, you need to use await when calling it.
But even then, setting state is an asynchronous operation too, so the account list won't be updated immediately after getAccounts completes - even when you use await when calling it.
If you don't use the accountList for rendering UI, you should probably get rid of it as a useState hook altogether, and just use regular JavaScript variables to pass the value around.
But even if you use it in the UI, you'll need to use different logic to check its results. For example, you could run the extra checks inside the getAccounts function and have them use the same results as a regular variable:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
const result = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setAccountList(result);
emailCheck(result);
direct();
};
getAccounts();
}, []);
const emailCheck = (accounts) => {
setHasEmail(accounts.some((e) => e.email === auth.currentUser.email));
};
Alternatively, you can use a second effect that depends on the accountList state variable to perform the check and redirect:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
});
useEffect(() => {
emailCheck();
direct();
}, [accountList]);
Now the second effect will be triggered each time the accountList is updated in the state.

React Typescript. The right way to push new objects form api to array with existing objects without repeating them

I have a custom hook where I call 16 subreddits at the time because I want to implement an infinite scroll. When the url page parameter change I want to add the new data to the array witch then I map. But I cant find the right way to do it with typescript. Can some of you guys show me the right way?
The types:
export type Subreddits = [Subreddit];
export type Subreddit = {
id: string;
title: string;
description: string;
};
The Hook:
function useSubreddit() {
let [subredditData, setSubredditData] = useState<any>([]);
const [loadingSubbredits, setLoadingSubreddits] = useState(false);
const [subredditError, setSubredditError] = useState(null);
const dispatch = useDispatch();
const url =
"https://6040c786f34cf600173c8cb7.mockapi.io/subreddits?page=1&limit=16";
useEffect(() => {
setLoadingSubreddits(true);
axios
.get(url)
.then((response) => {
setSubredditData(
(subredditData = [ new Set([...subredditData, ...response.data])])
);
dispatch(setSubredditsData(response.data));
})
.catch((err) => {
setSubredditError(err);
})
.finally(() => setLoadingSubreddits(false));
}, [url]);
return { loadingSubbredits, subredditError, subredditData };
}
export default useSubreddit;
This is the right way to add new item to set:
setSubredditData((
{ subredditData }) => ({
subredditData: new Set(subredditData).add(response.data)
})
);
Change subredditData type to Array
const [subredditData, setSubredditData] = useState<Array>([])
Then use;
setSubredditData(subredditData => [...subredditData, ...response.data])
Ideally you should define the type of response object so Typescript knows what kind of data it has;
.then((response: ResponseType) => {
Working example: codesandbox
import { useState, useEffect } from "react";
import axios from "axios";
function useSubreddit() {
let [subredditData, setSubredditData] = useState({});
const [loadingSubbredits, setLoadingSubreddits] = useState(false);
const [subredditError, setSubredditError] = useState(null);
//console.log(subredditData);
const url =
"https://6040c786f34cf600173c8cb7.mockapi.io/subreddits?page=1&limit=16";
useEffect(() => {
setLoadingSubreddits(true);
axios
.get(url)
.then((response) => {
const result = response.data?.reduce((prev,curr) => ({ ...prev, ...{ [curr.id]: curr }}),{});
setSubredditData((subredditData) => ({ ...subredditData, ...result }));
})
.catch((err) => {
setSubredditError(err);
})
.finally(() => setLoadingSubreddits(false));
}, [url]);
return { loadingSubbredits, subredditError, subredditData:Object.values(subredditData) };
}
export default useSubreddit;
working example: https://codesandbox.io/s/musing-brown-9nf7ne?file=/src/App.js
I found the solution
function useSubreddit() {
const [subredditData, setSubredditData] = useState<Array<any>>([])
const [loadingSubbredits, setLoadingSubreddits] = useState(false);
const [subredditError, setSubredditError] = useState(null);
const dispatch = useDispatch();
const url =
"https://6040c786f34cf600173c8cb7.mockapi.io/subreddits?page=1&limit=4";
useEffect(() => {
setLoadingSubreddits(true);
axios
.get(url)
.then((response: SubredditsResponse) => {
setSubredditData(Array.from( new Set([ ...subredditData, ...response.data])))
dispatch(setSubredditsData(response.data));
})
.catch((err) => {
setSubredditError(err);
})
.finally(() => setLoadingSubreddits(false));
}, [url]);
return { loadingSubbredits, subredditError, subredditData };
}
export default useSubreddit;
Thank you for your help guys

UseEffect is not called

I have a question about useEffect. My useEffect is not fetching the data the first time, I have to switch route for it to have the data I needed
const Comments = ({ ...rest }) => {
const theme = useTheme();
const classes = useStyles({ theme });
const [users, setUsers] = useState([]);
const { push } = useHistory();
const { token, loading } = useContext(AuthContext)
const dispatch = useDispatch();
const allUsers = useSelector(state => state.allUsers);
const comments = useSelector(state => state.listCommentsByBookId);
const listBooks = useSelector((state) => state.userListBooks);
const isFetching = useSelector((state) => state.isFetching);
const [stateReady, setReadyForRender] = useState(false)
const redirectTo = ( rowData ) => {
push({
pathname: ROUTE.USERS_DETAILS,
user: rowData
});
}
const options = {
filterType: 'checkbox',
selectableRowsHeader: false,
selectableRowsHideCheckboxes: false,
selectableRowsOnClick: false,
onRowClick: redirectTo,
};
const getAllComments = async () => {
var allusersId = [];
//get all ids
await allUsers.map((user) => {
allusersId.push(user.uid);
})
//get all books from users
await allusersId.map(async (id) => {
await dispatch(getUserListBooks(apiURL + `api/bdd/userListBooks/${id}`, token))
})
var listArray = [];
//filter the array and delete empty rows
listArray.push(listBooks);
var newArray = listArray.filter(e => e);
//map every user and stock the list of books in string
await newArray.forEach(async (book)=> {
await book.map(async (book) => {
await dispatch(getCommentsByBookId(apiURL + `api/bdd/ratingByBook/${book.id}`, token));
})
})
setReadyForRender(true)
}
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
}, [stateReady])
console.log('COM', comments);
return (
<div>
{stateReady &&
<Card>
<Box className={classes.tableContainer} sx={{ minWidth: 1050 }}>
<MUIDataTable
data={comments}
columns={columns}
options={options}
/>
</Box>
</Card>}
</div>
);
};
Why? It might be related to async await but I'm stuck here.
If you want to fetch these informations on the first render, you'll have to pass an empty array as the second parameter of your useEffect.
The reason your useEffect is not called is because stateReady does not change during the course of your current code.
See this link, particularly the note section, it explains way better than me how the empty array as second parameter works.
Can you replace the useEffect section to the below code:
useEffect(() => {
(async () => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
})()
}, [stateReady])
You can read more about this in this link
You can use eslint to show errors when coding with hooks. In this case if you want useEffect to handle stateReady, please provide it in the function getAllComments() => getAllComments(stateReady) and when you call this function in useEffect with [stateReady] as dependencies, it'll work.
You should remove stateReady from your dependency array in the useEffect hook. Adding variables in the dependency array means that the use Effect hooks fires only when one of the dependencies changes. Here's how to use useEffect as lifecycle methods https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
});

Add a new value inside the State object in React Hook

I have a data and I put it in the state and I want to add a new value in addition to the content of the data in the object called watched, but this is a problem, thank you for your help.
import React, { useEffect, useState } from "react";
import ListManager from "./component/manager";
const App = () => {
const [getMovies, setMovie] = useState([]);
const [getLoading, setLoading] = useState(true);
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
setMovie(response);
setLoading(false);
});
});
useEffect(() => {
return () => {
setMovise(
getMovies.map((item) => ({
id: item.id,
text: item.name,
rate: item.rate,
watched: false,
}))
);
};
});
if (getLoading) {
return "Please wait...";
}
return <ListManager movies={getMovies} />;
};
export default App;
You don't need the second useEffect, you should use the first useEffect to do all your stuff, also you should pass an empty array to useEffect in order to be executed one time.
import React, { useEffect, useState } from "react";
import ListManager from "./component/manager";
const App = () => {
const [getMovies, setMovie] = useState([]);
const [getLoading, setLoading] = useState(true);
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
setMovie(response.map((item) => ({
id: item.id,
text: item.name,
rate: item.rate,
watched: false,
})));
setLoading(false);
});
}, []);
if (getLoading) {
return "Please wait...";
}
return <ListManager movies={getMovies} />;
};
export default App;
It looks like you have a typo calling setMovise instead of setMovies.
I'm not sure about your second useEffect. A return in useEffect is often used
for a cleanup function like this (from ReactJS documentation):
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});
I'd just put it normally in the code outside the hook before the return statements.
Your variable naming doesn't fit the usual convention for
useState.
const [getMovies, setMovie] = useState([]);
should be
const [movies, setMovies] = useState([])

React Hook error in array handing in useEffect() and setState does not set the state

I'm having two problems in this code. The first is in the second useEffect. For the reason that I don't understand the useEffect stops working every now and then and causes an error "Cannot read property 'toLowerCase'". Removing the toLowerCase does not solve the problem, but the whole array handling seems to be impossible at that time.
The other problem is in the function addName. setNewName does not set newName. That one I've tried in various kinds of forms, such as setNewName(...newName, {name: '', number: ''}), setNewName('') inside .then and else as well as outside else.
...
import React, {useState, useEffect} from 'react'
import Filter from './components/Filter'
import PersonForm from './components/PersonForm'
import Persons from './components/Persons'
import personService from './services/person'
const App = () => {
const [person, setPerson] = useState([])
const [newName, setNewName] = useState({name: '', number: ''})
const [filteredPerson, setFilteredPerson] = useState([''])
const [searchTerm, setSearchTerm] = useState('')
useEffect(() => {
personService
.getAll()
.then(initialPersons => {
setPerson(initialPersons)
})
}, [])
useEffect( () => {
const results = person.filter( p =>
p.name.toLowerCase().includes(searchTerm) )
setFilteredPerson(results)
},[person,filteredPerson] )
const addName = (event) => {
event.preventDefault()
const nameObject = {
name: newName.name,
number: newName.number
}
if (person.some(p => p.name === newName.name)
) {
window.alert(`${newName.name} is already added to phonebook`)
}
else {
personService
.create(nameObject)
.then(returnedPerson => {
setPerson(person.concat(returnedPerson))
setNewName({name: '', number: ''})
})
console.log('newName', newName.name )
}
}
const handleAddPerson = (event) => {
console.log('event.target.name ', event.target.name)
console.log('event.target.value ', event.target.value)
setNewName({...newName,
[event.target.name]: event.target.value
})
}
const handleSearchTerm = (event) => {
setSearchTerm(event.target.value)
}
return (
<div >
<h2>Phonebook</h2>
<Filter searchTerm={searchTerm} onChange={handleSearchTerm} />
<h3>Add a new</h3>
<PersonForm onSubmit={addName} onChange={handleAddPerson} />
<h2>Numbers</h2>
<Persons list={filteredPerson} />
</div>
);
}
export default App;
...
import axios from 'axios'
const baseUrl = 'http://localhost:3001/persons'
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
const create = newObject => {
const request = axios.post(baseUrl, newObject)
return request.then(response => response.data)
}
const update = (id, newObject) => {
const request = axios.put(`${baseUrl}/${id}`, newObject)
return request.then(response => response.data)
}
/*const updater = {
getAll,
create,
update
}*/
export default {
getAll,
create,
update
}
EDIT
Use async await in your personService so you can return response instead of return request.then(...) something like:
const getAll = async () => {
const response = await axios.get(baseUrl);
return response;
}
After that you can do as follows in your useEffect
useEffect(() => {
(async () => {
const response = await personService.getAll();
if (response.status === 200) {
setPerson(response.data);
const filtered = response.data.filter(item =>
item.name.toLowerCase().includes(searchTerm)
);
setFilteredPerson([...filtered]);
}
})();
}, []);

Resources