In my parent component I have a function/event that looks like this:
const onClick = e => {
e.preventDefault();
POST('/api', { data: data }).then(
async (response) => {
const json = await response.json()
setData(json.data)
}
)
}
On the parent component, this is initiated by the following:
<button type="button" onClick={onClick}>Click me</button>
However, I also have a child component, where I would like a click event in that to also initiate this function/event and a . How is that done ?
I tried just doing something like:
<ChildComponent onclick={onClick} data={data} setData={setData} />
And then in the child component just doing something like:
<div onClick={() => {props.setData(i); props.onClick;}}>
But that doesn't seem to work.
So any hints to what I'm doing wrong here ?
I am not sure if this will fix your problem, but it seems you are using incorrectly the function prop.
Try this:
<div onClick={(e) => {props.setData(i); props.onClick(e);}}>
You can use functions in onClick in 2 ways:
1 - Assign the prop function itself to the onClick:
<div onClick={props.onClick}>
2 - Assign a callback to the onClick which calls the function with the event:
<div onClick={(e) => props.onClick(e)}>
To summarize: the onClick needs to receive a function, but your problem was you were assigning a callback (this is correct) which was not calling the props.onClick one
[you were doing props.onClick instead of props.onClick()]
You should update the state in the parent, not in the child. I am not sure why you are calling props.setData in the child when you can handle that in the parent onClick function.
Related
I pass 2 values to a child component:
List of objects to display
delete function.
I use a .map() function to display my list of objects(like in the example given in react tutorial page), but the button in that component fires the onClick function, on render(it should not fire on render time). My code looks like this:
module.exports = React.createClass({
render: function(){
var taskNodes = this.props.todoTasks.map(function(todo){
return (
<div>
{todo.task}
<button type="submit" onClick={this.props.removeTaskFunction(todo)}>Submit</button>
</div>
);
}, this);
return (
<div className="todo-task-list">
{taskNodes}
</div>
);
}
});
My question is: why does onClick function fire on render and how to make it not to?
Because you are calling that function instead of passing the function to onClick, change that line to this:
<button type="submit" onClick={() => { this.props.removeTaskFunction(todo) }}>Submit</button>
=> called Arrow Function, which was introduced in ES6, and will be supported on React 0.13.3 or upper.
Instead of calling the function, bind the value to the function:
this.props.removeTaskFunction.bind(this, todo)
MDN ref: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
The Problem lies in how you pass your function
At the moment you are not passing the function but Calling it instead:
<Button onClick={yourFunction()} />
You can Fix this in two ways:
<Button onClick={() => yourFunction(params)} />
Or if you dont have any params:
<Button onClick={yourFunction} />
The value for your onClick attribute should be a function, not a function call.
<button type="submit" onClick={function(){removeTaskFunction(todo)}}>Submit</button>
you need to use an arrow function with onClick in order to prevent immediately invoke.
so if your button looks like this :
<button onClick={yourfunctionname()} />
it must be like this :
<button onClick={() => yourfunctionname(params)} />
JSX is used with ReactJS as it is very similar to HTML and it gives programmers feel of using HTML whereas it ultimately transpiles to a javascript file.
Writing a for-loop and specifying function as
{this.props.removeTaskFunction(todo)} will execute the functions
whenever the loop is triggered .
To stop this behaviour we need to return the function to onClick.
The fat arrow function has a hidden return statement along with the bind
property. Thus it returns the function to OnClick as Javascript can
return functions too !!!!!
Use -
onClick={() => { this.props.removeTaskFunction(todo) }}
which means-
var onClick = function() {
return this.props.removeTaskFunction(todo);
}.bind(this);
For those not using arrow functions but something simpler ... I encountered this when adding parentheses after my signOut function ...
replace this <a onClick={props.signOut()}>Log Out</a>
with this <a onClick={props.signOut}>Log Out</a> ... ! 😆
JSX will evaluate JavaScript expressions in curly braces
In this case, this.props.removeTaskFunction(todo) is invoked and the return value is assigned to onClick
What you have to provide for onClick is a function. To do this, you can wrap the value in an anonymous function.
export const samepleComponent = ({todoTasks, removeTaskFunction}) => {
const taskNodes = todoTasks.map(todo => (
<div>
{todo.task}
<button type="submit" onClick={() => removeTaskFunction(todo)}>Submit</button>
</div>
);
return (
<div className="todo-task-list">
{taskNodes}
</div>
);
}
});
I had similar issue, my code was:
function RadioInput(props) {
return (
<div className="form-check form-check-inline">
<input className="form-check-input" type="radio" name="inlineRadioOptions" id={props.id} onClick={props.onClick} value={props.label}></input>
<label className="form-check-label" htmlFor={props.id}>{props.label}</label>
</div>
);
}
class ScheduleType extends React.Component
{
renderRadioInput(id,label)
{
id = "inlineRadio"+id;
return(
<RadioInput
id = {id}
label = {label}
onClick = {this.props.onClick}
/>
);
}
Where it should be
onClick = {() => this.props.onClick()}
in RenderRadioInput
It fixed the issue for me.
It is possible to achieve this even in more readable way than:
<button onClick={() => somethingHere(param)}/>
const Comp = () => {
const [triggered, setTriggered] = useState(false);
const handleClick = (valueToSet) => () => {
setTriggered(valueToSet);
};
return (
<div>
<button onClick={handleClick(true)}>Trigger</button>
<div>{String(triggered)}</div>
</div>
);
};
That way it won't fire the state setter and won't cause too many re-renders compared to <button onClick={setTriggered(true)}/>
which is okay if you don't have any params to pass to the function.
That's because you are calling the function directly instead of passing the function to onClick
If you have passed down onClick={onClickHandler()} then, the function onClickHandler() will be executed during the time of rendering too, the () instructs to execute the function as soon as it is rendered , which is not desired here , instead we use onClick={onClickHandler} , this will execute the onClickHandler only when the specified event occurs. But if we want to pass down a argument along with the function then we can make use of ES6 arrow function.
For your Case :
<button type="submit" onClick={() => this.props.removeTaskFunction(todo)}>Submit</button>
Bit late here but here is the simple answer.
direct approach will trigger by itself due to JS DOM rendering
onClick={this.props.removeTaskFunction(todo)}
anonymous arrow function approach. it will trigger on click
onClick={()=>this.props.removeTaskFunction(todo)}
You are not passing the function as an argument you are calling it directly that why it launches on the render.
HOW TO FIX IT
there are two ways:
First
<Button onClick={() => {
this.props.removeTaskFunction(todo);
}
}>click</Button>
OR
Just bind it
this.props.removeTaskFunction.bind(this,todo);
Currently I am facing the problem that I want to change a state of a child component in React as soon as a prop is initialized or changed with a certain value. If I solve this with a simple if-query, then of course I get an infinite loop, since the components are then rendered over and over again.
Component (parent):
function App() {
const [activeSlide, setActiveSlide] = useState(0);
function changeSlide(index) {
setActiveSlide(index);
}
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
Component (child):
function Button(props) {
const Icon = Icons[props.icon];
const [activeClass, setActiveClass] = useState("");
// This attempts an endless loop
if(props.active == props.index) {
setActiveClass("active");
}
function toggleView(e) {
e.preventDefault();
props.handler(props.index);
}
return(
<button className={activeClass} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
}
Is there a sensible and simple approach here? My idea would be to write the if-query into the return() and thus generate two different outputs, even though I would actually like to avoid this
The React docs have a nice checklist here used to determine if something does or does not belong in state. Here is the list:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
The active class does not meet that criteria and should instead be computed when needed instead of put in state.
return(
<button className={props.active == props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
This is a great use of useEffect.
instead of the if statement you can replace that with;
const {active, index} = props
useEffect(_ => {
if(active == index) {
setActiveClass("active");
}
}, [active])
The last item in the function is a dependency, so useEffect will only run if the active prop has changed.
React automatically re-renders a component when there is a change in the state or props. If you're just using activeClass to manage the className, you can move the condition in the className as like this and get rid of the state.
<button className={props.active === props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
however, if you still want to use state in the child component, you can use the useEffect hook to to update the state in the child component.
Try to use the hook useEffect to prevent the infinite loop. (https://fr.reactjs.org/docs/hooks-effect.html)
Or useCallback hook. (https://fr.reactjs.org/docs/hooks-reference.html#usecallback)
Try this and tell me if it's right for you :
function App() {
const [activeSlide, setActiveSlide] = useState(0);
const changeSlide = useCallback(() => {
setActiveSlide(index);
}, [index]);
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
trying to update state variable('visible') via internal function(setVisible) in component. I checked the tutorıal and did same but its not updating after initialization of state.
Sandobx link here.
props.visible is true when user click ShowModal button. but value of visible in function component is still false. (I have checked the content on debugger)
code:
import Modal from '../Helpers/AppModal'
class Streams extends Component {
constructor(props) {
super(props)
this.state = { showModal: false }
}
componentDidMount() {
this.props.getStreams()
}
showDeleteModal = (isShow) =>
{
this.setState({ showModal: isShow });
}
onClickBackdrop = () => {this.setState({ showModal: false });}
render() {
return (
<div>
<button onClick={()=> this.showDeleteModal(true)} className="btn btn-danger">Delete</button>
<Modal visible={this.state.showModal} onClickBackdrop={this.onClickBackdrop} />
</div>
)
}
}
AppModal.js:
const AppModal = (props) => {
const [visible, setVisible] = useState(props.visible)
useEffect(() =>{
setVisible(props.visible)
},[props.visible])
debugger
return (
<Modal visible={visible} fade={true} onClickBackdrop={props.onClickBackdrop}>
<div className="modal-header">
<h5 className="modal-title">{props.title}</h5>
</div>
<div className="modal-body">
{props.body}
</div>
<div className="modal-footer">
<React.Fragment>
<button type="button" className="btn btn-default" onClick={()=>setVisible(false)}>
Close
</button>
</React.Fragment>
</div>
</Modal>
)
}
The argument passed to useStateis just the initial state. Pass a prop to it doesn't mean that the state will be synchronized with props. You can setup an effect to mirror those changes into your local state.
Currently your Modal only see visible from the local state, changing the props value won't cause Modal to change
//Inside child
useEffect(() =>{
setVisible(props.visible)
},[props])
Why should I use props instead of props.visible there?
The dependencies array exists to keep synchronicity, you're telling react:
"Hey, everytime one of those values changes re run this effect."
The problem is that React performs a shallow comparison (Object.is) between old and new props, uppon each render a new props object is generated which is what is triggering your effect in the first place.
React doesn't know how to "react" to nested changes. What is really changing here is props, react doesn't know (and doesn't care) about props.visible, passing it as a dependency is the same as passing []
Actually passing props as dependency is useless, since props changes every render you can omit the dependencies array, which will trigger the effect on each render
useEffect(() => {
setVisible(props.visible)
})
visible is a boolean.
Try changing the way you call setVisible like so:
setVisible(false)
instead of
setVisible({visible:false})
If this is a toggle switch then you should do this:
onClick={() => setVisible(!visible)}
Then it'll toggle on/off correctly.
You might want to set the initial value more explicitly though:
const [visible, setVisible] = useState(false);
I have a few similar elements:
<div ref={this.ref1} onClick={this.handleClick} className="icon-wrapper"></div>
<div ref={this.ref2} onClick={this.handleClick} className="icon-wrapper"></div>
<div ref={this.ref3} onClick={this.handleClick} className="icon-wrapper"></div>
In my constructor, I declare these refs:
constructor(props) {
super(props);
this.ref1 = React.createRef();
this.ref2 = React.createRef();
this.ref3 = React.createRef();
}
I'm trying to pass these refs to their respective handleClick() functions. Something like:
<div ref={this.ref1} onClick={this.handleClick(ref1)} className="icon-wrapper"></div>
or
<div ref={this.ref1} onClick={this.handleClick(this.ref)} className="icon-wrapper"></div>
And I'm trying to handle the ref in the handleClick function like this:
handleClick = (ref) => {
console.log(ref);
}
And just as a ridiculously wild guess, I tried:
handleClick = () => {
console.log(this.ref.current);
}
That all seems dead wrong because onClick={this.handleClick(ref)} would invoke the function immediately, which is not what is wanted from a click function.
How do you pass different refs to the same function so that the function knows who called it?
The main issue here is you are invoking the function immediately. Thats what applying () does to the end of a function. Instead you need to pass a callback function that needs to be invoked by the event. This is done by passing the function reference.
You can use bind here if you need to pass a value. .bind will return a new function to be invoked. null as the first argument is for the this context, null wont change its context assuming its already been bound to your class. If you need to bind this method you can just pass this through instead of null.
I would recommend passing something to map though instead of the whole ref, like so
<div ref={this.ref1} onClick={this.handleClick.bind(null, 'ref1')} className="icon-wrapper"></div>
then you would access like so
handleClick = (ref, event) => {
if (!this[ref]) return
console.log(this[ref].current);
}
Most use cases for passing a parameter to your function would be because you need a specific key. In this case it looks like you just want to access the element itself. You can get that from the event itself.
<div onClick={this.handleClick} className="icon-wrapper"></div>
handleClick = (event) => {
console.log(event.target);
}
The answer to your question is to use a lambda method,
<div ref={this.ref1} onClick={() => this.handleClick(this.ref1)} className="icon-wrapper"></div>
<div ref={this.ref2} onClick={() => this.handleClick(this.ref2)} className="icon-wrapper"></div>
<div ref={this.ref3} onClick={() => this.handleClick(this.ref3)} className="icon-wrapper"></div>
But if what you want to do is to access the target that fired the event, you can use
handleClick = (event) => {
console.log(event.target);
}
<div onClick={this.handleClick} className="icon-wrapper"></div>
Edit: Here are some alternatives with better (marginal) performance.
handleClick1 = () => this.handleClick(this.ref1)
handleClick1 = this.handleClick.bind(this, this.ref1)
render() {
return (<div ref={this.ref1} onClick={this.handleClick1} className="icon-wrapper"></div>)
}
I found the following code in this post.(sandbox) I am not sure why these buttons behave differently.
One possible explanation is: Because Button's render method is not invoked for updating, the first button's click handler remains the same. However, this.props of the Button instance has changed to include the new onClick prop. In other words,if the props of a component element is changed, nextProps will finally become this.props even when shouldComponentUpdate returns false.
const submit = val => alert(val);
class App extends React.Component {
state = { val: "one" }
componentDidMount() {
this.setState({ val: "two" })
}
render() {
return <Form value={this.state.val} />
}
}
const Form = props => (
<Button
onClick={() => {
submit(props.value)
}}
/>
)
class Button extends React.Component {
shouldComponentUpdate() {
// lets pretend like we compared everything but functions
return false
}
handleClick = () => this.props.onClick()
render() {
return (
<div>
<button onClick={this.props.onClick}>This one is stale</button>
<button onClick={() => this.props.onClick()}>This one works</button>
<button onClick={this.handleClick}>This one works too</button>
</div>
)
}
}
Your explanation is correct on that when ShouldComponentUpdate() returns false, the Button component does not re-render on props change.
In the first <button> element, the onClick event is this.props.OnClick which is actually the function () => { submit("one"); } at the moment of the initial rendering. "one" is here because at this particular time point in the React lifecycle, Form's props.value evaluates to "one". Note that the function gets executed has nothing to do with Button's props.
In the second <button> element, however, the onClick event is () => this.props.onClick(). Regardless whether the Button component gets re-rendered, it is always the this.props.onClick() that get executed, where this.props changes when the parent components get state/props updates.
In the case of ShouldComponentUpdate() returning true, Button component will re-render on props updates. The first button's onClick event becomes a new function () => { submit("two"); } on the re-render triggered by the App component's state change. The two button elements' onClick event functions are never the same function, although they appear to produce the same result.
<button onClick={this.props.onClick}> Will call your onClick function and send an event object as its first parameter. This is the equivalent of doing this :
<button onClick={event => { this.props.onClick(event) }}>
<button onClick={() => this.props.onClick()}> Will set what your onClick function returns as the onClick function... It is the short syntax of the following code :
<button onClick={() => { return this.props.onClick() }}>
What you may be looking for is this : <button onClick={() => { this.props.onClick() }}>
In this case, clicking will simply call your function without sending any arguments
I suggest reading this documentation to know more about arrow/anonymous functions and their syntax