React useRef Hook with Flow Typings - reactjs

I'm using React useRef with Flow typings and I'm trying to write a wrapper component for a third party web components library.
The web component expects a changeCallback function and I'm using the ref to assign it to the ref.
function RadioButtonGroup({ onChange, children }) {
const ref: { current: null | ElementRef<ElementType> = React.useRef(null);
React.useEffect(() => {
if (ref.current) ref.current.changeCallback = onChange;
}, [onChange]);
return <web-component ref={ref}>{children}</web-component>
}
Since HTMLElement does not contain a property called changeCallback flow throws an error.
Cannot assign handleChange to ref.current.changeCallback because property changeCallback is missing in HTMLElement
I tried extending "ElementType" with the property like this
ElementRef<ElementType & { changeCallback: Function }>
But this results in the following error:
Cannot instantiate ElementRef because object type [1] is not a React component.
The web component does not fire the "change" event on change. It executes the function changeCallback. Here's the documentation for the library.
// MyComponent.js
class MyComponent extends Component {
constructor() {
// ...
// Create a ref
this.sdxSelectEl = React.createRef();
}
componentDidMount() {
// Attach callback here to ref
this.sdxSelectEl.selectCallback = (selection) => console.log(selection);
}
render() {
// ...
<sdx-select ref={el => (this.sdxSelectEl = el)} />
// ...
}
}

The solution is to call useRef with an explicit type argument to represent the expected type:
const ref = React.useRef<null | (HTMLElement & { changeCallback: Function })>(null);

I believe you need to use addEventListener to attach the callback to a web component:
if (ref.current) ref.current.addEventListener('change', onChange);

Related

Call function in components that are rendered by a factory component

I have the following problem:
I have a react functional component A (the parent component)
In the parent Component A, a factory component named < Component /> creates different Components such as Component B,C,D by using plain JSON objects.
What I want to achieve:
Component B,C and D shall all implement a handlerFunction with specific code on their own. So the handlerFunction is not provided by the parent component, it is implemented by the Components B,C and D on their own.
I want to call the specific handlerFunction of each Component B,C, and D.
How is this possible ?
Right, functional components, on their own, cannot be assigned a react ref, but you can forward the ref or pass a ref as a named prop.
In the class-based component example you have something like
class ComponentA extends Component {
handlerFunction = () => {
console.log("A handler function");
};
render() {
return ...;
}
}
and to invoke the handlerFunction, attach the ref and call ref.current.handlerFunction() in your code
const someFunction = () => {
...
refA.current.handlerFunction();
...
}
...
<ComponentA ref={refA} />
For a functional component you can forward the ref and use the useImperativeHandle hook to "connect" the ref to the internal handler function
const ComponentB = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
handlerFunction
}));
const handlerFunction = () => {
console.log("B handler function");
};
return ...;
});
and to invoke the handlerFunction, same thing, call ref.current.handlerFunction()
const someFunction = () => {
...
refB.current.handlerFunction();
...
}
...
<ComponentB ref={refB} />

How to initialize the react functional component state from props

I'm using React hooks for app state, I wondered about how to initialize the functional component state using props? The useState hook doc says something definitive like,
const [count, setCount] = useState(0);
I want to initialize that 0 value by the value of props being passed to the component. The Older as,
import React from 'react';
export default class Sym extends React.Component{
constructor(props){
super(props);
this.state = {
sym : [0,3,2,8,5,4,1,6],
active: this.props.activeSym
}
this.setActive = this.setActive.bind(this);
}
setActive(itemIndex){
this.setState({
active: itemIndex
});
}
render(){
return (
<div><h1>{ this.state.sym[this.state.active]}</h1></div>
);
}
}
works fine. Where the parent Component passes activeSym prop and Sym component initializes the state with it using this.props.activeSym in constructor. Is there any workaround to achieve same in function component?
First you can define it from props (if the prop exist):
const [count, setCount] = useState(activeSym);
And then you can update this value, when prop doesn't have a value immediately (when component rendered):
useEffect(() => {
if (activeSym) {
setCount(activeSym);
}
}, [activeSym])
Yes, this can be possible with functional component too! You just need to add useEffect to listen to prop change for initializing state with prop value
export const newComponent = (props) => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
useEffect(() => {
setVal(value);
}, [value]);
return <div>{val}</div>;
};
Attching sandbox link
https://codesandbox.io/s/confident-agnesi-ohkq7?file=/src/MakeComponent.js
Yes you can first define state using props:
const [name, setName] = useState(props.obj?.name);
And then you can if the state is still undefined means props doesn't have a value, then:
useEffect(() => {
if (JSON.stringify(props.obj) !== "{}") {
setName(props.obj?.name);
}
}, [props.obj])
Just as follows :
const MyFunctionalComponent = ({myProp}) => {
const [count, setCount] = useState(myProp)
return (
/* ... */
)
}
There are two ways you can change the state:
one is using this.state and
another one is this.setState.
We use the first method to initialize the state in the constructor, and the second method is used for the rest of the time.
Initialize State in the Constructor
One way is to initialize the state is in the constructor. As we discussed earlier constructor is the first method to be called when React instantiates the class. This is the perfect place to initialize the state for the component because the constructor is called before the React renders the component in the UI.
class WithConstructor {
constructor() {
this.state = {
name: "StackOverflow"
}
}
}
Initialize State Without Constructor
Another way of initializing state in React is to use the Class property. Once the class is instantiated in the memory all the properties of the class are created so that we can read these properties in the render function.
class WithoutConstructor {
state = {
name: "StackOverflow"
}
}

