Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS Amplify in React, how can I render the new data from the subscribed listener

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.

like image 843
Leem.fin Avatar asked Sep 20 '25 10:09

Leem.fin


1 Answers

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

like image 170
LoganRx Avatar answered Sep 22 '25 11:09

LoganRx