Calling React function from stencil web component - reactjs

This is my react functional component
const goTo = () => {
console.log("This method call.");
};
return (
<div>
<div className="container">
<otp-web-component
callBack={() => goTo}>
</otp-web-component>
</div>
</div>
)
This is stencil component
#Component({
tag: "otp-web-component",
styleUrl: "my-component.css",
})
export class otpcomponent {
#Method() CallBack:()=>void; //calling this on button click
#Listen("event.keydown.enter")
goBack() {
// calling callback function here
}
render(){
return(
<button
class="btn btn-primary"
style={buttonStyle}
onClick={this.goBack.bind(this)}
>
get
</button>
)
}
When clicking on get button in stencil component it should execute the react function goTo();

Just listing some things that look wrong and might help you figure out what the problem is:
You're using the #Method decorator for CallBack but you're not defining a method. You probably want to use the #Prop decorator. It's possible to pass a function (or any JS object) as a prop. You can read about the method decorator here: https://stenciljs.com/docs/methods.
When using your component in React, you're not actually calling the goTo function. You're passing a function that returns the goTo function instead because you forgot the (). You either want to use callBack={goTo} or callBack={() => goTo()}.
You've defined your prop (the one with the #Method decorator) as CallBack (pascal case) but then you're using it as callBack (camel case). Stencil component property names are not case insensitive.
#Listen("event.keydown.enter") this is not how you bind an event listener to a keyboard event. See https://stenciljs.com/docs/events#keyboard-events.
So, I think your component should look sth like this (simplified):
#Component({ tag: 'otp-web-component' })
export class OtpWebComponent {
#Prop() callback: () => void;
goBack = () => {
// ...
this.callback();
}
render() {
return <button onClick={this.goBack}>Click me</button>;
}
}
and then you can use it like
export default () => {
const goTo = () => console.log('I got called');
return <otp-web-component callback={goTo} />;
}

Related

Use state from a Context inside Class component

here is my Codepen which illustrates my current problem:
I woud like to use the class component, so I can call the forward function from parentComponents (through ref), but I currently cant figure out how to manipulate the context (Where the current state of the application is stored.
Can somebody help me ?
https://codesandbox.io/s/gallant-dust-vtp46?file=/src/App.tsx:0-1918
Kind regards
I don't know the exactly answer, but I have a solution, that you can forward function to parent component with Functional component. Check this code:
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
const ChildComponent(props, ref) => {
useImperativeHandle(ref, () => {
const funcCallFromParent = (params) => {
funcToHandle(params);
};
return { funcCallFromParent };
});
const doSomething(params) {
...
}
...
}
and then call if from your ParentComponent
...
childRef.current.funcCallFromParent(params);
...
This way will help you use Functional Component instead of Class Component, therefore easier to access the context.
Additional, maybe you'll want to try Redux, it's a good and most popular Context Management for ReactJS.
To consume React Context with class-based component you wrap them with the Consumer component and inject context value as props.
SStateButton
export class SStateButton extends Component {
refe;
name;
onclick;
constructor({
refe,
name,
onclick
}: {
refe: Status;
name: string;
onclick: any;
}) {
super({ refe, name, onclick });
this.refe = refe;
this.name = name;
this.onclick = onclick;
}
forwardToStatus = () => {
if (this.onclick) {
this.onclick(this.refe);
}
};
render() {
return (
<button
className="btn btn-light btn-outline-dark"
onClick={this.forwardToStatus}
>
ClassComponent {this.name}
</button>
);
}
}
App - Given context value value={[status, setStatus]}
<StateContext.Consumer>
{([, setStatus]) => (
<SStateButton
refe="1 Page"
name="Go to Page 1"
onclick={(val) => {
console.log("Additional functions added");
setStatus(val);
}}
/>
)}
</StateContext.Consumer>
Since SStateButton is being used in the same component that is providing the context value, and has the status state and updater function already in scope, the setStatus callback can also just be enclosed in the onclick callback and work just the same.
<SStateButton
refe="1 Page"
name="Go to Page 1"
onclick={(val) => {
console.log("Additional functions added");
setStatus(val);
}}
/>

React and React Hooks: Using an onClick function in a child to fire a function of a parent wrapping component

I have a wrapper component that conditionally renders it's children based on it's own state (isDeleted). Basically I have a 'delete-able item' component where if a button is clicked to delete, the item itself will be removed from the DOM (by returning an empty ReactNode i.e. <></>). The problem is, I can't figure out how to have the button click event, which appears as a child of the wrapper, to be passed INTO the wrapped component itself:
export default function App() {
return (
<DeleteableItemComponent>
{/* lots of other markup here */}
<button onClick={triggerFunctionInsideDeletableItemComponent}>
</DeleteableItemComponent>
)
}
and the most basic version of my delete-able item component:
export default function DeleteableItemComponent() {
const [isDeleted, setIsDeleted] = useState(false);
const functionIWantToFire = () => {
// call API here to delete the item serverside; when successful, 'delete' on frontend
setIsDeleted(true)
}
if (isDeleted) {
return <></>
}
return <>{children}</>
}
So put very simply, I just want to call the functionIWantToFire from the button onClick callback.
How can this be done properly via hooks? I've thought of using the context API but I've never seen it used to trigger function firing, only for setting values, and in this case I want to fire the event itself, not communicate specific values to the wrapper component. I also can't do it correctly through just passing a boolean prop, because then I can only set it once i.e. from false to true.
You could use React.cloneElement API to pass props to your child while iterating through it using React.children.map.
React.Children.map(children, (child) => {
return React.cloneElement(child, { /* .. props here */ });
});
A simple example would be.
You could check the example here
function App() {
return (
<Delete>
<Child1 />
<Child2 />
</Delete>
);
}
function Delete({ children }) {
const [clicked, setClicked] = React.useState(0);
const inc = () => setClicked(clicked + 1);
const dec = () => setClicked(clicked - 1);
return React.Children.map(children, (child) => {
return React.cloneElement(child, { inc, clicked, dec });
});
}
function Child1(props) {
return (
<div>
<p>{props.clicked}</p>
<button onClick={props.inc}>Inc</button>
</div>
)
}
function Child2(props) {
return (
<div>
<button onClick={props.dec}>Dec</button>
</div>
)
}

Implementing events in a REACT function component

I am trying to figure out how to implement my own custom events. I asked the question here but the word event seems to confuse my question. I was asked to add a new question, so I will try to do my best in another way:
Related post
My component:
import React, { useState } from "react";
const DropdownPaging2 = props => {
function myClickFunc(val) {
alert("WHAT SHOULD I ADD HERE TO FIRE MY EVENT TO THE CONSUMING COMPONENT");
}
return <div onClick={() => myClickFunc(100)}>CLICK me</div>;
};
export default DropdownPaging2;
Using my component in another components (comsuming component) render function:
<DropdownPaging2></DropdownPaging2>
I would like implement so I can pass a new event to the consuming component. Something lige this:
<DropdownPaging2 myCustomEvent={() => myCustomEvent(100)}></DropdownPaging2>
You can pass functions as props to your DropdownPaging2 component like you mentioned:
<DropdownPaging2 myEvent={() => myCustomEvent(100)}></DropdownPaging2>
And then use it in the component like this.
const DropdownPaging2 = props => {
const myClickFunc = (val) => {
if(props.myEvent){
props.myEvent();
} else {
// default if no function is passed
}
}
return <div onClick={() => myClickFunc(100)}>CLICK me</div>;
};
export default DropdownPaging2;
This way you are free to pass a custom function
Just make your component use the custom callback if it was passed as a prob, otherwise use the default one.
return <div onClick={prop.myCustomEvent ? prop.myCustomEvent : () => myClickFunc(100)}>CLICK me</div>;

Reactjs function invoking on page load before onClick event

Function working while the page load my code as follow
Parent
import React, { Component } from "react";
import ExtnButton from "./Button";
class MovieList extends Component {
handleDelete = index => {
console.log("inside handleDelete:");
};
render() {
return (
<React.Fragment>
<ExtnButton handleDelete={this.handleDelete} index={index} />
</React.Fragment>
);
}
}
export default MovieList;
Child
import React, { Component } from "react";
class Button extends Component {
state = {};
render() {
return (
<button
onClick={this.props.handleDelete(this.props.index)}
className="btn btn-danger"
>
Delete
</button>
);
}
}
export default Button;
But on page loading the function handleDelete invoking without any click event
Wrong:
onClick={this.props.handleDelete(this.props.index)}
Correct:
onClick={() => this.props.handleDelete(this.props.index)}
It's because you're calling the method inside the onClick event directly. There are three approaches to bind the events with the parameters:
Using inline arrow function:
onClick={() => this.props.handleDelete(this.props.index)}
Using public class method (as you also have currently), but just need to curry:
handleDelete = index => () => {
console.log("inside handleDelete:");
};
Using bound method:
handleDelete(index) {...}
But for this, you need to bind the this inside the constructor.
this.handleDelete = this.handleDelete.bind(this)
If you need to pass the event:
(using inline arrow function)
onClick={(e) => this.props.handleDelete(this.props.index, e)}
(using public class method)
handleDelete = index => e => {
console.log(e);
};
Notice that if you use inline arrow function, then you don't need to curry the function. This will be just fine:
handleDelete = index => {...}
Or, without using public class method (ie. bound method):
handleDelete(index) {...}

ReactJS: Warning: setState(...): Cannot update during an existing state transition

I am trying to refactor the following code from my render view:
<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange.bind(this,false)} >Retour</Button>
to a version where the bind is within the constructor. The reason for that is that bind in the render view will give me performance issues, especially on low end mobile phones.
I have created the following code, but I am constantly getting the following errors (lots of them). It looks like the app gets in a loop:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
Below is the code I use:
var React = require('react');
var ButtonGroup = require('react-bootstrap/lib/ButtonGroup');
var Button = require('react-bootstrap/lib/Button');
var Form = require('react-bootstrap/lib/Form');
var FormGroup = require('react-bootstrap/lib/FormGroup');
var Well = require('react-bootstrap/lib/Well');
export default class Search extends React.Component {
constructor() {
super();
this.state = {
singleJourney: false
};
this.handleButtonChange = this.handleButtonChange.bind(this);
}
handleButtonChange(value) {
this.setState({
singleJourney: value
});
}
render() {
return (
<Form>
<Well style={wellStyle}>
<FormGroup className="text-center">
<ButtonGroup>
<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange(false)} >Retour</Button>
<Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChange(true)} >Single Journey</Button>
</ButtonGroup>
</FormGroup>
</Well>
</Form>
);
}
}
module.exports = Search;
Looks like you're accidentally calling the handleButtonChange method in your render method, you probably want to do onClick={() => this.handleButtonChange(false)} instead.
If you don't want to create a lambda in the onClick handler, I think you'll need to have two bound methods, one for each parameter.
In the constructor:
this.handleButtonChangeRetour = this.handleButtonChange.bind(this, true);
this.handleButtonChangeSingle = this.handleButtonChange.bind(this, false);
And in the render method:
<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChangeSingle} >Retour</Button>
<Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChangeRetour}>Single Journey</Button>
I am giving a generic example for better understanding, In the following code
render(){
return(
<div>
<h3>Simple Counter</h3>
<Counter
value={this.props.counter}
onIncrement={this.props.increment()} <------ calling the function
onDecrement={this.props.decrement()} <-----------
onIncrementAsync={this.props.incrementAsync()} />
</div>
)
}
When supplying props I am calling the function directly, this wold have a infinite loop execution and would give you that error, Remove the function call everything works normally.
render(){
return(
<div>
<h3>Simple Counter</h3>
<Counter
value={this.props.counter}
onIncrement={this.props.increment} <------ function call removed
onDecrement={this.props.decrement} <-----------
onIncrementAsync={this.props.incrementAsync} />
</div>
)
}
That usually happens when you call
onClick={this.handleButton()} - notice the () instead of:
onClick={this.handleButton} - notice here we are not calling the function when we initialize it
THE PROBLEM is here: onClick={this.handleButtonChange(false)}
When you pass this.handleButtonChange(false) to onClick, you are actually calling the function with value = false and setting onClick to the function's return value, which is undefined. Also, calling this.handleButtonChange(false) then calls this.setState() which triggers a re-render, resulting in an infinite render loop.
THE SOLUTION is to pass the function in a lambda: onClick={() => this.handleButtonChange(false)}. Here you are setting onClick to equal a function that will call handleButtonChange(false) when the button is clicked.
The below example may help:
function handleButtonChange(value){
console.log("State updated!")
}
console.log(handleButtonChange(false))
//output: State updated!
//output: undefined
console.log(() => handleButtonChange(false))
//output: ()=>{handleButtonChange(false);}
If you are trying to add arguments to a handler in recompose, make sure that you're defining your arguments correctly in the handler. It is essentially a curried function, so you want to be sure to require the correct number of arguments. This page has a good example of using arguments with handlers.
Example (from the link):
withHandlers({
handleClick: props => (value1, value2) => event => {
console.log(event)
alert(value1 + ' was clicked!')
props.doSomething(value2)
},
})
for your child HOC and in the parent
class MyComponent extends Component {
static propTypes = {
handleClick: PropTypes.func,
}
render () {
const {handleClick} = this.props
return (
<div onClick={handleClick(value1, value2)} />
)
}
}
this avoids writing an anonymous function out of your handler to patch fix the problem with not supplying enough parameter names on your handler.
The problem is certainly the this binding while rending the button with onClick handler. The solution is to use arrow function while calling action handler while rendering. Like this:
onClick={ () => this.handleButtonChange(false) }
From react docs Passing arguments to event handlers
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
This same warning will be emitted on any state changes done in a render() call.
An example of a tricky to find case:
When rendering a multi-select GUI component based on state data, if state has nothing to display, a call to resetOptions() is considered state change for that component.
The obvious fix is to do resetOptions() in componentDidUpdate() instead of render().
I got the same error when I was calling
this.handleClick = this.handleClick.bind(this);
in my constructor when handleClick didn't exist
(I had erased it and had accidentally left the "this" binding statement in my constructor).
Solution = remove the "this" binding statement.
The onClick function must pass through a function that returns the handleButtonChange() method. Otherwise it will run automatically, ending up with the error/warning. Use the below to solve the issue.
onClick={() => this.handleButtonChange(false)}
The solution that I use to open Popover for components is reactstrap (React Bootstrap 4 components).
class Settings extends Component {
constructor(props) {
super(props);
this.state = {
popoversOpen: [] // array open popovers
}
}
// toggle my popovers
togglePopoverHelp = (selected) => (e) => {
const index = this.state.popoversOpen.indexOf(selected);
if (index < 0) {
this.state.popoversOpen.push(selected);
} else {
this.state.popoversOpen.splice(index, 1);
}
this.setState({ popoversOpen: [...this.state.popoversOpen] });
}
render() {
<div id="settings">
<button id="PopoverTimer" onClick={this.togglePopoverHelp(1)} className="btn btn-outline-danger" type="button">?</button>
<Popover placement="left" isOpen={this.state.popoversOpen.includes(1)} target="PopoverTimer" toggle={this.togglePopoverHelp(1)}>
<PopoverHeader>Header popover</PopoverHeader>
<PopoverBody>Description popover</PopoverBody>
</Popover>
<button id="popoverRefresh" onClick={this.togglePopoverHelp(2)} className="btn btn-outline-danger" type="button">?</button>
<Popover placement="left" isOpen={this.state.popoversOpen.includes(2)} target="popoverRefresh" toggle={this.togglePopoverHelp(2)}>
<PopoverHeader>Header popover 2</PopoverHeader>
<PopoverBody>Description popover2</PopoverBody>
</Popover>
</div>
}
}

Resources