Reactjs - parent, child action and functions - javascript

I am working on a reactjs application - and I am breaking up a big component to have a child component. I've created a callback function in the child that will go back to the parent. When a checkbox is checked -- the child component does the callback and this goes back into the parent shell -- however I want to now jump out of the event callback and push the data to an original parent function.
my application kind of looks like this on a streamlined level.
var Parent = React.createClass({
onSelect: function(value, flag){
this.updateSelected(value, flag);
}
updateSelected: function(value, flag) {
let array = this.state.selectedArray;
array.push({"value": value, "flag": flag});
this.setState({
selectedArray: array
});
},
render: function() {
return (
<div>
<Child onSelect={this.changeHandler} />
<span>{this.state.value}</span>
</div>
);
}
});
var Child = React.createClass({
selectHandler: function(e) {
this.props.onSelect(e.target.value, false);
},
render: function() {
return (
<input type="checkbox" onSelect={this.selectHandler} />
);
}
});
but I can not just invoke the "this.updateSelected(value, flag);" as its inside the this scope.

You should use ES6 syntax and JSX syntax instead of pure React API, that help you a lot to reduce this kind of error about scoping and become your code more readable, but If you even want to use that syntax, so you should bind the function once you pass down to the child component , you can do it like this:
var Parent = React.createClass({
onSelect: function(value, flag){
this.updateSelected(value, flag);
}
updateSelected: function(value, flag) {
let array = this.state.selectedArray;
array.push({"value": value, "flag": flag});
this.setState({
selectedArray: array
});
},
render: function() {
return (
<div>
<Child onSelect={this.changeHandler.bind(this)} />
<span>{this.state.value}</span>
</div>
);
}
});
as you can see adding the .bind method and passing as argument the context that you want your function execute, then once your function be invoked, the scope of that function will be the parent component instead of child component.
Remember that this approach bind method could affect the improve of your component if your Parent component rerenders many times.
If you are using JSX syntax you should do something like
class YourComponent extends React.Component {
changeHandler = (value, flag) => {
this.updateSelected(value, flag);
}
render() {
return (
<div>
...
<Child onSelect={this.changeHandler} />
</div>
);
}
}

Related

React 16: Call children's function from parent when using hooks and functional component

I need to call children's function in their parent component. How should I do it? Previous in React 15, I can use refs to call children's function. But have no idea how to do it with hooks and functional component.
function Child(props) {
function validate() {
// to validate this component
}
return (
<>Some code here</>
)
}
function Parent(props) {
const refs = useRef([]);
const someData = [1,2,3,4,5];
function validateChildren() {
refs.current.forEach(child => {
child.current.validate(); // throw error!! can not get validate function
});
}
return (
<>
<button onClick={validateChildren}>Validate</button>
{
someData.map((data, index) => {
return <Child ref={ins => refs.current[index] = ins} />
})
}
</>
)
}
My actual requirement is:
1. There are multiple tabs which can be added, delete base on user's behaviour
2. Click a button in the parent of tabs to trigger validate
3. All the tabs need to validate itself, show error message in their tabs and return a validate message
You can use useImperativeHandle with forwardRef.
From the docs,
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef
const Child = React.forwardRef((props,ref) => {
//validate will be available to parent component using ref
useImperativeHandle(ref, () => ({
validate() {
// to validate this component
console.log("I'm clicked");
}
}));
return (
<>Some code here</>
)
})
In the parent compoennt you can access child function as,
function validateChildren() {
refs.current.forEach(child => {
child.validate(); // you can access child method here
});
}
Demo

Passing value into callback from Parent Component to Child without Binding

I have a React component built like so, that takes in a callback function from its Parent component. When the onClick fires, it calls the callback with a value from the items being mapped over:
class Child extends Component {
static propTypes = {
...
}
render = () => {
return (
<div>
{this.props.data.map((el, idx) => {
return <section onClick={() = this.props.cb(el.val)}></section>
}
)}
</div>
);
}
}
What I'm wondering is how I can accomplish passing a value from that map to the callback without using this syntax () => this.props.cb(item.val) or binding a value to the callback. I can't just pass the callback to onClick because it fires immediately with the value, either.
The current syntax works but breaks the rules I have setup in my linter.
An alternative to binding in render is breaking out a new component which takes the callback and the value to be passed:
class Section extends React.Component {
handleClick = () => {
this.props.onClick(this.props.val)
}
render() {
return <section onClick={this.handleClick}></section>
}
}
(I would suggest just disabling that lint rule, though)

In a react class: How do I pass a variable inside same component

