Fowarding a ref with mobx - javascript

i am trying to build a custom component video player with react and mobx, and i need to drill a refrence from a main component to a child Component but i'm getting an error message when i use the forwardRef function on a component that is an observer.
the error message is "baseComponent is not a function"
Here's the code:
// code for main component
const videoPlayer = () => {
const controlsRef = useRef<any>(null);
return (<div>
// video player screen code //
<VideoPlayerButtonCode ref={controlsRef} />
<div>)
}
// the code for the players component
interface IProps{
controlsRef: any;
}
const VideoPlayerButtonCode: React.FC<IProps> = fowardRef({props from iprops}, controlsRef ) => {
return (<div>
<Button ref={controlsRef}>Button i want to get a ref for from main</Button>
<div>)
}
export default observer(VideoPlayerButtonCode)
thats a vague abstraction of the code but the same implementation.
is there any help for mobx supports for ref or is there a way i can store the refrence in a mobx store?

What version of mobx-react are you using? It should work fine with latest 7.0.0 version, but it seems to fail if you are using mobx-react-lite#3.0.0.
I've made codesandbox with all working variants as for now: https://codesandbox.io/s/httpsstackoverflowcomquestions64227496-75xdz?file=/src/App.js
For example same version as your works fine:
const ComponentWithForwardRef = React.forwardRef((props, ref) => {
return <div ref={ref}>My Observer Component</div>;
});
const ObserverComponentWithForwardRef = observer(ComponentWithForwardRef);
There is also a forwardRef option for observer HOC, but it only currently works with mobx-react-lite, and does not work with regular mobx-react package due to this bug https://github.com/mobxjs/mobx-react/issues/868
You can use it like that:
const MyObserverComponent = observer(
(props, ref) => {
return <div ref={ref}>My Observer Component</div>;
},
{ forwardRef: true }
);
If everything fails you can just use custom prop for your ref like so:
<MyObserverComponentCustomRef innerRef={myRef} />
// ...
const MyObserverComponentCustomRef = observer((props) => {
return <div ref={props.innerRef}>My Observer Component Inner Ref</div>;
});

Related

How do I access app.state from a Cypress test in a Remix project

Cypress has a way to expose the app's state to the test runner -- in React it usually looks like this:
class MyComponent extends React.Component {
constructor (props) {
super(props)
// only expose the app during E2E tests
if (window.Cypress) {
window.app = this
}
}
...
}
Then you could access your state in a test with
cy.window()
.its('app.state')
.should('deep.equal', myStateObject)
However, the setup for Remix projects relies on functional components. I've tried this in my root.tsx component with a useEffect call:
export default function App() {
useEffect(() => {
window.app = App;
}, []}
}
as well as in the root route (routes/index.tsx) by importing the <App /> component and using the logic in the useEffect function above. Neither of these options are working and I'm not sure where else to go here. Remix's GitHub issues are devoid of questions about this issue, so maybe I'm going about this the wrong way. Any help is appreciated! Thanks!
I haven't done much work with Remix, but there is a question here that might be useful:
React - getting a component from a DOM element for debugging.
Note the last paragraph
Function components
Function components don't have "instances" in the same way classes do, so you can't just modify the FindReact function to return an object with forceUpdate, setState, etc. on it for function components.
That said, you can at least obtain the React-fiber node for that path, containing its props, state, and such. To do so, modify the last line of the FindReact function to just: return compFiber;
There's a lib cypress-react-app-actions that implements this for Cypress
export const getReactFiber = (el) => {
const key = Object.keys(el).find((key) => {
return (
key.startsWith('__reactFiber$') || // react 17+
key.startsWith('__reactInternalInstance$') // react <17
)
})
if (!key) {
return
}
return el[key]
}
// react 16+
export const getComponent = (fiber) => {
let parentFiber = fiber.return
while (typeof parentFiber.type == 'string') {
parentFiber = parentFiber.return
}
return parentFiber
}
One of the example tests is
/// <reference types="cypress" />
import { getReactFiber, getComponent } from '../support/utils'
it('calls Example double()', () => {
cy.visit('/')
cy.get('.Example').within(() => { // select via className of component
cy.contains('[data-cy=count]', '0')
cy.get('[data-cy=add]').click().click()
cy.contains('[data-cy=count]', '2')
cy.root().then((el$) => {
const fiber = getReactFiber(el$[0])
console.log(fiber)
const component = getComponent(fiber)
console.log(component.stateNode)
cy.log('calling **double()**')
component.stateNode.double() // work with component for functional
})
cy.contains('[data-cy=count]', '4')
})
})
This example is for class components, but given the info in Function components section above, you would use the component object rather than component.stateNode.

