I have seen similar questions to this but they are usually talking about a parent accessing a child component's methods or passing in methods through props. My question is on a specific situation, using props.children, and having any child component be able to call a method on the parent that is rendering props.children.
A simplified example of what im trying to achieve:
class WrapperComponent extends React.Component {
constructor(props){
super(props);
this.handleInputChange = this.handleInputChange.bind(this)
}
handleInputChange(e){
console.log("neat, you changed the input")
}
render() {
return (
<form>
{this.props.children}
</form>
)
}
}
And the component that is calling said component and passing in the children as props.
const Component = (props) => {
return(
<WrapperComponent>
<div className="form-group" >
<label>
<div>Text</div>
<input onChange={this.handleInputChange} type={"text"}/>
</label>
</div>
</WrapperComponent>
)
}
The idea is that I can render a component that holds certain logic, and pass in the elements as children for that component to render, but also that the props.children can then call said logic within the wrapper, so I can pass in different children in different use cases, but the handling will always be the same. Is there a way to do this at all?
You can clone your elements and add new props to them using some built-in React goodies:
class WrapperComponent extends React.Component {
constructor(props){
super(props);
this.handleInputChange = this.handleInputChange.bind(this)
}
handleInputChange(e){
console.log("neat, you changed the input")
}
render() {
return (
<form>
{React.Children.map(
this.props.children,
el => React.cloneElement(el, {onInputChange: this.handleInputChange})
)}
</form>
)
}
}
Then (remove WrapperComponent):
const Component = (props) => {
return(
<div className="form-group" >
<label>
<div>Text</div>
<input onChange={props.onInputChange} type={"text"}/>
</label>
</div>
)
}
Then:
ReactDOM.render(
<WrapperComponent><Component /></WrapperComponent>,
document.getElementById('root')
)
Yes, there's a way, but its not straightforward, and you may want to consider a different approach.
In order for a child component to have access to the methods of an arbitrary parent, the parent must override the child's props. This can be done using React.cloneElement in the render() function of the parent. In your example:
class WrapperComponent extends React.Component {
constructor(props){
super(props);
this.handleInputChange = this.handleInputChange.bind(this)
}
handleInputChange(e){
console.log("neat, you changed the input")
}
render() {
return (
<form>
{React.Children.map(this.props.children, child => (
React.cloneElement(child, {handleInputChange: this.handleInputChange}
)}
</form>
)
}
}
Then, you can access the method in the child via this.props.handleInputChange.
Related
I created the following render props component, through the children prop as function:
export class GenericAbstractLoader extends React.Component<IGenericAbstractLoaderRenderProps, any> {
constructor(props: IGenericAbstractLoaderRenderProps) {
super(props);
}
public render() {
return this.props.isLoading ? (
<div className="generic-abstract-loader-wrapper">
<div className="generic-abstract-loader">
<img src={loader} />
</div>
{this.props.children(this.props)}
</div>
) : (
this.props.children(this.props)
);
}
}
It actually just renders the wrapped component if isLoading is false, or adds a layer of loading if the prop is true.
It works "quite perfectly".
I do call it in this way from another component:
public render() {
return (
<div>
<button onClick={this.justDisable}>JUST SET FALSE TO STATE</button>
<br />
<button onClick={this.enableDisable}>Disable/Enable Elements</button>
<br />
<GenericAbstractLoader customProp="custom prop" isLoading={this.state.shouldDisable}>
{props => <HomeTestForm {...props} />}
</GenericAbstractLoader>
</div>
);
}
As you can see the wrapped component is a simple HomeTestForm which contains just one input text:
export class HomeTestForm extends React.Component<IGenericAbstractLoaderHOCProps, any> {
constructor(props: IGenericAbstractLoaderHOCProps) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = { value: 'Initial Value' };
}
public render() {
return (
<div>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</div>
);
}
public handleChange(event: any) {
this.setState({ value: event.target.value });
}
}
My issue is that when I toggle the isLoading prop, the state isn't kept by the wrapped component, so if I change the value of the inner text field, when adding the loading layer, and when removing it, the value is not taken, so the input field is still rendered with the init value.
What's the right way to create persistent wrapped components with render props?
Your component's state is getting reset because React is creating an entirely new component when your loading state changes. You can see these if you add a console.log to the constructor of HomeTestForm. React thinks this is necessary because of the ternary operator in GenericAbstractLoader. If you can restructure the render function in GenericAbstractLoader to something like the example below, you can give React the context it needs to persist the component instance across renders.
render()
return (
<div className={this.props.isLoading ? "generic-abstract-loader-wrapper" : ""}>
{this.props.isLoading && <img src={loader} />}
{this.props.children(this.props)}
</div>
);
}
React's documentation has a brief section about this under Reconciliation. This situation falls under the Elements of Different Types heading.
Also, unrelated to your question, based on your current example you don't need to use a render prop. The render function in GenericAbstractLoader can just render {this.props.children} without the function call and you can place your props directly on the child component as in the example below. You may have just simplified your example for SO and have a situation where you need render props, but I wanted to point this out just in case.
<GenericAbstractLoader isLoading={this.state.shouldDisable}>
<HomeTestForm customProp="custom prop" />
</GenericAbstractLoader
I have a parent,child and grandchild component. I have different input fields and want to pass the values from grandchild to child to parent where eventually i set the state with the values. I havent included all of my code, but doing it like that is necessary because of other things in my code, which I didnt include in this post as its irrelevant. Im not sure how to do that and tried to implement what I found online, however, its not working. Any ideas? Thanks!!
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
input: {}
};
this.changeName = this.changeName.bind(this);
this.handleInput = this.handleInput.bind(this);
}
changeName(newName) {
this.setState({
name: newName
});
}
handleInput() {
console.log("helloooooo", this.state.name)
}
render() {
return (
<div>
<Child onChange={this.changeName} onClick={this.handleInput}/>
</div>
)
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleInput2 = this.handleInput2.bind(this);
}
handleChange(e) {
this.props.handleChange(e);
}
handleInput2() {
this.props.onClick()
}
render() {
return (
<div>
<GrandChild onChange={this.handleChange}/>
<input type="submit" onClick={this.handleInput2}/>
</div>
)
}
}
class GrandChild extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleInput2 = this.handleInput2.bind(this);
}
handleChange(e) {
const input = this.props.input;
input[name] = e.target.value;
}
render() {
return (
<div>
<input name="firstname" onChange={this.handleChange}/>
<input name="lastname" onChange={this.handleChange}/>
</div>
)
}
In real life everything is easier. For every component you answer following questions.
What data the component will receive?
Does it emit any event?
That's the props of the component.
So no matter how the relationship between your components is... Just answer those questions and you will be good.
Example:
I have a TodoList that contains a list of TodoItem elements. (Parent)
I have a TodoItem that displays the content of the TodoItem. (Child)
I have a Checkbox that displays a check box. (GrandChild)
a CheckBox receives a boolean saying isSelected and emit and event onChange. That's all what I know.
a TodoItem receives a Todo and emit onChange. That's all I care.
When you put everything together, TodoList has a todos, and pass todos[i] to its child and todos[i].isSelected to its grandchild, but that is what you don't need to care about. All what you care is:
What data the component will receive? Does it emit any event?
At the component level.
Having a container Component which hold state. it renders a number of stateless components.
I want to get access to all of their DOM nodes, so i can call the focus method on demand.
I am trying the ref approach as it is encouraged by the react documentation.
I'm getting the following error:
Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
What is the recommended way to get around this error?
preferably, without extra dom elements wrappers like exra divs.
Here is my Current Code:
Container Component - responsible for rendering the stateless components.
import React, { Component } from 'react';
import StatelessComponent from './components/stateless-component.jsx'
class Container extends Component {
constructor() {
super()
this.focusOnFirst = this.focusOnFirst.bind(this)
this.state = {
words: [
'hello',
'goodbye',
'see ya'
]
}
}
focusOnFirst() {
this.node1.focus()
}
render() {
return (
<div>
{
this.state.words.map((word,index)=>{
return <StatelessComponent
value={word}
ref={node => this[`node${index}`] = node}/>
})
}
<button onClick={this.focusOnFirst}>Focus on First Stateless Component</button>
</div>
)
}
}
Stateless Component - for sake of simplicity, just display a text inside a div.
import React from 'react';
export default function StatelessComponent(props) {
return <div>props.value</div>
}
Stateless (functional) components can't expose refs directly. However, if their internal components can use refs, you can pass a function from the parent (the grandparent) of the stateless component (the parent) as a ref via ref forwarding. Use the function as the ref target of the DOM element. Now the grandparent has direct access to the DOM element ref.
See Exposing DOM Refs to Parent Components in React's documentation.
const StatelessComponent = React.forwardRef((props, ref) => (
<div>
<input ref={ref} {...props} />
</div>
));
class Container extends React.Component {
itemRefs = []
componentDidMount() {
this.focusOnFirst();
}
focusOnFirst = () => this.itemRefs[0].focus()
inputRef = (ref) => this.itemRefs.push(ref)
render() {
return (
<div>
<StatelessComponent ref={this.inputRef} />
<StatelessComponent ref={this.inputRef} />
</div>
)
}
}
ReactDOM.render(
<Container />,
demo
)
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
Try this, basically you pass a callback as ref to the stateless components that gets the input instance an adds it to an array owned by the container
class Container extends Component {
constructor() {
super()
this._inputs = [];
this.focusOnFirst = this.focusOnFirst.bind(this)
this.state = {
words: [
'hello',
'goodbye',
'see ya'
]
}
}
focusOnFirst() {
this._inputs[0].focus()
}
refCallback(ref) {
this._inputs.push(ref)
}
render() {
return (
<div>
{
this.state.words.map((word,index)=>{
return <StatelessComponent
value={word}
refCallback={this.refCallback}/>
})
}
<button onClick={this.focusOnFirst}>Focus on First Stateless Component</button>
</div>
)
}
}
And the stateless get modified a little too
function StatelessComponent({refCallback, value}) {
return <input ref={refCallback} value={value}/>
}
Here's a working plunker
I'm having difficulties using the .map() method to iterate over the parent's state objects in a child component (ChildOne) to create multiple instances of another child component (ChildTwo).
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
listItems: [] // this list
}
render() {
return (
<div>
<ChildTwo listItems={this.state.listItems} />
</div>
}
for ChildOne, where the error can be found:
class TrackList extends React.Component {
render() {
console.log(this.props.items); // returns the array in parent state with no problem
return (
<div>
{this.props.items.map(item => { // this.props.items.map is not a function
return (
<ChildTwo item={item} key={item.id} />
)})
}
</div>
and finally, for ChildTwo:
class ChildTwo extends Component {
render() (
return (
<div>
<ChildOne items={this.props.listItems} />
</div>
I think your error is actually in ChildTwo? It's very confusing because your classes are not named the same as their rendering (TrackList, etc..).
You're rendering it like this, passing two different props named the same both named item:
<ChildTwo item={item} item={item.id} />
But the class definition is looking for a prop named listItems
class ChildTwo extends Component {
render() {
// ChildTwo was rendered with props `item`
return (
<div>
<ChildOne items={this.props.listItems} />
</div>
I would suggest using prop-types to show errors when the incorrect props are being passed.
According to the React tutorial at https://facebook.github.io/react/tutorial/tutorial.html:
When you want to aggregate data from multiple children or to have two
child components communicate with each other, move the state upwards
so that it lives in the parent component. The parent can then pass the
state back down to the children via props, so that the child
components are always in sync with each other and with the parent.
This seems to contradict good OOP practices where each object maintains it own state.
When you want to aggregate data from multiple children or to have two
child components communicate with each other, move the state upwards
so that it lives in the parent component. The parent can then pass the
state back down to the children via props, so that the child
components are always in sync with each other and with the parent.
Consider a case where a Parent has two children, with a structure as follows
<Parent>
<Child1/>
<Child2/>
</Parent>
Now Child1 just has the input component, and Child2 displays what was entered in the input say
In this case if you keep the value of the input in Child1, you cannot access it from the Parent as state is local to a component and is a private property . So it makes sense to keep the property in parent and then pass it down to child as props so that both children can use it
A sample snippet
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
inputVal: ''
}
}
handleChange = (val) => {
this.setState({inputVal: val});
}
render() {
return (
<div>
<Child1 handleChange={this.handleChange} inpVal={this.state.inputVal}/>
<Child2 value={this.state.inputVal}/>
</div>
)
}
}
class Child1 extends React.Component {
render() {
return (
<div>
<input type="text" value={this.props.inpVal} onChange={(e) => this.props.handleChange(e.target.value)} />
</div>
)
}
}
class Child2 extends React.Component {
render() {
return (
<div>
{this.props.value}
</div>
)
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></app>