React Functional Custom Button Not Registering A Click - javascript

So here is my functional Button component
export const MyButton = (props) => {
return (
<button className="myButton">{props.text}</button>
)
}
And I used it in another page like
<MyButton text="Update" onClick={this.checkIt} />
Where that checkIt function is just console logging the state of the class I used it in. But for some reason, it isn't registering the click at all. How can I go about solving it? I implemented in a similar way for input field where I used the props.onChange(event.target.value) and that is working properly.

You should attach events in child components, otherwise it's not bind to actual UI at all!
Solution 1:
export const MyButton = ({text, ...others}) => {
return (
<button className="myButton" {...others}>{text}</button>
)
}
Solution 2:
export const MyButton = ({text, onClick}) => {
return (
<button className="myButton" onClick={onClick}>{text}</button>
)
}

You can't use onClick on MyButton component directly because MyButton component isn't a Native DOM element it is just another REACT component and onClick is treated as another property MyButton Component. What you need to do is, inside of MyButton component pass the onClick component to DOM element Button
export const MyButton = (props) => {
return (
<button className="myButton" onClick={props.onClick}>{props.text}</button>
)
}

Related

Calling function defined within a react function component on a click event form another function component - React.js

I am constructing some node objects in a function(prepareNodes) to pass to React Flow within a functional component A (lets say), and I have defined a custom node component(CardNode) stateless, which has a button. On button click it should trigger the function(prepareNodes) defined within Component A.
function ComponentA = ({ selectedNodes }) => {
const reactFlowWrapper = useRef(null);
const [elements, setElements] = useState([]);
const [edges, setEdges] = useState([]);
const prepareNode = async (nodeid) => {
//some service calls to fetch data and constuct nodes
setElements([ ...nodes]);
setEdges([...edges]);
}
return (
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={elements}
edges={edges}
//some properties
>
</ReactFlow>
</div>
</ReactFlowProvider>
)
};
export default ComponentA;
function CardNode({ data }) {
const renderSubFlowNodes = (id) => {
console.log(id);
//prepareNode(id)
}
return (
<>
<Handle type="target" position={Position.Top} />
<div className="flex node-wrapper">
<button className="btn-transparent btn-toggle-node" href="#" onClick={() => renderSubFlowNodes(data['id']) }>
<div>
<img src={Icon}/>
</div>
</button>
</div>
<Handle type="source" position={Position.Bottom}/>
</>
);
}
export default CardNode;
I looked for some references online, and most of them suggest to move this resuable function out of the component, but since this function carries a state that it directly sets to the ReactFlow using useState hook, I dont think it would be much of a help.
Other references talks about using useCallback or useRefs and forwardRef, useImperativeHandle especially for functional component, Which I did not quite understand well.
Can someone suggest me a solution or a work around for this specific use-case of mine.
You can add an onClick handler to the each node, and within the node view you call this handler on click.
In the parent Component within the onClick handler you can call prepareNode as needed.
useEffect(() => {
setElements(
elements.map(item => {
...item,
onClick: (i) => {
console.log(i);
prepareNode();
},
})
)},
[]);
The classical approach is to have a parent object that defines prepareNode (along with the state items it uses) and pass the required pieces as props into the components that use them.
That "parent object" could be a common-ancestor component, or a Context (if the chain from the parent to the children makes it cumbersome to pass the props all the way down it).

function declared with const uses the old value of useState

I have a React component (functional) that contains a child component modifying the state of the parent component. I am using the hook useState for this.
After the state change, there is a "Next" button in the parent component that executes a function referencing the updated state. The problem is this next function uses the old state from before the state was modified by the child component.
I can't use useEffect here as the function needs to execute on the click of the "Next" button and not immediately after the state change. I did some digging about JavaScript closures, but none of the answers address my specific case.
Here's the code
const ParentComponent = () => {
const [myState, setMyState] = useState(0);
const handleNext = () => {
console.log(myState); // prints 0 which is the old value
}
return (
<ChildComponent modifyState = {setMyState} />
<Button onClick={handleNext} > Next </Button>
)
}
export default ParentComponent;
BTW there are no errors.
It's a little difficult to understand without your ChildComponent code. setMyState suggests that you need to update the increase the state by one when you click the next button, but you can't do that without also passing in the state itself.
An easier (imo) solution is to pass a handler down to the child component that is called when the next button is clicked. The handler then sets the state.
const {useState} = React;
function ChildComponent({ handleUpdate }) {
function handleClick() {
handleUpdate();
}
return <button onClick={handleClick}>Click</button>
}
function Example() {
const [myState, setMyState] = useState(0);
function handleUpdate() {
setMyState(myState + 1);
}
function handleNext() {
console.log(myState);
}
return (
<div>
<ChildComponent handleUpdate={handleUpdate} />
<button onClick={handleNext}>Next </button>
</div>
)
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
try to modify like this
<ChildComponent modifyState={(value) => setMyState(value)} />

Enzyme - Testing onClick of child component

I very new to testing and I am currently testing React components with jest/enzyme.
I have a parent component
ParentComp.jsx
export class ParentComp extends React.Component {
super(props);
this.state = {
selectedTemplate: "",
disabled: true
};
return <Modal
header={<h2>Header</h2>}
visible={true}
footer={<span>
<Button
disabled={false}
onClick={ () => {
sessionStorage.setItem('id', this.state.id);
}}
>
Continue
</Button>
}>
<h1> My modal </h1>
</Modal>
}
How would I go about testing the onClick and making sure the sessionStorage is tested?
I've already tried:
ParentComp.spec.jsx
const wrapper = shallow(<ParentComp/>);
wrapper.find(Modal).first().props().footer.find(Button).simulate('click')
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
expect(global.sessionStorage.getItem).toBecalledWith('id',1)
}
I thankfully don't get any actual errors, however, my sessionStorage line in ParentComp is apparently not being covered. How would I go about covering this line?
I have an idea to call directly prop onClick directly in this case instead of simulating of click. As long as you can find your <Button /> in your footer then call its prop of onClick. Let's try:
// You can console.log to see what is the correct to select the right one
// I'm not sure below route is correct :)
const yourButton = wrapper.find(Modal).first().props().footer.props.children;
yourButton.props.onClick();