How do I call a method within a Child component from the Parent in React Native

How do I call a method within a Child component from the Parent in React Native? What I essentially want to do is emulate what componentDidMount() does for class components in a functional component.
I've been getting the error "Function components cannot be given refs" and that I may want to use React.ForwardRef().
ps. idk how i would go about reformatting the child observer, pardon the bad formatting
class Dashboard extends Component {
constructor(props) {
super(props);
this.load = React.createRef();
componentDidMount() {
this.load.current.loadAudio();
}
render(){
latestEP.query = ref => ref.orderBy("id", "desc").limit(1);
return(
{latestEP.docs.map(doc => (
<DashboardItem key={doc.id} doc={doc} ref={this.load} />
))}
)
}
}
const DashboardItem = observer(({ doc }) => {
function loadAudio(){
return console.log("works")}
return (// stuff that requires loadAudio to run first)
})
You can achieve that by using useImperativeHandle hook. Please check this out:
https://reactjs.org/docs/hooks-reference.html#useimperativehandle
Wrap DashItem in forwardRef and implement useImperativeHandle hook like below:
const DashItem = React.forwardRef(({doc}, ref) => {
useImperativeHandle(ref, () => ({
loadAudio: () => {
return console.log("works");
}
}));
...
The error "Function components cannot be given refs" should be self-explanatory: you need to change DashItem to be a class component instead of a functional component.

Passing props to dynamic react component

So I have the below code to load a custom component called foo. Loading of the component works fine, but props arent passing to it like I would prefer
Container component
....
const id= foo
React.createElement(LoadComponent(id, attributes))
...
Custom component
export const LoadComponent = (id, attributes) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
How do I pass attributes prop to the Component in this case? I keep getting render exceptions.
Here is a simplified demo: https://codesandbox.io/s/zrw7x1zrrp
props are being passed to the component as the second parameter of createElement
const id = "foo";
const LoadComponent = props => {
return props.id;
};
ReactDOM.render(
React.createElement(LoadComponent, { id }, null),
document.getElementById("root")
);
React.createElement(component, props, ...children)
https://reactjs.org/docs/react-without-jsx.html
It's a bit confusing what you are trying to do. If you are using JSX you shouldn't need to invoke React.createElement.
If you insist of doing it without JSX though, React.createElement can take 3 parameters as per React's API. So, in your case your code will be React.createElement(LoadComponent, { id, attributes }, null) where the third parameter is the children.
Now, the id and attributes are accessible from within the props object of your custom component. So you have two options:
Destructure the props object:
export const LoadComponent = ({ id, attributes }) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
Use the props object directly:
export const LoadComponent = (props) => {
/*This will load up foo.js*/
const Component = require(`./${props.id}`);
return Component;
};

Missing this in onSubmit event handler with redux-form 6

I am trying to use my componenet props to call an action in submit event handler with redux-form 6.1.1 but in the event handler function I get "this" is undefined. here is what I do:
class ForgetPasswordForm extends Component {
xubmit (values) {
console.log(this);
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={ handleSubmit(this.xubmit) }>
...
);
};
}
I also tried () => handleSubmit(this.xubmit.bind(this)) and this.xubmit.bind(this) as mentioned in React: this is null in event handler but none of them worked out.
Here is more details about my setup:
boilerplate: create-react-app v.0.5
react: v.15.3.2
redux: v.3.6
handleSubmit.bind(this.xubmit) by this way inside the handleSubmit, this points to this.xubmit.
I also suggest you to read how bind works.
Note: I know only JavaScript
Since you didn't provide your handleClick function I can only imagine that you have directly called the function passed in like handleClick (fn) { fn() } and this way you would have access to the context in that fn. Also you should pass a function to event handlers. Do something like this and see if it works:
onSubmit={this.props.handleSubmit.bind(this.props.context, this.xubmit.bind(this))}
You need to send parent component's context to your ForgetPasswordForm and bind handleSubmit to it to have access to that parent component's context and bind this.xubmit to this in order for it not to be undefined.
your custom submit function basically is wrong , just edit your code this way.
class ForgetPasswordForm extends Component {
xubmit = (values) => {
console.log(this);
}
}
an call it this way ,
<form onSubmit={handleSubmit((values) => this.xubmit(values)) }>
you must use class properties and arrow function together
class properties is part of stage-2
class ForgetPasswordForm extends Component {
xubmit = (values) => {
console.log(this);
}
render() {
const { handleSubmit } = this.props;
return <form onSubmit={ handleSubmit(this.xubmit) }>;
};
}
Arrow function bind this to function

Resources