how to emulate messages/events with react useState and useContext?

Viewed 551

I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:

Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.

This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.

So I guess the react way is to somehow represent this event with the app state?

I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):

export interface MessageQueue{
  messages: number[],
  push:(num: number)=>void,
  pop:()=>number
}

const defaultMessageQueue{
  messages:[],
  push: (num:number) => {throw new Error("don't use default");},
  pop: () => {throw new Error("don't use default");}
}

export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);

In the component I'm providing this with:

const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
  messages: messages,
  push:(num:number)=>{
    setmessages([...messages, num]);
  },
  pop:()=>{
    if(messages.length==0)return;

    const message = messages[-1];
    setmessages([...messages.slice(0, -1)]);
    return message;
  }
}}>

Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.

Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?

2 Answers

Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:

Define the route to have an optional path param. Something like content-page/:section?

In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.

const { section } = useParams();

useEffect(() => {
  // the code to jump to the section
}, [section]);

I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener. Also if it's a matter of scrolling you can scrollIntoView using refs

Related