I am developing a React project & using AWS Amplify as a tool for serverless backend, in which I have a DynamoDB as my database in AWS Cloud.
My React app needs to detect realtime data update in DynamoDB. I am using GraphQL API from Amplify.
The GraphQL API generated by Amplify provides a subscriptions
API for subscribing for data changes in real-time. I think that's exactly what I need, so I am using it:
import React, {useEffect, useState, useRef} from 'react';
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import { onCreateData } from './graphql/subscriptions';
// subscribe to listen to new data creation in DynamoDB
const subscription = API.graphql(
graphqlOperation(onCreateData)
).subscribe({
next: (newData) => {
// New data is received here outside 'App' functional component,
// how can I render the data then?
console.log(`new data: ${JSON.stringify(newData)}`);
}
});
// I can't put the above subscription code inside App component, I don't know why but it just doesn't work unless I move it outside the App functional component.
function App() {
const [dataArray, setDataArray] = useState([]);
...
useEffect(() => {
fetchDataArray();
showDataArray();
return () => {
//cleanup
}
}, [dataArra])
return (<div>
<canvas ref={canvasRef}/>
</div>)
}
export default App;
Problem is the subscription
for new data insertion in DynamoDB only work if I put it outside the function App(){...}
as you can see above. So, when newData
is received in the subscription callback next: (newData)
, how can I show the new data in App
component?
You can see inside my App
component I have state variable dataArray
, the ideal solution is to update this dataArray
state with newData
but I think it is impossible now. So, in general I wonder how can I show the new data in my App
component now?
P.S. Amplify GraphQL API doc sample code (if you see the "subscription" section) also shows that the subscription code is in the same level as imports.
We use amplify for our projects and one example we have is for a simple chat function below:
interface ChatMessagesProps {
internal?: boolean
convoId: string
}
export const ChatMessages: FC<ChatMessagesProps> = ({ internal, convoId }) => {
const classes = useStyles()
const {
appState: { user }
} = useAppState()
let [messages, setMessages] = useState<any>([])
let [currentConversationId, setConversationId] = useState<string>(convoId)
const listRef = useRef<VariableSizeProps>()
let subscription = useRef<ISubscriptionObject | null>(null)
let updateSubscription = useRef<ISubscriptionObject | null>(null)
const addNewMessage = ({ onCreateMessage }) => {
console.log(onCreateMessage)
// from here we can do whatever we want with the data within the context of this component
}
const messageUpdated = ({ onUpdateMessage }) => {
console.log(onUpdateMessage)
// from here we can do whatever we want with the data within the context of this component
}
const getConversationDetails = async () => {
subscription.current = graphQLSubscription(
onCreateMessage,
{ conversationId: currentConversationId },
addNewMessage
)
updateSubscription.current = graphQLSubscription(
onUpdateMessage,
{ conversationId: currentConversationId },
messageUpdated
)
}
useEffect(() => {
if (currentConversationId) {
getConversationDetails()
}
return () => {
subscription?.current?.unsubscribe()
updateSubscription?.current?.unsubscribe()
}
}, [currentConversationId])
return (
<div className={classes.root}>
<MessageList listRef={listRef} messages={messages} internal={internal} />
<MessageInput userId={user?.id || ''} internal={internal} conversationId={currentConversationId} />
</div>
)
}
const useStyles = makeStyles(() => ({
root: {
width: '100%',
height: '100%',
backgroundColor: 'white',
position: 'relative'
}
}))
And we use the helper graphQLSubscription
export const graphQLSubscription = (subscription: string, options: any, callback: (d: any) => void) => {
return API.graphql(graphqlOperation(subscription, options)).subscribe({
next: ({ value: { data, errors } }) => {
console.log(errors)
callback(data)
}
})
}
For your example code you would just want to store you subscription in a ref in order to be able to unsubscribe on unmount as well
import React, {useEffect, useState, useRef} from 'react';
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import { onCreateData } from './graphql/subscriptions';
function App() {
const [dataArray, setDataArray] = useState([]);
const subscriptionRef = useRef()
...
useEffect(() => {
fetchDataArray();
showDataArray();
subscriptionRef.current = API.graphql(
graphqlOperation(onCreateData)
).subscribe({
next: (newData) => {
// New data is received here outside 'App' functional component,
// how can I render the data then?
console.log(`new data: ${JSON.stringify(newData)}`);
}
});
return () => {
//cleanup
subscriptionRef.current.unsubscribe()
}
}, [dataArra])
return (<div>
<canvas ref={canvasRef}/>
</div>)
}
export default App;
From there you can extract it into helper functions within the component or within you project like above
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With