Parent Class Component:
toggleFunction = (event, ...otherProps) => {
console.log(event) // EVENT GOT UNDEFINED
console.log(otherProps) // THIS IS DISPLAYING WHAT IS NEED!
}
<Child updateFunction={(event) => this.toggleFunction(event,"PROPS-1","PROPS-2")}>
Child Functional Component:
const Child = ({ updateFunction }) => {
... OTHER CODE HERE
<div onClick={() => updateFunction()}>Toggle</div>
}
My Problem is when i create event inside parent callback and pass to child as a method and execute parent toggleFunction in Parent Class. ONLY THE EVENT GOT UNDEFINED, OTHER PROPS IS DISPLAYING CORRECTLY
I found a solution by creating event inside child component and accessing inside Parent component, but that doesn't work for me as expected! Can anyone explain at what am i failing.
Thanks in advance!
You need to pass the event from onClick to updateFunction:
<div onClick={event => updateFunction(event)}>Toggle</div>
Or let onClick call it for you since the first parameter is the event:
<div onClick={updateFunction}>Toggle</div>
Related
I am trying to render multiple buttons in a parent component that manages all child states centrally. This means that the parent stores e.g. the click state, the disabled state for each button in his own state using useState and passes it as props to the childs. Additionally the onClick function is also defined inside of the parent component and is passed down to each child. At the moment I am doing this like the following:
const [isSelected, setIsSelected] = useState(Array(49).fill(false));
...
const onClick = useCallback((index) => {
const newIsSelected = [...prev];
newIsSelected[i] = !newIsSelected[i];
return newIsSelected;
}, []);
...
(In the render function:)
return isSelected.map((isFieldSelected, key) => {
<React.Fragment key={key}>
<TheChildComponent
isSelected={isFieldSelected}
onClick={onClick}
/>
</React.Fragment/>
})
To try to prevent the child component from rerendering I am using...
... useCallback to make react see that the onClick function always stays the same
... React.Fragment to make react find a component again because otherwise a child would not have a unique id or sth similar
The child component is exported as:
export default React.memo(TheChildComponent, compareEquality) with
const compareEquality = (prev, next) => {
console.log(prev, next);
return prev.isSelected === next.isSelected;
}
Somehow the log line in compareEquality is never executed and therefore I know that compareEquality is never executed. I don't know why this is happening either.
I have checked all blogs, previous Stackoverflow questions etc. but could not yet find a way to prevent the child components from being rerendered every time that at least one component executes the onClick function and by doing that updated the isSelected state.
I would be very happy if someone could point me in the right direction or explain where my problem is coming from.
Thanks in advance!
This code will actually generate a new onClick function every render, because useCallback isn't given a deps array:
const onClick = useCallback((index) => {
const newIsSelected = [...prev];
newIsSelected[i] = !newIsSelected[i];
return newIsSelected;
});
The following should only create one onClick function and re-use it throughout all renders:
const onClick = useCallback((index) => {
const newIsSelected = [...prev];
newIsSelected[i] = !newIsSelected[i];
return newIsSelected;
}, []);
Combined with vanilla React.memo, this should then prevent the children from re-rendering except when isSelected changes. (Your second argument to React.memo should have also fixed this -- I'm not sure why that didn't work.)
As a side note, you can simplify this code:
<React.Fragment key={key}>
<TheChildComponent
isSelected={isFieldSelected}
onClick={onClick}
/>
</React.Fragment/>
to the following:
<TheChildComponent key={key}
isSelected={isFieldSelected}
onClick={onClick}
/>
(assuming you indeed only need a single component in the body of the map).
Turns out the only problem was neither useCallback, useMemo or anything similar.
In the render function of the parent component I did not directly use
return isSelected.map(...)
I included that part from a seperate, very simple component like this:
const Fields = () => {
return isSelected.map((isFieldSelected, i) => (
<TheChildComponent
key={i}
isSelected={isFieldSelected}
onClick={onClick}
/>
));
};
That is where my problem was. When moving the code from the seperate component Fields into the return statement of the parent component the rerendering error vanished.
Still, thanks for the help.
I have a functional component and I have created a button inside it. I am also using a "Use_effect()" hook. My main is to re-render the functional component, update the use_effect() hook when the button is clicked.
const Emp_list = (props) => {
useEffect(() => {
props.getList(props.state.emp);
}, []);
return (
<div>
{props.state.emp.map((val ) =>
{val.feature_code}
{val.group_code}
<button onClick = {() => props.removeEmpFromList(val.feature_code)} > Remove </button>
<EmpForm empList={props.state.emp}
onChangeText = {props.onChangeText}
/>
</div>
<button onClick= {() => props.getdata (props.state)}>Get Names</button>
<p>
</div>
);
};
export default Emp_list;
removeEmpFromList = (i) => {
const remaining = this.state.emp( c => c.feature_code !== i)
this.setState({
emp: [...remaining]
})
}
When I click the Remove button , it will basically remove the employee from the list. The function removeEmpFromList will update the state.
The functional component EmpForm basically shows the list of all employees.
So I want to re-render the page so that, it updates the state value in useEffect() hook. So when EmpForm is called again on re-rending it shows the updated list.
You didn't provide the code for removeEmpFromList() ... but probably it updates the state by mutation therefor component gets the same object ref - compared shallowly - no difference, no reason to rerender.
Modify removeEmpFromList() method to create a new object for emp - f.e. using .filter.
If not above then passing entire state is the source of problem (the same reason as above).
Simply pass only emp as prop or use functions in setState() (to return a new object for the entire state) this way.
I figured it out! Thanks for the help guys.
So, it was not re-rendering because initally, useEffect() second parameter was [] , if you change it to props.state then it will update the changes made to the state and re-render the component automatically.
useEffect(() => {
props.getList(props.state.emp);
}, [props.state.emp]);
I've imported a custom component into my screen and rendered it in the render() function. Then, created a ref to that custom component. Now, the render() function simply looks like this.
render() {
return (
<View>
<MyComponent ref={component => this.myComponent = component} />
</View>
)
}
Then, I've created another function to access the state of my custom component. I wrote it like this.
myFunction = (ref) => {
ref.setState({ myState: myValue })
}
Then, I called that function like this.
this.myFunction(this.myComponent)
But, it does not work. It gives me the following error.
null is not an object (evaluating 'ref.setState')
Actually what I need this myFunction to do is,
this.myComponent.setState({ myState: myValue })
Can you please help me to solve this problem?
ref is not your this object. it's dom for your componnet. For setState you need this of your component.
you can pass this as argument.
myFunction(this)
Now you will be able to do ref.setState in myFunction.
function myFunction(ref) {
ref.setState({ myState: myValue })
}
To use setState, just use your component's context (this keyword). The context also have your ref in it, so you don't need to pass it as an argument if you are inside one component(not forwarding down to children)
myFunction = (event) => {
this.myComponent // -> points to your ref, DOM element
this.setState() // use your setState like that
}
Don't forget to bind your context in parent component if you want to pass the handler to the child components. Refer to this useful topic
EDIT: Based on your comment, I guess you want to update the parent state by calling a handler in some other component. To do that, you need to create a handler in your parent component, bind the context and pass it as a property to the child component. Next up, you need to assign this handler in your child component. You cannot pass a context with setState method via argument or ref, this is just not how it works in javascript and in react.
Example:
// ParentComponent.js
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 1,
};
this.onChangeHandler = this.onChangeHandler.bind(this);
}
onChangeHandler(event) {
this.setState({
value: someNewValue // will update state on parent component
})
}
render() {
return (
<View>
<SomeComponent>{this.state.value}</SomeComponent>
<ChildrenComponent onChangeHandler={this.onChangeHandler} />
</View>
);
}
}
// ChildrenComponent.js
const ChildrenComponent = (props) => (
<View>
<Button
onPress={props.onChangeHandler}
title="click me to change parent state"
/>
</View>
);
Hopefully, this is what you need :)
I have a parent and a child component. I need to access the parent's HTMLelement in the child component.
I have a working but unclean solution involving
this.setState({ domNode: ReactDOM.findDOMNode(this) }); in the parent's componentDidMount which is just wrong on many levels.
How can i do this using createRef() in the parent to pass its ref as a prop to the child and then how do i get the DOM node with type HTMLElement from the ref?
The best solution that doesn't involve any hack would be to pass a function from parent to child that return the ref of the element to be access
Parent:
constructor(props) {
super(props);
this.domElem = React.createRef();
}
getElem = () => {
return this.domElem;
}
render() {
return (
<div>
<div id="elem" ref={this.domElem}>Test Elem</div>
<Child getParentElem={this.getElem}/>
</div>
)
}
Child:
getParentRef = () => {
const elem = this.props.getParentElem();
}
The solution of #Shubham Khatri is proper to be mentioned. The small correction I want to show, there is no need to invoke a callback function getElem() from child to parent to receive the parent's ref. It can be passed directly the ref to child <Child getParentElem={this.domElem}/>
I see few user complaining to receive {current: null} at child. Please check if the components UI is delivered by a framework, like e.g. Semantic-UI. Probably there could be other methods to hook a ref from a component. In Semantic-UI have to be wrapped the parent component in <Ref innerRef={ ... }> ... </Ref>
As the title of the question, this context is not available in the functional component. So if I have to emit an event, how can I do that?
For example in below code snippet:
<template functional>
<div>
<some-child #change="$emit('change')"></some-child>
</div>
</template>
My functional component doesn't have this context and hence $emit is not available. How can I bubble-up this event?
Child Component
<template functional>
<button #click="listeners['custom-event']('message from child')">
Button from child
</button>
</template>
Parent Component
<template>
<div>
<child-component #custom-event="call_a_method" />
</div>
</template>
See it in action on codesandbox
Do you want to emit the event from the vue instance?
export default {
functional: true,
render(createElement, { listeners }) {
return createElement(
"button",
{
on: {
click: event => {
const emit_event = listeners.event_from_child;
emit_event("Hello World!Is this the message we excpected? :/");
}
}
},
"Pass event to parent"
);
}
};
See it also a sandbox example here
This is explained in the docs Passing Attributes and Events to Child Elements/Components:
If you are using template-based functional components, you will also have to manually add attributes and listeners. Since we have access to the individual context contents, we can use data.attrs to pass along any HTML attributes and listeners (the alias for data.on) to pass along any event listeners.
At the most basic level, you can delegate all listeners like this:
<some-child v-on="listeners"></some-child>
If you only want to bind the change listener, you can do:
<some-child #change="listeners.change"></some-child>
but this will fail if listeners.change is undefined/null (not provided to the functional component).
If you need to handle the situation where there is no change listener, then you can do this:
<some-child #change="listeners.change && listeners.change($event)"></some-child>
otherwise you would have to settle by writing the render function by hand, since I don't think it is possible to conditionally assign the change listener to <some-child> in the template of a functional component. (Or maybe you can? I'm not sure.)
If you want to pass event listener conditionally you can do it inside functional component template like this:
v-on="listeners.change ? { change: listeners.change } : null"
The issue of conditionally attaching listeners is discussed here
a component with jsx:
export default {
name: "MyText",
functional: true,// functional component
props: {
value: {
type: [String, Number],
default: ""
}
},
render(h, context) {
const { props } = context;
// with jsx
// return (
// <button
// onClick={() => {
// console.log(context.listeners);
// context.listeners.input(Math.random().toString(36));
// context.listeners["my-change"](Math.random().toString(36));
// context.data.on.change(Math.random().toString(36));
// }}
// >
// {props.value}
// </button>
// );
// or use h function
return h(
"h1",
{
on: {
// emit some event when click h1
click: () => {
// has value prop has has input event auto
// event name come what event u listen in parent component
console.log(context.listeners);
context.listeners.input(Math.random().toString(36));
context.listeners["my-change"](Math.random().toString(36));
context.data.on.change(Math.random().toString(36));
}
}
},
props.value
);
}
};
conext.listeners is just an alias for context.data.on.
in parent componet, you should listen my-change and change, or has error.
event name inside component comes what event u listen in parent component
<MyText
v-model="value"
#change="change"
#my-change="myChange"
#u-change="uChange"
/>
vue 2.6.11 works well.
see the codesandbox online
Parent:
<Child #onFunction="handleFunction">
and this is the child component:
Child
<template functional>
<div>
<some-child #change="execute"></some-child>
</div>
</template>
methods:
execute(){
#emit("onFunction")
}