Ad

ReactJS: Dynamically Add A Component On Click

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.

Ad

Answer

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/[email protected]/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="mount"></div>

Ad
source: stackoverflow.com
Ad