Dynamically wrapping a random number React components at run time - javascript

If I have say the following simple components:
const CompOne = (props) => {
return (
<div className="compOne">
{props.children}
</div>
);
};
const CompTwo = (props) => {
return (
<div className="compTwo">
{props.children}
</div>
);
};
const CompThree = (props) => {
return (
<div className="compThree">
{props.content}
</div>
);
};
Now during run time, after making an AJAX request the client receives information that gives the order in which components need to wrap into one another. The result of that AJAX request would look something like this:
let renderMap = [
{ component: CompOne, props: {} },
{ component: CompTwo, props: {} },
{ component: CompThree, props: { content: "hi there" } }
];
So the composition should flow by iterating through the array and composing one component into the next. e.g: CompOne(CompTwo(CompThree)))
Two important things to note when I tried creating a wrapping HOC to fix this issue:
Edit: Important detail I forgot to mention in the original post
1) The number of components to wrap will not be consistent. At times it could be 3, but other times as many as 4, 5, 6 components needed to wrap into each other
2) The order could be different each time
<CompOne>
<CompTwo>
<CompThree content="hi there">
</CompThree>
</CompTwo>
</CompOne>
So my resulting HTML would be:
<div className="compOne">
<div className="compTwo">
<div className="compThree">
hi there
</div>
</div>
</div>
I've tried various things but I can't get it to work once I start getting past just wrapping two components. Is this something I can even do in React?

Like the link that Arup Rakshit posted in the comments showed, you can use components that are stored in a variable - with JSX - as long as they are capitalized:
// in render()
const MyComp = props.someVariableContainingAComponent;
return <MyComp />;
With that in mind, one approach to your problem would be to iterate through all your components, starting with inner one, and then taking the each of the next to use as a wrapper for the previous one. Given the shape of your test data renderMap, and using Array.protype.reduce for the iteration, it could look something like this:
renderComponents(renderMap) {
const Component = renderMap
.reverse()
.reduce( (ComponentSoFar, {component, props}) => {
const Outer = component;
return () => (<Outer {...props} ><ComponentSoFar /></Outer>);
}, props => null ); // initial value, just a "blank" component
return ( <Component /> );
}
I have included a demo showing how both different number of components and varying order of nesting can be handled with this approach.
const CompOne = (props) => (
<div className="comp compOne"><p>One:</p>{ props.content || props.children } </div>);
const CompTwo = (props) => (
<div className="comp compTwo"><p>Two:</p> { props.content || props.children }</div>);
const CompThree = (props) => (
<div className="comp compThree"><p>Three:</p> { props.content || props.children }</div>);
const CompFour = (props) => (
<div className="comp compFour"><p>Four:</p> { props.content || props.children }</div>);
const CompFive = (props) => (
<div className="comp compFive"><p>Five:</p> { props.content || props.children }</div>);
const renderMap1 = [
{ component: CompOne, props: {} },
{ component: CompTwo, props: {} },
{ component: CompThree, props: {} },
{ component: CompFour, props: {} },
{ component: CompFive, props: { content: "hi there" } }
];
const renderMap2 = [].concat(renderMap1.slice(1,4).reverse(), renderMap1.slice(4))
const renderMap3 = renderMap2.slice(1);
class App extends React.Component {
constructor(props) {
super(props);
}
renderComponents(renderMap) {
const Component = renderMap
.reverse()
.reduce( (ComponentSoFar, {component, props}) => {
const Outer = component;
return () => (<Outer {...props} ><ComponentSoFar /></Outer>);
}, props => null ); // initial value, just a "blank" component
return ( <Component /> );
}
render() {
return ( <div>
{ this.renderComponents(renderMap1) }
{ this.renderComponents(renderMap2) }
{ this.renderComponents(renderMap3) }
</div> );
}
};
ReactDOM.render(<App />, document.getElementById('root'));
.comp {
border: 5px solid green;
padding: 5px;
margin: 5px;
display: inline-block;
}
.compOne { border-color: red;}
.compTwo { border-color: green;}
.compThree { border-color: blue;}
.compFour { border-color: black;}
.compFive { border-color: teal;}
<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="root"></div>

