I got a component, called Parent and it injects Child, another component.
Child has props defined as following:
function mapStateToProps(state) {
return {
foo : 'some-value'
}
}
I would like to access foo in Parent component like that:
class Parent extends Component {
render(){
//something like this
<div className={this.props.children.foo}>
{this.props.children}
</div>
}
#edit
After using this React.Children.toArray I managed to get the children as an array and forEach-it.
But the props are not available in child.props.
There is something missing. Anyone have an idea?
As this.props.children can be multiple components you should loop on it
render() {
this.props.children.forEach((component) => {
console.log(component.props.foo);
})
return ...;
}
Take a look at this
You children can be a single node or multiple node. If single node, you can access the props directly else use map to iterate over the children and return the porps.
Hope this helps!
render() {
const childProps = ! this.props.children instanceof Array
? this.props.children.props.foo //returns foo if child is a single node
: this.props.children.map(child => child.props.foo) // returns array of foo if children is a multiple nodes.
console.log(childProps)
return ...;
}
Related
i want to render a list of react components.The props of each component vary,but every element's props will be overwritten by parents props.i tried something like this
render(){
let children=this.props.children
let dis;
if(Array.isArray(children)){
dis=[]
children.forEach(c=>{let l=Object.assign(c,Object.assign(c.props,this.props))
dis.push(l)})
}
return(
{dis}
)
}
}
but got error that props in read only.
It sounds like what you're trying to do is have a parent component inject additional settings into the children it is going to render.
Take a look here:
https://stackoverflow.com/a/50786569/1541237
Basically, you would render like this:
render() {
return (
React.Children.map(this.props.children, child =>
React.cloneElement(child, { ...this.props }));
);
}
I have 3 components, App, Parent and Child. The App uses a method provided by Parent and registers the children into Parent. This is for the Parent to collect the Refs (create a dependency map) of the Children it encompasses.
Child has a method named Animate which will be invoked by Parent via refs created.
When I try to print the dependencyMap I can see an array is being generated, the way I expect.
But when I try to access the child components from Parent using the dependencyMap, it always invokes the last on on the list of children.
That is to say, when I try to animate < CustomComponent > using animateDependentChildren within Parent.js, the CustomComponent2 only gets animated, even if I pass the correctId for CustomComponent1.
App.js
export default class App extends React.Component {
render(){
return(
<Parent>
{
(props)=>{
return(
<View>
<CustomComponent1 ref={props.register('s1')} key={1}>Text</CustomComponent1>
<CustomComponent2 ref={props.register('s2')} key={2}>Text2</CustomComponent2>
<ListComponent id={'s1'} key={3}/>
<ListComponent id={'s2'} key={3}/>
</View>
)
}
}
</Parent>
);
}
}
Parent.js
class Parent extends React.Component{
constructor(props) {
super(props);
this.dependencyMap = {}
}
render() {
return (
<View>
{this.props.children({
register: this.register.bind(this)
})}
</View>
);
}
register(dependentOn){
let dependentRef = React.createRef();
if(!this.dependencyMap[dependentOn]){
this.dependencyMap[dependentOn] = [];
}
this.dependencyMap[dependentOn].push(dependentRef);
return dependentRef;
}
animateDependentChildren(listId){
let subscribers = this.dependencyMap[listId];
subscribers.forEach(subscriber => {
console.log('subscriber', subscriber);
console.log('subscriber dom', subscriber.current);
this.refs[subscriber].animate(scrollObj); // <-This function always animates the last of the list of children (ie CustomComponent2)
});
}
}
Any idea what Im doing wrong here? Cant React.createRef be used to create multiple references and then invoke them later individually?
As subscriber is the component ref, try changing this line:
this.refs[subscriber].animate(scrollObj);
...to:
subscriber.animate(scrollObj);
I hope this helps.
I'm writing a component that handle some internal state according to a ref of it's child (a mouse event related to that child's ref for example).
This component is using a render-prop to pass on the relevant piece of state to it's child, and render the child with the ref attached via React.cloneElement util.
The problem is that when the child is a class component, for some reason the ref is not available, and i can't find a way to render it as it's a react element object with a type of function (after i clone it of course).
But if the child is just a DOM node like a div for example, it is working as expected.
My work-around is to check the type of the child, and if it is a type of function I'll wrap the cloned element with my own div, if it's just a dom node then render as is.
However, i would like to not wrap the child with an extra div as i don't want to add unnecessary DOM nodes.
Here is a basic code example, most code removed for brevity:
The Parent component:
class Parent extends Component {
attachRef = node => {
this.ref = node;
}
render() {
const { render } = this.props;
const { someValue } = this.state;
const Child = render(someValue);
const WithRef = React.cloneElement(Child, {
ref: this.attachRef
});
if (typeof WithRef.type === 'string') { // node element
return WithRef;
}
else if (typeof WithRef.type === 'function') {
// this is a react element object.. not sure how to render it
// return ?
} else {
// need to find a way to render without a wrapping div
return (
<div ref={this.attachRef}>{Child}</div>
);
}
}
}
The usage:
class App extends Component {
render() {
return (
<div>
<Parent render={someValue => <div> {someValue}</div>} />
<Parent render={someValue => <Menu someValue={someValue} />} />
</div>
);
}
}
When i render regular DOM nodes like the first example it works fine, when i try to render the Menu (which is a class component) it doesn't work as mentioned above.
I had almost an identical issue.
i chose to use findDOMNode from react-dom, you can see the full solution in react-external-click.
Although the warning notes:
findDOMNode is an escape hatch used to access the underlying DOM node.
In most cases, use of this escape hatch is discouraged because it
pierces the component abstraction.
findDOMNode only works on mounted components (that is, components that
have been placed in the DOM). If you try to call this on a component
that has not been mounted yet (like calling findDOMNode() in render()
on a component that has yet to be created) an exception will be
thrown.
findDOMNode cannot be used on functional components.
I think this is the better solution for this particular challenge.
It let's you be "transparent" to the consumer, while being able to target the component in the DOM.
Ok here it is, grabbing the ref:
componentDidMount() {
this.ref = findDOMNode(this);
// some logic ...
}
this is how i use a render function with no wrapper of my own:
render() {
const { children, render } = this.props;
const { clickedOutside } = this.state;
const renderingFunc = render || children;
if (typeof renderingFunc === 'function') {
return renderingFunc(clickedOutside);
} else {
return null
}
}
}
I have simple component called List which is a simple ul with some li inside. Each li is a simple component.
I have other parent component which render one input field and the List component. Tapping on Send key I catch text of input field. I want to call for example a function called handleNewText(inputText) but this function need to stay inside List component because the state I use to populate other li components live in List component.
I don' t want to refactor List and MyParent component passing the manage of data from List to MyParent.
first is parent and second is child
class TodoComp extends React.Component {
constructor(props){
super(props);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
componentDidMpunt(){
console.log(this._child.someMethod());
}
handleKeyPress(event){
if(event.key === 'Enter'){
var t = event.target.value;
}
}
render(){
return (
<div>
<input
className="inputTodo"
type="text"
placeholder="want to be an hero...!"
onKeyPress={this.handleKeyPress}
/>
<List/>
</div>
);
}
}
export default class List extends React.Component {
constructor() {
super();
this.flipDone = this.flipDone.bind(this);
this.state = {
todos: Array(3).fill({ content: '', done: false})
};
}
flipDone(id) {
let index = Number(id);
this.setState({
todos: [
...this.state.todos.slice(0, index),
Object.assign({}, this.state.todos[index], {done: !this.state.todos[index].done}),
...this.state.todos.slice(index + 1)
]
});
}
render() {
const myList = this.state.todos.map((todo, index) => {
return (
<Todo key={index}
clickHandler={this.flipDone}
id={index}
todo={todo}
handleText={this.handleText}
/>
);
})
return (
<ul className="list">
{myList}
</ul>
);
}
ReactDOM.render(<TodoComp />,document.getElementById('myList'));
You need to make use of refs to call a function in the child component from the parent component
render the List component from parent as
<List ref="myList"/>
and then access the handleNewText() function as this.refs.myList.handleNewText()
UPDATE:
Strings refs are no longer recommended by React, you should rather use ref callbacks, check this
<List ref={(ref) => this.myList=ref}/>
and then access the child function like
this.myList.handleNewText()
Adding to #shubham-khatri solution:
If you are referencing a connected child component...
a. That child must say withRef: true in the (4th) config parameter:
#connect(store => ({
foo: store.whatever
…
}),null,null,{ withRef: true })
b. Access is through getWrappedInstance() (note, that getWrappedInstance also needs to be called ())
getWrappedInstance().howdyPartner()
I started learning React when functional component came out. Another way I experimented with some success is returning functions that you want to access as closures within a JSON. I like this method because closure is a construct of Javascript and it should still work even if React is updated yet again. Below is an example of child component
function Child(){
//declare your states and use effects
const [ppp, setPPP] = useState([]);
const [qqq, setQQQ] = useState(2);
//declare function that you want to access
function funcA(){ /*function to interact with your child components*/}
function funcB(){ /*function to interact with your child components*/}
//pure React functional components here
function Content(){
//function that you cannot access
funcC(){ /*.....*/}
funcD(){/*.......*/}
//what to render
return (
<div>
{/* your contents here */}
</div>
)
}
//return accessible contents and functions in a JSON
return {
content: Content, //function for rendering content
ExposeA: funcA, //return as a closure
ExposeB: funcB, //return as a closure
}
}
Below is an example of how you would render the child contents within the parent
function Parent(){
let chi = Child();
let ChildContent = chi.Content;
//calling your exposed functions
//these function can interacts with the states that affects child components
chi.ExposeA();
chi.ExposeB();
//render your child component
return (<div>
<div> {/* parent stuff here */</div>
<div> {/* parent stuff here */</div>
<ChildContent {/*Define your props here */} />
</div>)
}
I have simple component called List which is a simple ul with some li inside. Each li is a simple component.
I have other parent component which render one input field and the List component. Tapping on Send key I catch text of input field. I want to call for example a function called handleNewText(inputText) but this function need to stay inside List component because the state I use to populate other li components live in List component.
I don' t want to refactor List and MyParent component passing the manage of data from List to MyParent.
first is parent and second is child
class TodoComp extends React.Component {
constructor(props){
super(props);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
componentDidMpunt(){
console.log(this._child.someMethod());
}
handleKeyPress(event){
if(event.key === 'Enter'){
var t = event.target.value;
}
}
render(){
return (
<div>
<input
className="inputTodo"
type="text"
placeholder="want to be an hero...!"
onKeyPress={this.handleKeyPress}
/>
<List/>
</div>
);
}
}
export default class List extends React.Component {
constructor() {
super();
this.flipDone = this.flipDone.bind(this);
this.state = {
todos: Array(3).fill({ content: '', done: false})
};
}
flipDone(id) {
let index = Number(id);
this.setState({
todos: [
...this.state.todos.slice(0, index),
Object.assign({}, this.state.todos[index], {done: !this.state.todos[index].done}),
...this.state.todos.slice(index + 1)
]
});
}
render() {
const myList = this.state.todos.map((todo, index) => {
return (
<Todo key={index}
clickHandler={this.flipDone}
id={index}
todo={todo}
handleText={this.handleText}
/>
);
})
return (
<ul className="list">
{myList}
</ul>
);
}
ReactDOM.render(<TodoComp />,document.getElementById('myList'));
You need to make use of refs to call a function in the child component from the parent component
render the List component from parent as
<List ref="myList"/>
and then access the handleNewText() function as this.refs.myList.handleNewText()
UPDATE:
Strings refs are no longer recommended by React, you should rather use ref callbacks, check this
<List ref={(ref) => this.myList=ref}/>
and then access the child function like
this.myList.handleNewText()
Adding to #shubham-khatri solution:
If you are referencing a connected child component...
a. That child must say withRef: true in the (4th) config parameter:
#connect(store => ({
foo: store.whatever
…
}),null,null,{ withRef: true })
b. Access is through getWrappedInstance() (note, that getWrappedInstance also needs to be called ())
getWrappedInstance().howdyPartner()
I started learning React when functional component came out. Another way I experimented with some success is returning functions that you want to access as closures within a JSON. I like this method because closure is a construct of Javascript and it should still work even if React is updated yet again. Below is an example of child component
function Child(){
//declare your states and use effects
const [ppp, setPPP] = useState([]);
const [qqq, setQQQ] = useState(2);
//declare function that you want to access
function funcA(){ /*function to interact with your child components*/}
function funcB(){ /*function to interact with your child components*/}
//pure React functional components here
function Content(){
//function that you cannot access
funcC(){ /*.....*/}
funcD(){/*.......*/}
//what to render
return (
<div>
{/* your contents here */}
</div>
)
}
//return accessible contents and functions in a JSON
return {
content: Content, //function for rendering content
ExposeA: funcA, //return as a closure
ExposeB: funcB, //return as a closure
}
}
Below is an example of how you would render the child contents within the parent
function Parent(){
let chi = Child();
let ChildContent = chi.Content;
//calling your exposed functions
//these function can interacts with the states that affects child components
chi.ExposeA();
chi.ExposeB();
//render your child component
return (<div>
<div> {/* parent stuff here */</div>
<div> {/* parent stuff here */</div>
<ChildContent {/*Define your props here */} />
</div>)
}