Fill schema from websocket messages

Hi everyone,
I have an app that renders JSON schemas, using JSONForms with material renderers, and allows user to fill them both manually and from API requests. There is a server beneath this app that listen from user requests and send them to the frontend via websocket.
The only way I found so far to actually fill and render the changes coming from the server along with the manual ones, is to create custom renderers assigning a WebSocket.addEventListener to each field and calling the onChange method ( returned by useDebouncedChange) inside the listener.
So far everything works fine but:

  1. in this way I have to create many custom renderers and I feel that there should be a much cleaner way. I would like to have only one socket listener in a component that renders the JsonForms element, that changes the data prop and re-renders the schema (or that trigger onChange from socket messages in some way). A solution to this makes point 2) irrelevant.
  2. I have to render very big schemas and to avoid bad performances I am ‘hiding’ parts of the schema using Accordion with TransitionProps={{ unmountOnExit: true }}. At the first render the hidden part of the schema are not reachable by socket messages being their socket listeners never assigned. The only things that comes to me is to render everything at the beginning and then collapse the Accordions. Any better idea?

Hi @andrecchia,

I agree that integrating server updates via the UI is not a good practice, so I would also not recommend doing it this way.

I would recommend to merge in the server data with the local data yourself into a combined data object and then hand over this updated data to JSON Forms. Then you also don’t need custom renderers. In case you receive new data at the same time as editing the form you just need to be careful to not lose any changes.

Thanks @sdirix for the quick response.
My problem that I couldn’t find a way to do what you suggest. To begin I tried to do something like this:

export const ShowForm = (props: ShowFormProps) =>{
  const {schema, initialData} = props;
  const [data, setData] = useState(initialData ? initialData : {});
  useEffect(() =>{
    socket.addEventListener('message', (event: any) => { // socket is a WebSocket instance
      const reqObj=JSON.parse(event.data);
      if(reqObj['path'] === '/compile_v2'){
        for (const [key,val] of Object.entries(reqObj['content'])){
          let addData=data;
          set(addData,key,val); // this is 'lodash/set'
          setData(data => set(data,key,val));
        }
      }
    });
  },[]);
  return(
    <JsonForms
                schema={schema}
                data={data}
                renderers={renderers}
                cells={materialCells}
                onChange={({ data, errors }) => {setData(data); compiledData = data;}}
                />
   );
}

but while I can see that the socket message updates data (printing it on console), the schema is not re-rendered until after I trigger some change elsewhere.
I’m sorry if this may be a more React related than JSONForms question, I am relatively new to these arguments. Really nice instrument and communuty by the way, thanks again!

Hi @andrecchia,

No problem! Most optimizations in React are based on the identity of the arguments. The issue here is that the data is updated by just doing a deep nested change via lodash’s set, therefore oldData === newData.

This has two consequences:

  • setData(data => set(data, key, val)) will not mark the component to be rerendered as you are handing over the same object to setData as is already stored.
  • Even if the component is rerendered by some other effect, JSON Forms will not rerender as we use the the standard React.memo optimization which prevents rerendering if all properties are equal.

To conveniently update the data to break these optimizations I would like to recommend lodash’s fp/set. This variant of set makes sure to not only update the nested value but also all of its parents. Using that set, your code should work.

1 Like