in React, how to call a function living in another component?

in my react's App.js's return i am currently calling this.searchVenues() in a way that works but is messy and i know there is a better way. The searchVenues() function lives in App.js and I have buttons that need to be in their own component, then just <ButtonComponent/> instead of:
render() {
return (
<div className="App container-fluid">
<Navbar/>
<div className="row">
<div className="col-xs-3">
<button onClick ={() => this.searchVenues("yoga+coffee", "5")}>5</button>
<button onClick ={() => this.searchVenues("yoga+coffee", "10")}>10</button>
<button onClick ={() => this.searchVenues("yoga+coffee", "15")}>15</button>
<SideBar {...this.state} handleListItemClick={this.handleListItemClick}/>
</div>
<div className="col-md-9 full-height">
<Map {...this.state}
handleMarkerClick={this.handleMarkerClick}/>
</div>
</div>
<Footer/>
</div>
);
}
but when i do this.searchVenues("yoga+coffee", "5") does not work, understandably so. What's the best or a better way to make it work? How do i access the function from another file ( component )?
I believe you want to declare your searchVenues func in App.js component like this:
searchVenues = (arg1, arg2) => {here is the body of your function}
... and pass it down to the ButtonComponent using props:
<ButtonComponent searchVenues={this.searchVenues}
Once you are in your stateless ButtonComponent, you can create a new function inside it if you want to avoid anonymous functions in your render (you can read on it here)
const searchVenues = (arg1, arg2) => {
return event => {props.searchVenues(arg1, arg2);}
}
... and add it to the onClick event:
<button onClick ={searchVenues('coffee+yoga', props.value)}>{props.value}</button>
If you want your buttons to live in another component, but receive handlers from another component, you can pass them down through props.
import React, { Component } from 'react'
import MyButton from './MyButton'
export default class App extends Component {
buttonClickHandler= () => {
alert('I have been clicked!')
}
render = () => (
<MyButton onClickHandler={this.buttonClickHandler} />
)
}
And here is the MyButton component file:
import React from 'react'
const MyButton = (props) => (
<button onClick={props.onClickHandler}>Click Me for an Alert!</button>
)
export default MyButton
You could either have searchVenues defined in your ButtonComponent or pass it to ButtonComponent as a prop. Then in your ButtonComponent render function you would do the same thing:
<button onClick ={() => this.searchVenues("yoga+coffee", "15")}>15</button>
or if it is passed as a prop
<button onClick ={() => this.props.searchVenues("yoga+coffee", "15")}>15</button>
I will also add that #Bryan is absolutely right about services. Lets say for instance you have a table in a database called Product. Well, you don't want to implement getAllProducts() in every component that needs a list of products. Instead, you would create a class called ProductService where getAllProducts() would be defined. Then, for any component that needs a list of products, you would import the ProductService class and call ProductService.getAllProducts().
Hope that helps.
If you want to execute methods from any component, and the result of those methods will change the global state of the app, you might benefit from using actions.
Whether you're using flux or redux architecture, actions can be triggered from any part of the app, and perform changes in the global state, this changes will be reflected on any component that is listening to this state.
https://reactjs.org/blog/2014/07/30/flux-actions-and-the-dispatcher.html
https://redux.js.org/basics/actions

React - passing ref to parent

I am trying to think the react way but I can't find a solution on how to invoke the .submit() method of the form component.
I have a material-ui Dialog where I have to pass the buttons via actions property. From this action component, I would like to invoke the .submit() method of the Form component, which is a child of the dialog.
Do I have to pass the formRef up to the Dialog to pass it then to the Actions, and how would I do that? Or is there any React way I am missing out on?
class FormDialog extends React.Component {
render() {
return (
<Dialog actions={<Actions />} >
<Form />
</Dialog>
)
}
}
const Actions = (props) => {
return (
<FlatButton
label="Submit"
onTouchTap={() => formRef.submit()}
/>
)
}
const Form = () => {
let formRef;
return (
<AutoForm
ref={ref => formRef = ref}
onSubmit={doc => db.save(doc)}
>
</AutoForm>
)
}
Any buttons inside a form that submit the form should be type="submit", and clicking on any of them will trigger the <form />'s onSubmit handler. There's no need to pass around a reference.
There's a few React-way notes here:
If you have to pass things "up" and then back "down" a component tree, you're probably not approaching the problem correctly.
Components should never call methods on other components.
Unless you really know when they're needed, refs to DOM elements should only be referenced inside the component owning the ref.

Categories