I have an initial state that I never use directly in the code, only inside another set value state
Only a scratch example:
interface PersonProps {}
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("")
const [todayYear, setTodayYear] = useState<string>("")
const [birthYear, setBirthYear] = useState<string>("")
const [age, setAge] = useState<string>("")
const getPerson = async () => {
try {
const response = await getPersonRequest()
const data = await response.data
setName(data.name)
setTodayYear(data.today_year)
setBirthYear(data.future_year)
setAge(data.todayYear - data.birthYear)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getPerson()
})
return (
<h1>{name}</h1>
<h2>{age}</h2>
)
}
export default Person
In this case as you can see I will never use "todayYear" and "birthYear" on UI, so code give a warning
todayYear is assigned a value but never used
What can I do to fix this and/or ignore this warning?
If you don't use them for rendering, there's no reason to have them in your state:
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("")
const [age, setAge] = useState<string>("")
const getPerson = async () => {
try {
const response = await getPersonRequest()
const data = await response.data
setName(data.name)
setAge(data.todayYear - data.birthYear)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getPerson()
})
return (
<h1>{name}</h1>
<h2>{age}</h2>
)
}
Side note: In most cases, you can leave off the type argument to useState wen you're providing an intial value. There's no difference between:
const [name, setName] = useState<string>("")
and
const [name, setName] = useState("")
TypeScript will infer the type from the argument. You only need to be explicit when inference can't work, such as if you have useState<Thingy | null>(null).
As this other answer points out, unless you want your code to run every time your component re-renders (which would cause an infinite render loop), you need to specify a dependency array. In this case, probably an empty one if you only want to get the person information once.
Also, since it's possible for your component to be unmounted before the async action occurs, you should cancel your person request if it unmounts (or at least disregard the result if unmounted):
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("");
const [age, setAge] = useState<string>("");
const getPerson = async () => {
const response = await getPersonRequest();
const data = await response.data;
return data;
};
useEffect(() => {
getPerson()
.then(data => {
setName(data.name)
setAge(data.todayYear - data.birthYear)
})
.catch(error => {
if (/*error is not a cancellation*/) {
// (Probably better to show this to the user in some way)
console.log(error);
}
});
return () => {
// Cancel the request here if you can
};
}, []);
return (
<h1>{name}</h1>
<h2>{age}</h2>
);
};
If it's not possible to cancel the getPersonRequest, the fallback is a flag:
const Person: React.FC<PersonProps> = () => {
const [name, setName] = useState<string>("");
const [age, setAge] = useState<string>("");
const getPerson = async () => {
const response = await getPersonRequest();
const data = await response.data;
return data;
};
useEffect(() => {
let mounted = true;
getPerson()
.then(data => {
if (mounted) {
setName(data.name)
setAge(data.todayYear - data.birthYear)
}
})
.catch(error => {
// (Probably better to show this to the user in some way)
console.log(error);
});
return () => {
mounted = false;
};
}, []);
return (
<h1>{name}</h1>
<h2>{age}</h2>
);
};
I also would like to mention one more thing. It's not related to your question but I think it's important enough to talk about it.
you need to explicitly state your dependencies for useEffect
In your case, you have the following code
useEffect(() => {
getPerson()
})
it should be written as follow if you want to trigger this only one time when a component is rendered
useEffect(() => {
getPerson()
}, [])
or if you want to trigger your side effect as a result of something that has changed
useEffect(() => {
getPerson()
}, [name])
If this is not clear for I suggest read the following article using the effect hook
Related
For my posts
in component AboutUsers.jsx
const [users, setUsers] = useState([]);
if I write like this, it's working, I see posts in users:
in component AboutUsers.jsx
useEffect(()=> {
const getUsers = axios.get('https://jsonplaceholder.typicode.com/todos',{
params:{
_limit:limitPage,
_page:currentPage
}
})
.then(response => setUsers(response.data))
},[])
but I created other component PostMyServise.js with:
export default class PostMyServise {
static async getPost(limit=10, page=1) {
const result = await axios.get('https://jsonplaceholder.typicode.com/todos',{
params: {
_limit: limit,
_page: page,
}
})
.then(response => {
return response
})
return result;
}
}
And one yet component useCreatePosts.js:
export const usePosts = (callback) => {
const [isTrue, setIsTrue] = useState('')
const [error, setError] = useState('')
const createPost = async () => {
try {
setIsTrue(false);
await callback;
} catch (e) {
setError(e.message);
} finally {
setIsTrue(true);
}
}
return [createPost, isTrue, error];
}
export default usePosts;
I wrote this, and I see empty array in console.log(users):
I don't understand why array is empty
const [createPost, isTrue, error] = usePosts (async ()=> {
const response = await PostMyServise.getPost(limitPage, currentPage);
setUsers(response.data)
})
useEffect(() => {
createPost();
},[currentPage])
You are not calling the callback. You need to add the parentheses.
const createPost = async () => {
try {
setIsTrue(false);
await callback();
} catch (e) {
setError(e.message);
} finally {
setIsTrue(true);
}
}
I feel like something about your code is over-engineered and too complex but that's outside the scope of the question. This change will at least get it working. Also, I suggest changing the name of isTrue and setIsTrue to something more meaningful as those names do not tell you what they are for.
I'm struggling with this problem and I've already tried many solutions but none of them fit me.
I have a context that I use to share information that I get from an API. I will summarize the files for you:
file: useGetInfo.tsx
type InfoContextData = { ... }
type Props = { ... }
type InfoResponseProps = { ... }
export const InfoContext = createContext<InfoContextData>({} as InfoContextData)
export const InformationProvider = ({ children }: Props) => {
const isBrowser = typeof window !== `undefined`
const [infoStorage, setInfoStorage] = useState(
isBrowser && localStorage.getItem('info')
? String(localStorage.getItem('info'))
: undefined
)
const [result, setResult] = useState<InfoResponseProps | null>(null)
const getInfo = useCallback(async (value: string) => {
const url = `<URL_FROM_API${value}>`
await axios.get(url)
.then((response) => {
setResult(response.data)
})
.catch((_) => {
setResult(null)
})
})
useEffect(() => {
if (!infoStorage) {
return
}
getInfo(infoStorage)
}, [infoStorage, getInfo])
return (
<InfoContext.Provider
value={{
result,
setResult,
infoStorage,
setInfoStorage,
getInfo,
}}
>
{children}
</InfoContext.Provider>
)
}
Then in the component I call the context:
file: SomeComponent.tsx
const Component = () => {
const { setInfoStorage, getInfo, result } = useContext(InfoContext)
const [input, setInput] = useState('')
const handleInfoSubmit = useCallback(() => {
getInfo(input)
if (!result || !result?.ok) {
localStorage.removeItem('info')
setInfoStorage(undefined)
}
setInfoStorage(input)
localStorage.setItem('info', 'input')
setInput('')
}, [input, result, getInfo, setInfoStorage, setInput])
return (
...
<Form onSubmit={handleInfoSubmit}>
<input>
...
</Form>
)
}
Basically, the user inserts a code in the form and when he submits the form, it runs the handleInfoSubmit function. Then, the code runs the function getInfo() and after requesting the API it returns the information to the state result.
The problem is in the SomeComponent.tsx file: when I run the function getInfo(input) I need the information in the state result but at the time axios finishes the request to the API and the code goes to the if (!result || !result?.ok) line, the result state is not still fulfilled.
I know that React/Gatsby can't update immediately the state like what I need, but is there a way to overcome this problem? Thanks in advance.
I think the value of the result would always be stale inside the handleInfoSubmit function per your code.
Rewrite the getInfo and handleInfoSubmit like this
// Return data from getInfo so that we can use the value directly in handleInfoSubmit
const getInfo = useCallback(async (value: string) => {
const url = `<URL_FROM_API${value}>`
try {
const { data } = await axios.get(url);
setResult(data)
return data;
} catch {
setResult(null)
}
return null;
})
const handleInfoSubmit = useCallback(async () => {
// await getInfo and get the axios response data.
const result = await getInfo(input)
if (!result || !result?.ok) {
localStorage.removeItem('info')
setInfoStorage(undefined)
}
setInfoStorage(input)
localStorage.setItem('info', 'input')
setInput('')
}, [input, getInfo, setInfoStorage, setInput])
I have three apis in all. GetAssets is the first, followed by assetsOptionsList and getAssetsLibrary. The issue I'm having is that when I post the data on getAssetsLibrary, I want to be able to present it on get Assets at runtime.Everything is working fine but i want to show assets on runtime.
I'm setting the runTime state true on get request but the problem is it works only for one time.Second time, it does not map on runtime. Actually, i want to know is there any alternative so that i can achieve the goal.
In the below code the one function is getting the assets. And i want to run the one function when the post request successfully sent.
const [images, setImages] = useState([]);
const [assetOptions, setAssetOptions] = useState([]);
const [faqOpened, setToggleFaq] = useState(false);
const [runTime, setRunTime] = useState(false)
const [assetID, setAssetID] = useState()
const [isLoading, setIsLoading] = useState(false);
const handleForm = (e) => {
const index = e.target.selectedIndex;
const el = e.target.childNodes[index]
const option = el.getAttribute('id');
setAssetID(option)
}
const formHandler = (e) => {
e.preventDefault()
let formData = new FormData();
formData.append('media', e.target.media.files[0]);
formData.append('assetListId', assetID)
formData.append('name', e.target.name.value);
console.log(Object.fromEntries(formData))
const res = axios.post('api/asset-library',
formData
).then((response) => {
showSuccessToaster(response?.data?.message)
setRunTime(true)
setToggleFaq(false)
})
.catch((error) => {
showErrorToaster(error?.response?.data?.message)
})
}
const showSuccessToaster = (response) => {
return uploadToasterSuccess.show({ message: response });
}
const showErrorToaster = (error) => {
return uploadToasterError.show({ message: error });
}
const one = async () => {
setIsLoading(true)
const data = await axios.get('api/assets').then((res) => {
return res?.data?.data
})
setImages(data)
setIsLoading(false)
}
const two = async () => {
const data = await axios.get('/api/asset-list').then((res) => {
return res?.data?.data
})
setAssetOptions(data)
}
useEffect(() => {
one()
two()
}, [runTime]);
So I have an array of objects and each property in the object comes from the user. User chooses an object that he wants to edit and I am passing it using context to component that takes data from user. Now after getting data I am inserting it back to array but it is giving me wrong data.
Here is component that takes data from user.
const SelectedSymptom = () => {
const [
selected,
setSelected,
selectedSymptom,
setSelectedSymptom,
] = useContext(symptomContext);
const [note, setNote] = useState("");
const [since, setSince] = useState("");
const [severity, setSeverity] = useState("");
const [feature, setFeature] = useState("");
const [place, setPlace] = useState("");
const [colour, setColour] = useState("");
useEffect(() => {
return async () => {
await setSelectedSymptom((prev) => {
return {
...prev,
feature,
since,
severity,
place,
colour,
note,
};
});
await setSelected((prev) => {
const cur = prev.filter((item) => item.name !== selectedSymptom.name);
if (selectedSymptom !== "") cur.push(selectedSymptom);
return cur;
});
console.log(selectedSymptom, selected);
};
}, [since, feature, severity, place, colour]);
}
Data from form is coming correctly but I guess due to async nature of setState call, I am getting error.
First remove the return from useEffect. Return is a cleanup method for useEffect, example to terminate intervals, listeners etc.
I changed to a constant instead update() which I run in the end of the useEffect.
Also the parameters in the code below, you only had set the key, not the value to the keys
await setSelectedSymptom((prev) =>
return {
...prev,
feature: feature,
since: since,
severity: severity,
place: place,
colour: colour,
note: note,
};
});
I hope this brings some clarification
const SelectedSymptom = () => {
const [
selected,
setSelected,
selectedSymptom,
setSelectedSymptom,
] = useContext(symptomContext);
const [note, setNote] = useState("");
const [since, setSince] = useState("");
const [severity, setSeverity] = useState("");
const [feature, setFeature] = useState("");
const [place, setPlace] = useState("");
const [colour, setColour] = useState("");
useEffect(() => {
const update = async () => {
await setSelectedSymptom((prev) => {
return {
...prev,
feature: feature,
since: since,
severity: severity,
place: place,
colour: colour,
note: note,
};
});
await setSelected((prev) => {
const cur = prev.filter((item) => item.name !== selectedSymptom.name);
if (selectedSymptom !== "") cur.push(selectedSymptom);
return cur;
});
update();
};
}, [since, feature, severity, place, colour]);
}
I was wondering what would be the best way to get multiple data from Firebase in an async function to wait for some data from the first request. I'm using this code right now but it's not reliable and it breaks sometimes saying that it can't fetch the data for the second call as it's undefined.
function useOccasion() {
const [occasionData, setOccasionData] = useState(null)
const [friend, setFriend] = useState(null)
let { occasion } = useParams()
useEffect(() => {
const unsubscribe = firestore.collection('occasions').doc(occasion)
.onSnapshot(async eventData => {
setOccasionData({id: eventData.id, ...eventData.data()})
let friendData = await firestore.collection("friends").doc(eventData.data().friend).get();
setFriend({id: friendData.id, ...friendData.data()});
})
return () => unsubscribe()
}, [occasion])
return [occasionData, friend]
}
If there's a more robust way to achieve this that would be amazing.
I'd suggest breaking your data fetches into two hooks, one for each collection occasion and friends.
I'm not sure how you've set firebase up, but I access it through context.
e.g.
const useOccasion = () => {
const firebase = useContext(FirebaseContext)
const [occasions, setOccasions] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const unsubscribe = firebase.db.collection('occasions')
.onSnapshot(snapshot => {
if (snapshot.size) {
let occasionList = []
snapshot.forEach(doc =>
occasionList.push({ ...doc.data(), uid: doc.id }),
)
setOccasions(occasionList)
setLoading(false)
} else {
setOccasions([])
setLoading(false)
}
})
return () => {
unsubscribe()
}
}, [])
return { occasions, loading }
}
Finally, in your component where you require the data you can access this hook:
const { occasions, loading } = useOccasion()