React Hooks - refresh some data on page with new data from post - reactjs

I have a component that fetches data only when mounted. I ideally only want to make this call once as it is fetching a lot of data. When I make a post request, I receive new data which I want to display on the page and optimistically update the UI. I don't want to refetch the data again as it's an expensive call and would instead just like to update the changed data. I could create an API endpoint that I call to fetch the necessary data on updates but why not update the data with what I receive from the post request?
example code:
const App = () => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [alert, setAlert] = useState(null);
// data states
const [user, setUser] = useState(null);
const [account, setAccount] = useState(null);
const [key, setKey] = useState(null);
const [externalAccount, setExternalAccount] = useState(null);
const [showModal, setShowModal] = useState(false);
// fetch data upon component mount
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// Get data for working with accounts
const res = await get(
'/api/v1/account'
);
setUser(res.data.user);
setAccount(res.data.account);
setKey(res.data.key);
setExternalAccount(res.data.external_account);
} catch (e) {
setError(e);
}
setLoading(false);
};
fetchData();
}, []);
const createAccount = async params => {
setLoading(true);
try {
// send request
const res = await post(
'/api/v1/account',
params
);
// set updated account data
setAccount(res.data.account); //doesn't update on the page. I could call a refetch in the above useEffect but not ideal. Any other options?
// set success alert
setAlert('Your account was created successfully.');
// update loading state
setLoading(false);
} catch (e) {
setAlert(e.message);
setLoading(false);
}
};
// Define page renders
if (error) return <ErrorComponent />;
if (loading) return <Loader />;
return (
<>
<h1>Account</h1>
{account &&
<div>
//display information using state information on user and account
</div>
}
{showModal &&
<CreateModal
toggleModal={setShowModal}
createAccount={createAccount}
user={user}
account={account}
/>
}
</>
);
}
const CreateModal = ({ toggleModal, createAccount }) => {
const handleSubmit = e => {
e.preventDetault();
const params = // set up params for post request
createAccount(params)
return (
<form onSubmit={e => handleSubmit(e)}>
//form code here
</form>
)
}

Related

How to automatically refresh getstream.io FlatFeed after a new post using reactjs?

I would like to understand how can I auto-update the feed after submitting the form through the StatusUpdateForm component. At the moment I have to refresh the page to see the changes.
In general, my task is to differentiate feeds based on the user's location, I requested extended permissions from support so that different users can post to one feed, and therefore I use the modified doFeedRequest parameters of the FlatFeed component to show the feed without being tied to the current user and it works.
I do not use notification, I want the posted messages to appear immediately in the feed.
If I wrote my own custom feed (FeedCustom) component to display data, it would work fine, but how do I make it work with FlatFeed of getstream.io? Any help would be greatly appreciated.
import React, { useEffect, useState } from 'react';
import { StreamApp, FlatFeed, StatusUpdateForm } from 'react-activity-feed';
import 'react-activity-feed/dist/index.css';
// import FeedCustom from './FeedCustom';
const STREAM_API_KEY = 'XXXXXXXXXXXXXXXX';
const STREAM_APP_ID = 'XXXXX';
const App = () => {
const [userToken, setUserToken] = useState(null);
const [loading, setLoading] = useState(true);
const [locationId, setLocationId] = useState(null);
const [data, setData] = useState([]);
const callApi = async () => {
const response = await fetch('https://localhost:8080/user-token')
const userResponse = await response.json();
return userResponse;
};
useEffect(() => {
callApi()
.then(response => {
const resp = JSON.parse(response.body);
setLoading(false);
setUserToken(resp.userToken);
setLocationId(resp.locationId);
})
.catch(e => alert(e));
}, []);
const customDoFeedRequest = (client, feedGroup = 'timeline', userId = locationId, options) => {
const feed = client.feed(feedGroup, userId);
const feedPromise = feed.get(options);
feedPromise.then((res) => {
setData((data) => res.results);
});
return feedPromise;
}
return loading ? (
<div>.... Loading ....</div>
) : (
<StreamApp
apiKey={STREAM_API_KEY}
appId={STREAM_APP_ID}
token={userToken}
>
{/* <FeedCustom dataFeed={ data } /> */}
<FlatFeed doFeedRequest={customDoFeedRequest} />
<StatusUpdateForm
userId={locationId}
feedGroup={'timeline'}
onSuccess={(post) => setData((data) => [...data, post])}
/>
</StreamApp>
)
};
export default App;
My backend https://localhost:8080/user-token returns an object kind of:
{
userToken: 'XXXXXXX'
locationId: 'XXXXXXX'
}

Render fetched API json object in react component Typescript

i have my json received from api call and is saved in the state "data"
i want to show a loading screen while api is being fetched like i have a state for that too "Loading"
Loading ? Then render data on elements : Loading..
const App = () => {
const [data, setData] = useState([]);
const [Loading, setLoading] = useState(false);
useEffect(() => {
Fetchapi();
}, []);
const Fetchapi = async () => {
try {
await axios.get("http://localhost:8081/api").then((response) => {
const allData = response.data;
setData(allData);
});
setLoading(true);
} catch (e) {
console.log(e);
}
};
return (
<div>
i need my json object rendered here i tried map method on data and i am
getting errors and i have my json2ts interfaces imported in this
</div>
);
};
export default App;
I would camelCase your values/functions and move your fetchApi into the effect itself, as currently its a dependency.
Put setLoading(true) above your fetch request as currently it's not activating until the fetch goes through.
Then below it put setLoading(false), and also inside of your catch.
In your return statement you can now add something like this:
<div>
{loading ? "Loading..." : JSON.stringify(data)}
</div>
Edit
Example for the commented requests.
import { Clan } from "../clan.jsx"
// App
<div>
{loading ? "Loading..." : <Clan data={data}/>}
</div>
// New file clan.jsx
export const Clan = (props) => {
return (
<div>
<h1>{props.data.clan.name}</h1>
</div>
);
}
try this
interface ResponseData {
id: string
// other data ...
}
const App = () => {
const [data, setData] = useState<ResponseData | null>(null)
const [Loading, setLoading] = useState(true)
useEffect(() => {
Fetchapi()
}, [])
const Fetchapi = async () => {
try {
setLoading(true) // USE BEFORE FETCH
await axios.get("http://localhost:8081/api").then(response => {
setLoading(false) // SET LOADING FALSE AFTER GETTING DATA
const allData: ResponseData = response.data
setData(allData)
})
} catch (e) {
setLoading(false) // ALSO SET LOADING FALSE IF ERROR
console.log(e)
}
}
if (Loading) return <p>Loading...</p>
if (data?.length)
return (
<div>
{data.map(d => (
<div key={d.id}>{d.id}</div>
))}
</div>
)
return <div>no data found</div>
}
export default App

Can't perform a React state update on an unmounted component error when fetching data

I am having an issue when I am trying to fetch some data. For some reason, I keep receiving this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
This is the entire component, where I am fetching the data and then passing it in to the drawerconfig component This data then gets passed down further and further in to other components:
export default function Root() {
const [userImage, setUserImage] = useState();
const [userName, setUserName] = useState();
const [name, setName] = useState();
const [urlName, setUrlName] = useState();
const [userID, setUserID] = useState();
const [followerCount, setFollowerCount] = useState();
const [followingCount, setFollowingCount] = useState();
const [links, setLinks] = useState([{link: null}]);
const [pageLoading, setPageLoading] = useState(true);
// Following counts, displayname, image
const fetchData = useCallback((data) => {
const dataRef = firestore().collection('usernames');
const usersLinks = firestore().collection('links');
// Fetch user Links
usersLinks.doc(data.data().urlName).onSnapshot((doc) => {
const entries =
doc.data() === undefined ? [undefined] : Object.values(doc.data());
entries[0] === undefined ? setLinks([{link: null}]) : setLinks(entries);
});
dataRef.doc(data.data().urlName).onSnapshot((snap) => {
// Fetch users image
setUserImage(snap.data().imageUrl);
setUserID(snap.data().displayName);
setUserName(snap.data().userName);
setUrlName(data.data().urlName);
setName(snap.data().displayName);
setFollowerCount(snap.data().followers);
setFollowingCount(snap.data().following);
setPageLoading(false);
});
}, []);
// Fetch all data here
useEffect(() => {
auth().onAuthStateChanged((user) => {
if (user !== null) {
if (user.emailVerified) {
const cleanup = firestore()
.collection('users')
.doc(user.uid)
.onSnapshot(fetchData);
return cleanup;
}
}
});
}, [fetchData]);
return (
<>
{/* ALL SCREENS */}
{pageLoading ? (
<ActivityIndicator size="large" color="black" />
) : (
<DrawerConfig
links={links}
username={userName}
userimage={userImage}
userid={userID}
displayname={name}
urlname={urlName}
followerCount={followerCount}
followingCount={followingCount}
/>
)}
</>
);
}
Any help would be appreciated, Thank you
Looks like you need to modify your useEffect a bit - I don't think your listener is being unsubscribed when you unmount this component.
// Fetch all data here
useEffect(() => {
return auth().onAuthStateChanged((user) => {
...
})
})
.onAuthStateChanged() returns the unsubscribe function; useEffect accepts an unsubscribe function as a return to be executed on unmount.

Can not change state of object [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I am trying to fetch a single object, and it fetches, I get correct data in response.data, but i can't set it to state. It just remains as a null object. What am I doing wrong?
Post is a json object with several fields like: post_id, post_title, post_content and etc.
const [post, setPost] = useState({})
let id = match.params.id
useEffect(() => {
axios.get(`${BASE_URL}/post?id=${id}`)
.then(response => {
console.log(response.data)
setPost(response.data)
})
.then(() => {
console.log("post: ", post)
}
)
setAction like your setPost are asynchronous, as stated in the official REACT documentation (https://reactjs.org/docs/hooks-reference.html#usestate); this means that, once the setAction is executed, you don't know when it will be actually executed and terminated: you will know because the component will re-render.
In your case, if you'd like to perform action AFTER post has got the new value, you would need the useEffect hook (https://reactjs.org/docs/hooks-reference.html#useeffect):
React.useEffect(() => {
console.log(post);
}, [post]);
By the way, I think you would want the data inside the response, so you would probably save the JSON retrieved from the body of the HTTP Response, that you can get using response.json().
EDIT: As stated in the comment from Siradji Awoual, what I wrote about response and response.json() is not valid for Axios (but it still is for fetch API).
Setting a state is asynchronous. That means you don't know exactly when that action will finish executing.
If I were you, I would use something like useEffect to check if the state is being set.
React.useEffect(() => console.log(post), [post])
Using axios.get is low-level and requires that you hook up a bunch of extra stuff to get things working correctly. Instead, try writing custom hooks to abstract this logic away -
const identity = x => x
const useAsync = (runAsync = identity, deps = []) => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [result, setResult] = useState(null)
useEffect(_ => {
Promise.resolve(runAsync(...deps))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, deps)
return { loading, error, result }
}
Using useAsync looks like this -
const MyApp = () => {
const { loading, error, result } =
useAsync(_ => axios.get("./foo.json").then(res => res.json()))
if (loading)
return <p>loading...</p>
if (error)
return <p>error: {error.message}</p>
return <pre>result: {result}</pre>
}
But you will probably have many components that fetch JSON, right? We can make an even higher level custom hook, useJSON that is a specialization of useAsync -
const fetchJson = (url = "") =>
axios.get(url).then(r => r.json()) // <-- stop repeating yourself
const useJson = (url = "") =>
useAsync(fetchJson, [url]) // <-- useAsync
const MyApp = () => {
const { loading, error, result } =
useJson("./foo.json") // <-- dead simple
if (loading)
return <p>loading...</p>
if (error)
return <p>error: {error.message}</p>
return <pre>result: {result}</pre>
}
See the custom hooks in action in this functioning code snippet -
const { useState, useEffect } =
React
// fake fetch slows response down so we can see loading
const _fetch = (url = "") =>
fetch(url).then(x =>
new Promise(r => setTimeout(r, 2000, x)))
const identity = x => x
const useAsync = (runAsync = identity, deps = []) => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [result, setResult] = useState(null)
useEffect(_ => {
Promise.resolve(runAsync(...deps))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, deps)
return { loading, error, result }
}
const fetchJson = (url = "") =>
_fetch(url).then(r => r.json())
const useJson = (url = "") =>
useAsync(fetchJson, [url])
const MyComponent = ({ url = "" }) => {
const { loading, error, result } =
useJson(url)
if (loading)
return <pre>loading...</pre>
if (error)
return <pre style={{color: "tomato"}}>error: {error.message}</pre>
return <pre>result: {JSON.stringify(result, null, 2)}</pre>
}
const MyApp = () =>
<main>
ex 1 (success):
<MyComponent url="https://httpbin.org/get?foo=bar" />
ex 2 (error):
<MyComponent url="https://httpbin.org/status/500" />
</main>
ReactDOM.render(<MyApp />, document.body)
pre {
background: ghostwhite;
padding: 1rem;
white-space: pre-wrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

async custom hooks does't provide updated data

I have a simple hook to help me handle a POST request. With the following code, I expect unsub will be true after the POST is done. Can anyone point out anything I could have done wrong?
Custom Hook
const useUnsubscribeEmail = () => {
const [userId, setUserId] = useState(null);
const [unsub, setUnSub] = useState();
const UNSUB_URL = '/web-registry-api/v1/reviews/unsubscription';
useEffect(() => {
if (userId) {
// async POST call
(async () => {
try {
await ApiService.post(`${UNSUB_URL}/${userId}`);
// update unsub value
setUnSub(true);
} catch (error) {
console.error(error)
}
})();
}
}, [userId]);
return [unsub, setUserId];
};
export default useUnsubscribeEmail;
Component
const ReviewUnsubscription = () => {
const { userId } = useParams();
const [unsub, unsubscribeEmail] = useUnsubscribeEmail();
return (
<MinimumLayout>
<div className={styles.content}>
<h1>Unsubscribe from email reminders to review products you’ve received from Zola?{unsub}</h1>
{/* unsub here is still undefined */}
<Button disabled={unsub} onClick={() => { unsubscribeEmail(userId); }} variant="primary" className={styles.button}>Unsubscribe</Button>
</div>
</MinimumLayout>
);
};
unsub is still going to be undefined until you click the button as you have not set a default state for it in your hook.
change : const [unsub, setUnSub] = useState(); to const [unsub, setUnSub] = useState(false); is what I would recommend
I tested on my side and works just fine; However, I cannot test the APIService.post.

Resources