So I'm using useEffect hook to fetch my data from database and after I get that data I want to set it as useState for title and postBody, but it doesn't work, because useEffect hook runs "last", how can I fix it?
Code:
const [cPost, setCPost] = useState([]);
const postId = id.match.params.id;
useEffect(() => {
axios.get('http://localhost:5000/posts/'+postId)
.then(posts => {
setCPost(posts.data);
console.log("SAS");
})
}, []);
const [title, setTitle] = useState(cPost.title);
const [postBody, setPostBody] = useState(cPost.postBody);
As a temporary and quick solution, you can use such workaround:
const [cPost, setCPost] = useState();
const [title, setTitle] = useState();
const [postBody, setPostBody] = useState();
const postId = id.match.params.id;
useEffect(() => {
axios.get('http://localhost:5000/posts/'+postId)
.then(post => {
setCPost(post.data);
console.log("SAS");
})
}, []);
useEffect(() => {
if(cPost) {
setTitle(cPost.title);
setPostBody(cPost.postBody);
}
}, [cPost]);
Or the second option:
const [cPost, setCPost] = useState();
const [title, setTitle] = useState();
const [postBody, setPostBody] = useState();
const postId = id.match.params.id;
useEffect(() => {
axios.get('http://localhost:5000/posts/'+postId)
.then(post => {
setCPost(post.data);
setTitle(post.title);
setPostBody(post.postBody);
console.log("SAS");
})
}, []);
But in the future I would recommend doing side effects like API requests and others using special libraries or create hook for making API requests.
For example redux-saga or redux-thunk.
And use a state manager like redux or mobx.
P.S. and consider whether you need to store the title and body separately in the component state. I have a strong suspicion that you have no need for it.
Related
I'm send some settings data in useEffect:
const [settingsData, setSettingsData] = useState([]);
const [settingsLoading, setSettingsLoading] = useState(true);
useEffect(() => {
if (isAuth) {
apiClient
.get("api/v1/page-data/settings")
.then((response) => {
setSettingsData(response.data);
setSettingsLoading(false);
})
.catch((error) => console.error(error));
}
}, []);
And create useStates for two keys inputs with data from backend:
const [publicKey, setPublicKey] = useState(
settingsData?.data?.user_data?.public_key
);
const [secretKey, setSecretKey] = useState(
settingsData?.data?.user_data?.secret_key
);
There is an inputs:
<input
value={publicKey}
onChange={(e) => setPublicKey(e.target.value)}
placeholder="PublicKey"
/>
<input
value={secretKey}
onChange={(e) => setSecretKey(e.target.value)}
placeholder="SecretKey"
/>
When page is loaded i'm just see empty inputs, looks like useState assign my backend property when it's undefined. How can i assign backend data to useState if this hook initialize initial value before my settings data is loaded from backend?
Your key related initial states are set to undefined because the data is not available when they are created. There are a few ways to solve that, here are a couple:
You can set those key related values at the same time you set your data object instead of trying to derive them
const [settingsData, setSettingsData] = useState([]);
const [settingsLoading, setSettingsLoading] = useState(true);
const [publicKey, setPublicKey] = useState('');
const [secretKey, setSecretKey] = useState('');
useEffect(() => {
if (isAuth) {
apiClient
.get("api/v1/page-data/settings")
.then(({ data }) => {
setSettingsData(data);
setSettingsLoading(false);
setPublicKey(data.user_data.public_key);
setSecretKey(data.user_data.secret_key);
})
.catch((error) => console.error(error));
}
}, []);
A less optimal option would be to use another useEffect to update your key telated states
const [settingsData, setSettingsData] = useState([]);
const [publicKey, setPublicKey] = useState('');
const [secretKey, setSecretKey] = useState('');
useEffect(() => {
if (settingsData) {
setPublicKey(settingsData.user_data.public_key);
setSecretKey(settingsData.user_data.secret_key);
}
}, [settingsData]);
a third option is to make only the initial state of the inputs derived from the returned data and use short circuiting to manage the value
const [settingsData, setSettingsData] = useState([]);
const [settingsLoading, setSettingsLoading] = useState(true);
const [publicKey, setPublicKey] = useState('');
const [secretKey, setSecretKey] = useState('');
const publicK = settingsData?.data?.user_data?.public_key;
const secretK = settingsData?.data?.user_data?.secret_key;
useEffect(() => {
if (isAuth) {
apiClient
.get("api/v1/page-data/settings")
.then(({ data }) => {
setSettingsData(data);
setSettingsLoading(false);
setPublicKey(data.user_data.public_key);
setSecretKey(data.user_data.secret_key);
})
.catch((error) => console.error(error));
}
}, []);
// inputs will start with the values from const but once you write anything
// in the input it will change to the managed value in state
<input
value={publicKey || publicK}
onChange={(e) => setPublicKey(e.target.value)}
placeholder="PublicKey"
/>
<input
value={secretKey || secretK}
onChange={(e) => setSecretKey(e.target.value)}
placeholder="SecretKey"
/>
I want to get news but i have an empty dictionary in the first render.
My useEffect
const [news, setNews] = useState({});
const [page, setPage] = useState(1);
const [user, setUser] = useState({});
useEffect(() =>{
const getNews = async() =>{
const newsData = await httpClient.get(`/feed/${pk}/?page=${page.toString()}`)
setNews(newsData.data);
const userData = await httpClient.get('/profile/')
setUser(userData)
}
getNews();
}, [page])
How can i get data in the first time render?
because you have [page] in the dependency array - add hook for initial render:
const [page, setPage] = useState(0);
useEffect(() => setPage(1), [])
You will always have your state what you initialize it as on first render, react won't wait until useEffect is finished before render since that would lock up the UI.
You need some sort of loading indicator while data is fetching, you can do this for example
const [loading, setLoading] = useState(true);
const [news, setNews] = useState({});
const [page, setPage] = useState(1);
const [user, setUser] = useState({});
useEffect(() =>{
const getNews = async() =>{
const newsData = await httpClient.get(`/feed/${pk}/?page=${page.toString()}`)
setNews(newsData.data);
const userData = await httpClient.get('/profile/')
setUser(userData)
setLoading(false)
}
setLoading(true)
getNews();
}, [page])
if (loading) {
return <>{"loading"}</>
}
change the return value to whatever you want, maybe you want to just return an empty <></> component so that when it first shows up it'll have all the data.
I am trying to execute a function to update a setState but it as well needs other state to load first.
const [chatsIds, setChatsIds] = useState([]);
const [chats, setChats] = useState([]);
useEffect(() => {
getChatsIds();
}, []);
useEffect(() => {
getChats();
}, [chats]);
the "getChats" needs the value from "chatsIds" but when the screen is loaded the value isn't , only when i reload the app again it gets the value.
Here are the functions :
const getChatsIds = async () => {
const ids = await getDoc(userRef, "chats");
setChatsIds(ids);
}
const getChats = async () => {
const chatsArr = [];
chatsIds.forEach(async (id) => {
const chat = await getDoc(doc(db, "Chats", id));
chatsArr.push(chat);
console.log(chatsArr);
});
setChats(chatsArr);
}
I've tried with the useEffect and useLayoutEffect hooks, with promises and async functions, but i haven't found what i'm doing wrong :(
The problem is in your useEffect hook dependency. It should depends on chatsIds not chats.
useEffect(() => {
getChats();
}, [chatsIds]);
Which mean fetching chatsIds should depend on first mount and fetching chats should depend on if chatsIds is chnaged.
You simply change the useEffect hook to like below.
useEffect(() => {
getChatsIds();
}, [chatsIds]);
I Think getChat() is depend on chatIds...
so you use useEffect with chatIds on dependency
const [chatsIds, setChatsIds] = useState([]);
const [chats, setChats] = useState([]);
useEffect(() => {
getChatsIds();
}, []);
useEffect(() => {
getChats(chatsIds);
}, [chatsIds]);
const getChatsIds = async () => {
const ids = await getDoc(userRef, "chats");
setChatsIds(ids);
}
const getChats = async (chatsIds) => {
const chatsArr = [];
chatsIds.forEach(async (id) => {
const chat = await getDoc(doc(db, "Chats", id));
chatsArr.push(chat);
console.log(chatsArr);
});
setChats(chatsArr);
}
Here is the code of the snippet I want to change to a Functional component, I write almost my code here now please check.
import _ from 'lodash';
import { ListItem, SearchBar, Avatar } from 'react-native-elements';
import { getUsers, contains } from './api/index';
function App(props) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [fullData, setFullData] = useState([]);
const [query, setQuery] = useState();
useEffect(() => {
makeRemoteRequest();
},[query]);
const makeRemoteRequest = _.debounce(() => {
setLoading(true);
getUsers(20, query)
.then((users) => {
setLoading(false);
setData(users);
setFullData(users);
})
.catch((error) => {
setLoading(false);
});
}, 250);
const handleSearch = (text) => {
const formattedQuery = text.toLowerCase();
const data = _.filter(fullData, (user) => {
return contains(user, formattedQuery);
});
// I want to change the below code to work on Functioanl component
// this.setState({ data, query: text }, () => //this.makeRemoteRequest());
// New code here.....
};
I implemented it in a different way but not work.
You can have something like the following.
const [query, setQuery] = useState();
const [data, setData] = useState();
useEffect(() => {
makeRemoteRequest();
}, [query])
Read more about useEffect here
You're trying to make a set of data and text, then call a callback after the set.
There are several ways to obtain this behaviour.
What I would suggest you is to have a state (useState) which include data and text and then listen for the changes of this stage through a useEffect.
export default function App() {
const [request, setRequest] = useState({data: {}, text: ''});
const makeRemoteRequest = useCallback(() => console.log({request}),[request]);
useEffect(() => {
//on mount
setRequest({data: {obj:'with data'}, text: 'text'})
},[])
useEffect(() => {
makeRemoteRequest()
},[request,makeRemoteRequest])
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
What you can see here, it's a functional component which is:
setting a state on mount (read comment)
define a function makeRemoteRequest every time the state request changes through the useCallback hook
call the function makeRemoteRequest every time the state request or the callback makeRemoteRequest changes through the useEffect hook
EDIT:
import _ from 'lodash';
import { ListItem, SearchBar, Avatar } from 'react-native-elements';
import { getUsers, contains } from './api/index';
function App(props) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [fullData, setFullData] = useState([]);
const [query, setQuery] = useState();
useEffect(() => {
makeRemoteRequest();
},[query]);
const makeRemoteRequest = _.debounce(() => {
setLoading(true);
getUsers(20, query)
.then((users) => {
setLoading(false);
setData(users);
setFullData(users);
})
.catch((error) => {
setLoading(false);
});
}, 250);
const handleSearch = (text) => {
const formattedQuery = text.toLowerCase();
const data = _.filter(fullData, (user) => {
return contains(user, formattedQuery);
});
setData(data);
setQuery(text);
}
};
Actually what you want is to trigger the function makeRemoteRequest, right now that you have to do in order to get it is to make the proper set (which means setQuery), which is going to trigger the useEffect
I have events which is pulled from redux, and if the events array contains data, then updateData will be used to filter events into the state var data.
I have data and events both added to the dependency array as talked about here. but I'm still getting this error:
const SingleTable = () => {
const events = useSelector(state => eventsSelector(state));
const [data, updateData] = useState([]);
const [sortCol, updateSortCol] = useState(0);
const [sortDir, updateSortDir] = useState('ascending');
useEffect(() => {
const formattedArray = events ? formatLoss(events): [];
events && updateData(formattedArray);
}, [data, events]);
//...
Thoughts, ideas?
Because you are executing useEffect callback whenever data changes and you are changing data in useEffect callback.
Remove data as dependency.
Use this code to fix it
const SingleTable = () => {
const events = useSelector(state => eventsSelector(state));
const [data, updateData] = useState([]);
const [sortCol, updateSortCol] = useState(0);
const [sortDir, updateSortDir] = useState('ascending');
useEffect(() => {
const formattedArray = events ? formatLoss(events): [];
events && updateData(formattedArray);
}, [events]);
//...