Other answers that involve checking for globals like globalThis.React will work fine if the website uses react via a dedicated script HTML element, but will otherwise face the problem that bundlers like webpack can wrap dependency code inside immediately-invoked-function-expressions or other mechanisms for encapsulating their details and preventing them from unnecessarily bleeding into the global scope. Such encapsulation is very often desirable.
One can try to get around this by testing if DOM elements have properties on them that get set in React contexts, such as _reactRootContainer. Like so:
Array.from(document.querySelectorAll('*'))
.some(e => e._reactRootContainer !== undefined)
A page can have tons of elements, so one can try to optimize based on an assumption that React code will call ReactDOM.createRoot and pass it an element queried via HTML id. Ie. instead of checking all DOM elements, only check those that have an id attribute. Like so:
Array.from(document.querySelectorAll('[id]'))
.some(e => e._reactRootContainer !== undefined)
Be aware that the id-filtering optimization will not always work because the id assumption will not always hold.
Important: Since this method relies on the react DOM already having been created, it should be careful not to be applied until one thinks the react DOM has been created. Once can try to apply techniques like the defer attribute on scripts, or using document.onload, or setTimeout, or a combination of them.
Note that wrapping the nodelist from the query to turn it into an array is probably sub-optimal performance-wise, but I feel that to try to optimize it might be micro-optimizing. A check for the presence of react should probably be saved to a variable and never performed again anyway.