I'm learning ReactJS and need to pass a variable inside the same component.
Here's an example
var DataBase = [
{
position: 1
},
{
position: 2
},
{
position: 3
},
{
position: 4
}
];
var Component = React.createClass({
getDefaultProps: function() {
var counter = 0;
},
componentDidMount: function() {
var dbPos = this.props.db[counter+1].position;
return dbPos;
},
render: function () {
return (
<div className="Component">
{this.dbPos}
</div>
);
}
});
ReactDOM.render(
<Component db={DataBase} />,
document.getElementById('main')
);
So, this obviously doesn't work. What I need is to pass var dbPos created in componentDidMount to the render (without any events like onClick). This will be time driven, like 10 seconds in each position with setTimeout().
Is this possible? How? Is there a better solution? I'll appreciate all your help.
That problem may regard state handling. There are multiple ways to handle the application's state in a React application, but I will assume that you are interested in keeping dbPos as part of the component's state (and that you may be mutating it in the future). To achieve this, simply use this.setState and this.state.
Before I show the example, I will state a few other mistakes in your code snippet:
getDefaultProps should return a hash object of the props, not declare them with var (that would make them scoped to the method rather than the component instance)
counter, as a prop, must be referred as this.props.counter. Note that counter is not part of this component's state, and can only change with a respective change of that prop in an upper level of the component tree.
With that in mind:
var Component = React.createClass({
getDefaultProps: function() {
return {counter: 0};
},
componentDidMount: function() {
var dbPos = this.props.db[this.props.counter+1].position;
this.setState({ // change the state of this component
dbPos: dbPos
})
},
render: function () {
return (
<div className="Component">
{this.state.dbPos}
</div>
);
}
});
If you do not want dbPos to mutate as part of the component's state, simply make a new method for retrieving the intended position. No mutable state will be involved here.
var Component = React.createClass({
getDefaultProps() {
return {counter: 0};
},
componentDidMount() {
// no longer needed
},
getPosition() {
return this.props.db[this.props.counter + 1].position;
},
render () {
return (
<div className="Component">
{this.getPosition()}
</div>
);
}
});

React.js - Changing state of owner component by an event handler

Is this the correct way to handle the model change event -
a. The handleModelChange function is being passed as onModelChange prop to SubClass.
b. When the model change event triggers, for the re-render to occur, the handler from the SubComponent changes the state of the MainComponent.
var _SomeMixin={
componentWillMount: function() {
this.props.options.model.on("MODEL_CHANGED", this.props.onModelChange);
},
componentWillUnmount: function() {
this.props.options.model.off("MODEL_CHANGED", this.props.onModelChange);
},
/* more mixin functions */
}
var SubComponent = React.createClass({
mixins: [_SomeMixin],
render: function() {
return (
<div>
<!-- ... more elements .. >
</div>
);
}
});
var MainComponent = React.createClass({
getInitialState: function() {
return {MainComponentState: []};
},
handleModelChange: function() {
if (this.isMounted()) {
this.setState({MainClassState: this.props.options.model.toJSON()});
}
},
render: function() {
return (
<SubClass options={this.props.options} onModelChange={this.handleModelChange} />
);
}
});
This is one of possible ways to inform parent component that inner component has been changed. But this approach will lead you to a callbacks hell, that you will have to pass every time.
Better solution is to use some state management library like Moreartyjs

Clone a component that has already been mounted

I am trying to build an app that uses drag-and-drop behaviour, and the component being dragged needs to be cloned elsewhere in the DOM. Since the component is already mounted, trying to mount it again causes the browser to hang.
Trying to use cloneWithProps results in a Cannot read property 'defaultProps' of undefined error.
Here's a testcase:
var TestCase = React.createClass({
getInitialState () {
return {
draggingItem: null
}
},
render () {
return <div>
<ExampleComponent onClick={this.setDraggingItem} />
{this.state.draggingItem}
</div>
},
setDraggingItem (component) {
// This gives `Cannot read property 'defaultProps' of undefined`
//React.addons.cloneWithProps(component)
// This crashes the browser
//this.setState({ draggingItem: component })
}
})
var ExampleComponent = React.createClass({
render () {
return <div onClick={this.handleOnClick}>Hello World</div>
},
handleOnClick (event) {
this.props.onClick(this)
}
})
React.render(<TestCase />, document.body)
Of course I could simply clone component.getDOMNode() in setDraggingItem, but it really seems like rendering the component or calling cloneWithProps should work?
The two things you need to create an element is: the component class (e.g. ExampleComponent) and its props. cloneWithProps is only to be used in render and only with an element coming from props which was created in another component's render. You shouldn't save elements, or pass them around other than to other components in render. Instead, you pass around objects (props) and component classes.
Since you need to know the props and component class to render it in the first place, you can handle all of this in TestCase.
var TestCase = React.createClass({
getInitialState () {
return {
draggingItem: null,
draggingItemProps: null
}
},
render () {
return <div>
<ExampleComponent onClick={this.setDraggingItem.bind(null,
/* the component class */ ExampleComponent,
/* the props to render it with */ null
)} />
{
this.state.draggingItem && React.createElement(
this.state.draggingItem,
this.state.draggingItemProps
)
}
</div>
},
setDraggingItem (component, props, event) {
this.setState({ draggingItem: component, draggingItemProps: props })
}
});
var ExampleComponent = React.createClass({
render () {
return <div onClick={this.handleOnClick}>Hello World</div>
},
// just defer the event
handleOnClick (event) {
this.props.onClick(event)
}
});
If you wish to make these valid outside this TestCase component, ensure there aren't any functions bound to TestCase in the props. Also ensure there's no children prop with react elements in it. If children are relevant, provide the {componentClass,props} structure needed to recreate them.
It's hard to tell what your actual requirements are, but hopefully this is enough to get you started.
You need be sure you're creating a new component with the same props, not mount the same one multiple times. First, setup a function that returns an instantiated components (easier to drop JSX here):
function getComponent(props) {
return ExampleComponent(props);
}
Then in your TestCase render:
return (<div>
{ getComponent({ onClick: this.setDraggingItem }) }
{ this.state.draggingItem }
</div>);
That will create the first component. Then to create a clone:
setDraggingItem(component) {
var clone = getComponent(component.props);
}
This deals with the cloning part. You still have some dragging and rendering to figure out.

Categories