Edit: New info was added to the question. Given that info, this approach doesn't work.
You can probably solve it using a Higher-order component (HOC), something like this:
const superWrapped = (Outer) => (Middle) => (Inner) => props => {
return (
<Outer>
<Middle>
<Inner content={props.content} />
</Middle>
</Outer>
)
};
Where you would later use it like this:
render() {
const SuperWrapped =
superWrapped(CompOne)(CompThree)(CompTwo); // any order is fine!
return (<SuperWrapped content="Something here.." /> );
}
Some minor adjustments on your components would be necessary for this to work. I've included a working demo below:
const superWrapped = (Outer) => (Middle) => (Inner) => props => {
return (
<Outer>
<Middle>
<Inner content={props.content} />
</Middle>
</Outer>)
};
const CompOne = (props) => {
return (
<div className="compOne">
<p>One:</p>
{props.children || props.content}
</div>
);
};
const CompTwo = (props) => {
return (
<div className="compTwo">
<p>Two:</p>
{props.children || props.content}
</div>
);
};
const CompThree = (props) => {
return (
<div className="compThree">
<p>Three:</p>
{props.children || props.content}
</div>
);
};
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const components = getComponentOrder();
const SuperWrapped1 =
superWrapped(components[0])(components[1])(components[2]);
const SuperWrapped2 =
superWrapped(components[2])(components[1])(components[0]);
return (
<div>
<SuperWrapped1 content="Hello, world!" />
<SuperWrapped2 content="Goodbye, world!" />
</div>
);
}
}
const getComponentOrder = () => {
return Math.random() < 0.5 ?
[CompOne, CompTwo, CompThree] :
Math.random() < 0.5 ?
[CompThree, CompOne, CompTwo] :
[CompTwo, CompOne, CompThree]
};
ReactDOM.render(<App />, document.getElementById('root'));
.compOne {
border: 5px solid red;
padding: 5px;
margin: 5px;
display: inline-block;
}
.compTwo {
border: 5px solid green;
padding: 5px;
margin: 5px;
display: inline-block;
}
.compThree {
border: 5px solid blue;
padding: 5px;
margin: 5px;
display: inline-block;
}
<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="root"></div>

The easiest way to achieve this is to use a recursive function:
let renderMap = [
{ component: CompOne, props: {} },
{ component: CompTwo, props: {} },
{ component: CompThree, props: { content: "hi there" } }
];
function App() {
let index = 0;
let structure = "Hi There!"
function packing() {
if (index === renderMap?.length) return;
const Comp = renderMap[index].component
structure = <Comp>{structure}</Comp>;
index += 1;
packing();
}
packing();
return <>{structure}</>
}

Related

React composition - How to call parent method

I'm using composition in React and would like to call a parent method. All of the examples I've found use inheritance.
Container component - Inserts a child component
interface ContainerProps {
children: ReactNode;
}
function Container(props: ContainerProps) {
const [showApply, setShowApply] = useState<boolean>(false);
return (
<>
<div>Children</div>
{props.children}
</>
);
// I want to call this method from the `children`
function calledByChild(){}
}
Composition - Needs to call Container method when button is clicked
function CombinedComponent() {
return <Container handleApplyClicked={handleApplyClicked}>
<Button type="primary" shape="round" onClick={tellContainerThatButtonWasClicked}>
</Container >
}
When the button is clicked in the CombinedComponent I would like it to inform the Container. The examples I've seen use inheritance and pass the parents method to the child but in this case the child is defining the parent within it.
How can this be achieved?
Update
I've tried adding this to the parent but the child components don't seem to have the extra property added.
{React.cloneElement(props.children as React.ReactElement<any>, { onClick: myFunc })}
Child interface/props
interface CombinedComponentProps{
// This value is always undefined
onClick?: () => void;
}
function CombinedComponent(props: CombinedComponentProps) {
...
// Undefined
console.log(props.onClick)
}
I recently had to do something similar and, inspired by this post, I ended up with:
const WrapperComponent = ({ children }) => {
const myFunc = React.useCallback(() => {
// ...
}, []);
return React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { onClick: myFunc });
}
});
}
[edit] a working demo:
The following snippet demonstrate how the above approach could be used to read a child prop from the wrapper/parent component.
Please be aware that it might take a few seconds for the snippet to load and run; I did not investigate the reason for this, as it is out of the scope for the question.
const App = () => {
return (
<div>
<h1>MyApp</h1>
<WrapperComponent>
<button id='btn1'>btn1</button>
<button id='btn2'>btn2</button>
<button id='btn3'>btn3</button>
<div className='fakeBtn' id='div1'>div1</div>
</WrapperComponent>
</div>
);
}
const WrapperComponent = ({ children }) => {
const [clickedChildId, setClickedChildId] = React.useState();
const myFunc = React.useCallback((id) => {
setClickedChildId(id)
}, [setClickedChildId]);
React.useEffect(() => {
clickedChildId && console.log(`the clicked child ID is ${clickedChildId}`);
}, [clickedChildId]);
return React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { onClick: () => myFunc(child.props.id) });
}
});
}
ReactDOM.render(<App />, document.querySelector('#mountNode'))
div.fakeBtn {
background-color: #cdf;
padding: 5px;
margin: 3px 0;
border: 1px solid #ccc;
border-radius: 2px;
}
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id='mountNode'></div>
You can do it by cloning the children, and giving it the props that you want:
React.cloneElement(children, {calledByChild})
This way, you add the function calledByChild to the children, so you can call it from the children component.
It could look like this:
const Parent = ({ children }) => {
const func = () => console.log("click in Parent");
return (
<>
<div>children</div>
{cloneElement(children, {func})}
</>
);
};
const Children = ({func}) => {
return <button onClick={func}>Click</button>;
};
Take a look at this article

