1

I have an object which value updates and i would like to know if there is a way to re-render the component when my object value is updated. I can't create a state object because the state won't be updated whenever the object is. Using a ref is not a good idea(i think) since it does not cause a re-render when updated.

The said object is an instance of https://docs.kuzzle.io/sdk/js/7/core-classes/observer/introduction/

itishermann
  • 27
  • 1
  • 10
  • have you tried `useEffect` ? putting your object inside the `useEffect dependency array` would do the job and force update the component when your object value is changing. more about [useEffect](https://reactjs.org/docs/hooks-effect.html) – Omar Dieh Aug 05 '22 at 18:13
  • Are you changing object's ref or just updating props of it – Iavor Orlyov Aug 08 '22 at 11:15
  • could you share how you are updating the object? – miraj Aug 09 '22 at 18:34

3 Answers3

1

The observer class doesn't seem to play well with your use case since it's just sugar syntax to manage the updates with mutable objects. The documentation already has a section for React, and I suggest following that approach instead and using the SDK directly to retrieve the document by observing it.

You can implement this hook-observer pattern

import React, { useCallback, useEffect, useState } from "react";
import kuzzle from "./services/kuzzle";

const YourComponent = () => {
  const [doc, setDoc] = useState({});
  const initialize = useCallback(async () => {
    await kuzzle.connect();
    await kuzzle.realtime.subscribe(
      "index",
      "collection",
      { ids: ["document-id"] },
      (notification) => {
        if (notification.type !== "document" && notification.event !== "write")
          return;
        // getDocFromNotification will have logic to retrieve the doc from response
        setDoc(getDocFromNotification(notification));
      }
    );
  }, []);

  useEffect(() => {
    initialize();
    return () => {
      // clean up
      if (kuzzle.connected) kuzzle.disconnect();
    };
  }, []);

  return <div>{JSON.stringify(doc)}</div>;
};

diedu
  • 19,277
  • 4
  • 32
  • 49
0

useSyncExternalStore, a new React library hook, is what I believe to be the best choice.

StackBlitz TypeScript example

In your case, a simple store for "non state object" is made:

function createStore(initialState) {
  const callbacks = new Set();
  let state = initialState;

  // subscribe
  const subscribe = (cb) => {
    callbacks.add(cb);
    return () => callbacks.delete(cb);
  };

  // getSnapshot
  const getSnapshot = () => state;

  // setState
  const setState = (fn) => {
    state = fn(state);
    callbacks.forEach((cb) => cb());
  };

  return { subscribe, getSnapshot, setState };
}

const store = createStore(initialPostData);

useSyncExternalStore handles the job when the update of "non state object" is performed:

const title = React.useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().title
);

In the example updatePostDataStore function get fake json data from JSONPlaceholder:

async function updatePostDataStore(store) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${Math.floor(Math.random()*100)+1}`)
  const postData = await response.json()
  
  store.setState((prev)=>({...prev,...postData}));
};
Boris Traljić
  • 956
  • 1
  • 10
  • 16
0

My answer assumes that the object cannot for some reason be in React as state (too big, too slow, too whatever). In most cases that's probably a wrong assumption, but it can happen.

I can't create a state object because the state won't be updated whenever the object is

I assume you mean you can't put that object in a React state. We could however put something else in state whenever we want an update. It's the easiest way to trigger a render in React.

Write a function instead of accessing the object directly. That way you can intercept every call that modifies the object. If you can reliably run an observer function when the object changes, that would work too.

Whatever you do, you can't get around calling a function that does something like useState to trigger a render. And you'll have to call it in some way every time you're modifying the object.

const myObject = {};
let i = 0;
let updater = null;

function setMyObject(key, value) {
  myObject[key] = value;
  i++;
  if (updater !== null) {
    updater(i);
  }
};

Change your code to access the object only with setMyObject(key, value).

You could then put that in a hook. For simplicity I'll assume there's just 1 such object ever on the page.

function useCustomUpdater() {
  const [, setState] = useState(0);
  useEffect(()=>{
    updater = setState;
    
    return () => {
      updater = null;
    }
  }, [setState]);
}

function MyComponent() {
  useCustomUpdater();

  return <div>I re-render when that object changes</div>;
}

Similarly, as long as you have control over the code that interacts with this object, you could wrap every such call with a function that also schedules an update.

Then, as long as your code properly calls the function, your component will get re-rendered. The only additional state is a single integer.

The question currently lacks too much detail to give a good assessment whether my suggested approach makes sense. But it seems like a very simple way to achieve what you describe.

It would be interesting to get more information about what kind of object it is, how frequently it's updated, and in which scope it lives.

inwerpsel
  • 2,677
  • 1
  • 14
  • 21