React HOC: Pass data attributes to the first child/element of wrapped component

I have a hoc component like this:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => (
<WrappedComponent {...props} data-test-id="this-is-a-element" />
);
return ModifiedComponent;
};
export default withAttrs;
and I use it like this:
import React from 'react';
import withAttrs from './withAttrs';
const SomeLink = () => <a><p>hey</p</a>;
export default withAttrs(SomeLink);
I expect to have an anchor tag like this:
<a data-test-id="this-is-a-element"><p>hey</p></a>
But the hoc doesn't add the data-attribute to the first element. Is there a way to achieve this?
But the hoc doesn't add the data-attribute to the first element.
It's not the HOC that isn't adding it, it's SomeLink, which doesn't do anything with the props the HOC passes to it.
The simple answer is to update SomeLink:
const SomeLink = (props) => <a {...props}><p>hey</p></a>;
That's by far the better thing to do than the following.
If you can't do that, you could make your HOC add the property after the fact, but it seems inappropriate to have the HOC reach inside the component and change things. In fact, React makes the element objects it creates immutable, which strongly suggests you shouldn't try to mess with them.
Still, it's possible, it's probably just a bad idea:
export const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
/*export*/ const withAttrs = (WrappedComponent) => {
const ModifiedComponent = (props) => {
// Note we're *calling* the function, not just putting it in
// a React element via JSX; we're using it as a subroutine of
// this component rather than as its own component.
// This will only work with function components. (You could
// write a version that handles class components as well,
// but offhand I don't think you can make one HOC that handles
// both in this case.)
const result = WrappedComponent(props);
// THIS IS PROBABLY A VERY BAD IDEA. React makes these objects
// immutable, probably for a reason. We shouldn't be mucking
// with them.
return {
...result,
props: {
...result.props,
"data-test-id": "this-is-a-element",
},
};
};
return ModifiedComponent;
};
const SomeLink = () => <a><p>hey</p></a>;
const SomeLinkWrapped = withAttrs(SomeLink);
const Example = () => {
return <div>
<div>Unwrapped:</div>
<SomeLink />
<div>Wrapped:</div>
<SomeLinkWrapped />
</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
/* So we can see that it was applied */
[data-test-id=this-is-a-element] {
color: green;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Again, I don't think I'd do that except as a very last resort, and I wouldn't be surprised if it breaks in future versions of React.

Using useContext and useReducer in NextJS and reading null as context

Attempting to create a tic-tac-toe game in NextJS and trying to create a board context for my components to read. Once I introduced a reducer into the Wrapper for a more complex state, I am now getting a null error.
Errors:
TypeError: Cannot read properties of null (reading 'useContext')
Error in then the terminal:
Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
I believe I only have the react version installed in this project that next installs when using create-next-app. I'm largely stuck on where to look for what's going wrong. Can I not use the useReducer hook instead of the useState hook when providing context in NextJS?
Context Declaration:
const TTTContext = createContext();
const TTTBoard = new Board(3);
const gameStateReducer = (state, action) => {
switch (action.type) {
//...reducer logic
}
}
const TTTWrapper = ({ children }) => {
const [gameState, dispatch] = useReducer(gameStateReducer, {board: TTTBoard, playerPiece:"DEFAULT"});
const gameContextValue = () => {gameState, dispatch};
return (
<TTTContext.Provider value={gameContextValue}>
{children}
</TTTContext.Provider>
);
}
const useTTTContext = () => {
return useContext(TTTContext)
};
module.exports = { TTTWrapper, useTTTContext }
The context is applied in _app.js here:
<TTTWrapper>
<Component {...pageProps} />
</TTTWrapper>
And the game state is retrieved in the actual Grid component here:
import { useTTTContext } from "../contexts/TTTContext";
const {gameState, dispatch} = useTTTContext();
Solved: const {gameState, dispatch} = useTTTContext(); was outside of its component body.
From what I can see of the code you've provided, the issue seems to be in the last snippet:
import { useTTTContext } from "../contexts/TTTContext";
const {gameState, dispatch} = useTTTContext();
The useTTTContext doesn't appear to be used/called inside any React function component body nor any custom React hook. It should be moved into either a React function or custom hook body.

React call functions on renderless component

I need to have a component for handling settings, this component (called Settings) stores state using useState(), for example the primary color.
I need to create a single instance of this component and make it available to every component in the app. Luckily, I already pass down a state dict to every component (I'm very unsure if this is the correct way to achieve that btw), so I can just include this Settings constant.
My problem is that I don't know how to create the component for this purpose, so that I can call its functions and pass it to children.
Here is roughly what my Settings component looks like:
const Settings = (props) => {
const [primaryColor, setPrimaryColor] = useState("")
const getColorTheme = (): string => {
return primaryColor
}
const setColorTheme = (color: string): void => {
setPrimaryColor(color)
}
return null
}
export default Settings
Then I would like to be able to do something like this somewhere else in the app:
const App = () => {
const settings = <Settings />
return (
<div style={{ color: settings.getColorTheme() }}></div>
)
}
Bear in mind that I'm completely new to react, so my approach is probably completely wrong.
You can use a custom Higher Order Component(HOC) for this purpose, which is easier than creating a context(even thougn context is also a HOC). A HOC takes a component and returns a new component. You can send any data from your HOC to the received component.
const withSettings = (Component) => {
const [settings, setSettings] = useState({})
// ...
// ...
<Component {...props} settings={settings}/>
);
And you can use it like this:
const Component = ({ settings }) => {
...your settings UI
}
export default SettingsUI = withSettings(Component);
You can read more about HOCs in the official react documentation

Duplicate componentDidMount logic with useEffect() to load external JavaScript on client side

I’m implementing a rich text editor into a NextJS project. There are no React components for it, and it runs only on the client side, so I have to load the JavaScript and CSS files from an external source and work around SSR. Please don't recommend to use another tool, as that is not an option.
The tool works fine as a class component, but I’d like to port it into a functional component. When I test the functional component, it works occasionally — namely, after I change my file and save (even if it's just adding a space). But as soon as I refresh the page, I lose the editor. I thought it was because the component hadn’t mounted, but now I check for that, yet the issue persists.
I’ve tried various approaches, including Next’s Dynamic import with SSR disabled, but so far only the class method below has worked (the editor works by binding to the <textarea> element):
import React from "react";
import Layout from "../components/Layout";
class Page extends React.Component {
state = { isServer: true };
componentDidMount() {
this.MyEditor = require("../public/static/cool-editor.js");
this.setState({ isServer: false }); // Trigger rerender.
var app = MyEditor("entry"); // Create instance of editr.
}
render(props) {
return (
<Layout>
<textarea id="entry"></textarea>
</Layout>
);
}
}
export default Page;
Last attempt at functional component:
import React, { useEffect } from "react";
import Layout from "../components/Layout";
function hasWindow() {
const [isWindow, setIsWindow] = React.useState(false);
React.useEffect(() => {
setIsWindow(true);
return () => setIsWindow(false);
}, []);
return isWindow;
}
const Editor = () => {
useEffect(() => {
const script = document.createElement("script");
script.src =
"http://localhost:3000/static/article-editor/cool-editor.js";
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);
var app = MyEditor("entry");
return (
<Layout>
<textarea id="entry"></textarea>
</Layout>
);
};
const Page = () => {
const isWindow = hasWindow();
if (isWindow) return <Editor />;
return null;
};
export default Page;
You can use useRef hook in <textarea> tag:
const refContainer = useRef(null);
return <textarea ref={refContainer}>
then useEffect to check if the the element has been mounted.
useEffect(() => {
if (refContainer.current) {
refContainer.current.innerHTML = "ref has been mounted";
console.log("hello");
}
}, []);
Check the code here: https://codesandbox.io/s/modest-dubinsky-7r3oz
Some of the things I could suggest changing:
var app = MyEditor("entry"); is being created on every render. Consider using useRef as a way to keep instance variable: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
In Editor, the MyEditor variable is not defined.
hasWindow includes a useEffect that runs once (with empty dependency array), I don't think it needs the clean up function. To check staying at browser or server, you could simply use const isServer = type of window === 'undefined'
Custom hook should be named with prefix use

Categories

Resources