How do you call a function by default in the render method?

For some reason, its not actually accessing the exercises method. Can someone tell me what I'm doing wrong here. Appreciate it!
class StartWorkout extends Component {
state = {
step: 0
};
exercises = () => {
const { exerciselist } = this.props.selectedWorkout;
const { step } = this.state.step;
while (step < exerciselist.length) {
return <StartExercise exercise={exerciselist[step]} />;
}
};
render() {
const { name } = this.props.selectedWorkout;
const { step } = this.state;
return (
<>
<ClientMenuBar title={name} />
<Container maxWidth="xl">
{this.exercises()}
<div style={styles.buttonWrapper}>
<Button
color="inherit"
variant="contained"
style={styles.button}
size="large"
>
Back
</Button>
<Button
color="primary"
variant="contained"
type="submit"
style={styles.button}
size="large"
onClick={() => {
this.setState({ step: step + 1 });
}}
>
Next
</Button>
</div>
</Container>
</>
);
}
}
I know its not actually going into the method because I am console logging right before i access it and right before the while loop in the function and its only calling the first.
The issue is with the exercises method. Here you are trying to accessstep from this.state.step. Replace const { step } = this.state.step; . by const { step } = this.state;.
Notice the extra step property which you are trying to access which comes as undefined and undefined < any number is false, and hence the code inside while loop never gets executed.
A few problems here:
// this line is double-dereferencing 'step'
const { step } = this.state.step;
// step will be undefined here
while (step < exerciselist.length) {
// if you're going to return this, what's the loop for?
// (and if you don't return, this loop will never exit
// because 'step' never changes)
return <StartExercise exercise={exerciselist[step]} />;
}
Here's an updated example of how you might go about it now that I better understand what you're trying to do.
class Exercises extends React.Component {
state = {step: 0}
render() {
const {step} = this.state;
const {workout: {exercises}} = this.props;
return (
<div>
<div> {exercises[step]} </div>
<button
disabled={step < 1}
onClick = {() => this.setState({step: step - 1})}
>
Previous
</button>
<button
disabled={step >= exercises.length - 1}
onClick = {() => this.setState({step: step + 1})}
>
Next
</button>
</div>
)
}
}
const workout = {
exercises: Array.from({length: 5}, (_, i) => `Exercise ${i + 1}`)
};
ReactDOM.render(
<Exercises workout={workout} />,
document.querySelector("#app")
);
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You need to use constructor. Update your code like this:
class StartWorkout extends Component {
constructor(props){
super(props);
this.state = {
step: 0,
};
}
exercises = () => {
const { exerciselist } = this.props.selectedWorkout;
// The way your destructuring the state is wrong. It should be like this.
const { step } = this.state; // not required anymore
return exerciselist.map(item => {
return <StartExercise exercise={item} />;
}
};
// Use your render function as it is.
}
Hope this helps!!

Add multiple objects to an array?

Every time I click an option of size and click add to cart I would like to add the data of the selected object to this array cart. This currently works kinda but only one object can be added and when you try to do it again the old data disappears and is replaced with the new object.
I would like to keep odd objects in the array and add new objects too. How do I go about doing this?
index.js
export class App extends Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
render() {
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
,
{
name: "size three",
price: 3
}
];
const cart = [];
const addCart = function() {
cart.push(product[evenIndex]);
if (cart.length > 0) {
}
};
console.log("cart", cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={addCart}>Add to cart</button>
</div>
);
}
}
child.js
export class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
Here is my code on sandbox: https://codesandbox.io/s/14vyy31nlj
I've just modified your code to make it work. Here is the complete code. You need cart to be part of the state, so it does not initialize in each render, and to make the component render again when you add an element.
Remove the function to make it a method of the class:
addToCart() {
const selectedProduct = products[this.state.evenSelected];
this.setState({
cart: [...this.state.cart, selectedProduct]
});
}
And call it on render:
render() {
console.log("cart", this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = products[evenIndex] && products[evenIndex].price;
return (
<div>
<Child
product={products}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addToCart.bind(this)}>Add to cart</button>
</div>
);
}
}
Check that I have binded on render, which can bring performance issues in some cases. You should check this
Update
As devserkan made me notice (Thanks!), when you use the previous state to define the new state (for example adding an element to an array), it is better to use the updater function instead of passing the new object to merge:
this.setState(prevState => ({
cart: [...prevState.cart, products[selectedProduct]],
}));
For more info check the official docs.
I don't quite understand what are you trying to but with a little change here it is. I've moved product out of the components like a static variable. Also, I've changed the addCart method, set the state there without mutating the original one and keeping the old objects.
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
{
name: "size three",
price: 3
}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null,
cart: [],
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
addCart = () => {
const evenIndex = this.state.evenSelected;
this.setState( prevState => ({
cart: [ ...prevState.cart, product[evenIndex] ],
}))
};
render() {
console.log(this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addCart}>Add to cart</button>
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.selectorRBox {
width: 260px;
height: 29.5px;
border: 1px solid #727272;
margin-top: 18px;
}
.selectedRBox {
width: 254px;
height: 29.5px;
margin-top: 14px;
border: 4px solid pink;
}
.selectorTextL {
font-family: "Shree Devanagari 714";
color: #727272;
cursor: pointer;
font-size: 18px;
}
<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="root"></div>

Draft-JS - How to create a custom block with some non-editable text

In Draft-JS, I would like a basic custom block, rendering an <h1> element. I would like to add some text before my h1, that the user cannot edit. The text is here to inform people that this block is for Title. So I would like to add "TITLE" in front of the block that is not editable.
What is the best way to achieve this in Draft JS?
You can achieve your aim by applying contentEditable={false} and readOnly property on the node that should be read-only:
class MyCustomBlock extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="my-custom-block">
<h1
contentEditable={false} // <== !!!
readOnly // <== !!!
>
Not editable title
</h1>
<div className="editable-area">
<EditorBlock {...this.props} />
</div>
</div>
);
}
}
Check working demo in the hidden snippet below:
const {Editor, CharacterMetadata, DefaultDraftBlockRenderMap, ContentBlock, EditorBlock, genKey, ContentState, EditorState} = Draft;
const { List, Map, Repeat } = Immutable;
class MyCustomBlock extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="my-custom-block">
<h1
contentEditable={false}
readOnly
>
Not editable title
</h1>
<div className="editable-area">
<EditorBlock {...this.props} />
</div>
</div>
);
}
}
function blockRendererFn(contentBlock) {
const type = contentBlock.getType();
if (type === 'MyCustomBlock') {
return {
component: MyCustomBlock,
props: {}
};
}
}
const RenderMap = new Map({
MyCustomBlock: {
element: 'div',
}
}).merge(DefaultDraftBlockRenderMap);
const extendedBlockRenderMap = Draft.DefaultDraftBlockRenderMap.merge(RenderMap);
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty()
};
}
_handleChange = (editorState) => {
this.setState({ editorState });
}
_onAddCustomBlock = () => {
const selection = this.state.editorState.getSelection();
this._handleChange(addNewBlockAt(
this.state.editorState,
selection.getAnchorKey(),
'MyCustomBlock'
))
}
render() {
return (
<div>
<div className="container-root">
<Editor
placeholder="Type"
blockRenderMap={extendedBlockRenderMap}
blockRendererFn={blockRendererFn}
editorState={this.state.editorState}
onChange={this._handleChange}
/>
</div>
<button onClick={this._onAddCustomBlock}>
ADD CUSTOM BLOCK
</button>
</div>
);
}
}
ReactDOM.render(<Container />, document.getElementById('react-root'));
const addNewBlockAt = (
editorState,
pivotBlockKey,
newBlockType = 'unstyled',
initialData = new Map({})
) => {
const content = editorState.getCurrentContent();
const blockMap = content.getBlockMap();
const block = blockMap.get(pivotBlockKey);
if (!block) {
throw new Error(`The pivot key - ${ pivotBlockKey } is not present in blockMap.`);
}
const blocksBefore = blockMap.toSeq().takeUntil((v) => (v === block));
const blocksAfter = blockMap.toSeq().skipUntil((v) => (v === block)).rest();
const newBlockKey = genKey();
const newBlock = new ContentBlock({
key: newBlockKey,
type: newBlockType,
text: '',
characterList: new List(),
depth: 0,
data: initialData,
});
const newBlockMap = blocksBefore.concat(
[[pivotBlockKey, block], [newBlockKey, newBlock]],
blocksAfter
).toOrderedMap();
const selection = editorState.getSelection();
const newContent = content.merge({
blockMap: newBlockMap,
selectionBefore: selection,
selectionAfter: selection.merge({
anchorKey: newBlockKey,
anchorOffset: 0,
focusKey: newBlockKey,
focusOffset: 0,
isBackward: false,
}),
});
return EditorState.push(editorState, newContent, 'split-block');
};
body {
font-family: Helvetica, sans-serif;
}
.container-root {
border: 1px solid black;
padding: 5px;
margin: 5px;
}
.my-custom-block {
background-color: cadetblue;
margin: 15px 0;
font-size: 16px;
position: relative;
}
.editable-area {
background-color: lightblue;
height: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/draft-js/0.10.0/Draft.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/draft-js/0.7.0/Draft.css" rel="stylesheet"/>
<div id="react-root"></div>

How do I set a ref when using react.createElement?

I want to get a ref to the component represented by the element i create, but cannot get it to work. I tried this:
var comp = React.createElement(
MyComp,
{
props: myprops,
ref: "mycomp"
}
);
But this doesn't work. How do i set a ref on it so the parent can call this.refs.mycomp.someMethod()?
https://facebook.github.io/react/docs/top-level-api.html#react.createelement
ReactElement createElement(
string/ReactClass type,
[object props],
[children ...]
)
The second parameter of the function is an optional props object for the component. Unless you want to refer to the props in the component as props.props you can splat the myProps object:
var comp = React.createElement(MyComp, { ...myprops, ref: "mycomp" });
class MyComp extends React.Component {
constructor(props) {
super(props);
this.initialValue = props.initialValue;
this.state = { value: this.initialValue };
this.increment = this.increment.bind(this);
this.reset = this.reset.bind(this);
}
increment() {
this.setState({ value: this.state.value + 1 });
}
reset() {
this.setState({ value: this.initialValue });
}
render() {
return (
<div className="child">
<h1>Counter: {this.state.value}</h1>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.reset = this.reset.bind(this);
}
reset() {
this.refs.mycomp.reset();
}
render() {
const myProps = { initialValue: 1 };
const Comp = React.createElement(MyComp, { ...myProps, ref: "mycomp" });
return (
<div className="parent">
{Comp}
<button onClick={this.reset}>Reset</button> Calls this.refs.mycomp.reset
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
.parent {
background-color: #555;
color: #FFF;
padding: 10px;
}
.child {
background-color: #888;
padding: 10px;
}
h1 {
margin: 0;
}
<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"></div>
As of react v16.3, this.refs has been discouraged. One of the popular ways of doing it would be,
const Comp = React.createElement(
MyComp,
{
props: myprops,
ref: ref => this.mycomp = ref
}
);

Categories

Resources