ReactJS: Dynamically add a component on click
Below I've knocked up a really simple Parent / Child type setup,..
The Parent is responsible for rendering the Buttons, I just used a simple numbered array here. When you click any of the buttons, it calls the setState in the Parent, and this in turns causes the Parent to re-render it's Children.
Note: I've also used React Hooks to do this, I just find them more natural and easier to use. You can use Classes, the same principle applies.
const {useState} = React;
function Child(props) {
const {caption} = props;
const {lines, setLines} = props.pstate;
return <button onClick={() => {
setLines([...lines, lines.length]);
}}>
{caption}
</button>;
}
function Parent(props) {
const [lines, setLines] = useState([0]);
return lines.map(m => <Child key={m} caption={`Click ${m}`} pstate={{lines, setLines}}/>);
}
ReactDOM.render(<React.Fragment>
<Parent/>
</React.Fragment>, document.querySelector('#mount'));
<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="mount"></div>
devamat
Updated on May 02, 2020Comments
-
devamat about 4 years
I have a menu button that when pressed has to add a new component. It seems to work (if I manually call the function to add the components they are shown). The problem is that if I click the button they are not shown, and I suppose because I should use setState to redraw them. I am not sure how to call the setState of another component within another function/component.
This is my index.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import Menu from './Menu'; import * as serviceWorker from './serviceWorker'; import Blocks from './Block.js'; ReactDOM.render( <div className="Main-container"> <Menu /> <Blocks /> </div> , document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: serviceWorker.unregister();
Then I have the Menu.js
import React from 'react'; import './Menu.css'; import {blocksHandler} from './Block.js'; class Menu extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleAdd = this.handleAdd.bind(this); } handleAdd(event) { blocksHandler.add('lol'); console.log(blocksHandler.render()); } render() { return ( <div className="Menu"> <header className="Menu-header"> <button className="Menu-button" onClick={this.handleAdd}>Add block</button> </header> </div> ); } } export default Menu;
And finally the Block.js
import React from 'react'; import './Block.css'; // this function adds components to an array and returns them let blocksHandler = (function() { let blocks = []; return { add: function(block) { blocks.push(block); }, render: function() { return blocks; } } })(); class Block extends React.Component { constructor(props) { super(props); this.state = { title: '', content: '' }; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({[event.target.name]: event.target.value}); } handleSubmit(event) { alert('A name was submitted: ' + this.state.title); event.preventDefault(); } render() { return ( <div className="Block-container"> <form onSubmit={this.handleSubmit}> <div className="Block-title"> <label> Block title: <input type="text" name="title" value={this.state.value} onChange={this.handleChange} /> </label> </div> <div className="Block-content"> <label> Block content: <input type="text" name="content" value={this.state.value} onChange={this.handleChange} /> </label> </div> <input type="submit" value="Save" /> </form> </div> ); } } class Blocks extends React.Component { render() { return ( <div> {blocksHandler.render().map(i => ( <Block key={i} /> ))} </div> ) } } export default Blocks; export {blocksHandler};
I am a React complete beginner so I'm not even sure my approach is correct. Thank you for any help you can provide.
-
Keith about 5 yearsYou can pass parent state down to children using props,. If passing down props to children contains lots of sub-components, you can also pass props using contexts. reactjs.org/docs/context.html The best solution is not using React state at all, and using a more robust state management system, Redux is meant to be good for this.
-
devamat about 5 yearsThanks for your post. I'm trying to learn ReactJS and adding Redux might make things too complex for now. I'll check out your link.
-
Keith about 5 yearsYes, using Redux might confuse things if your just learning,.. I could maybe knock you up a really simple snippet that might help.
-
-
devamat about 5 yearsThank you for your post. I could not make this code work. I get an error at blocksArray: this.state.blocksArray.push(block), I changed it to blocksArray: this.state.blocksArray.push('lol') but with no luck, I still get an error: TypeError: this.state.blocksArray.map is not a function. Probably the array is empty for some reason.
-
Keith about 5 years@devamat Your welcome, if anything in the above looks confusing, just ask away. If you want a little exercise on the above snippet, see if you can alter it so that only the last button is enabled each time.
-
Rajesh Kumaran about 5 yearsactually, inside handleAdd() , you need to give the configuration for the blocks,like this.state.blocksArray.push(/*give the data needed to render Block component*/)