4

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.

3
  • 1
    You 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. Commented May 1, 2019 at 13:49
  • Thanks 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. Commented May 1, 2019 at 13:54
  • Yes, using Redux might confuse things if your just learning,.. I could maybe knock you up a really simple snippet that might help. Commented May 1, 2019 at 13:57

2 Answers 2

11

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>

Sign up to request clarification or add additional context in comments.

2 Comments

@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.
Worked For me. The key is using spread operator and updating the array.
0

Instead of creating blocksHandlers as a separate function ,you can have it nside the Menu.js like as follows *

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>
      );
    }
}

Menu.js

class Menu extends React.Component {

  constructor(props) {

    super(props);
    this.state = {value: '',blocksArray:[]};

    this.handleAdd = this.handleAdd.bind(this);

  }

  handleAdd() {
   this.setState({
        blocksArray:this.state.blocksArray.push(block)
     })

  }

renderBlocks = ()=>{
      this.state.blocksArray.map(block=> <Block/>)
 }
  render() {
    return (
      <div className="Menu">
        <header className="Menu-header">
          <button className="Menu-button" onClick={()=>this.handleAdd()}>Add block</button>
        </header>
    {this.renderBlocks()}

      </div>
    );
  }
}

export default Menu;

2 Comments

Thank 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.
actually, inside handleAdd() , you need to give the configuration for the blocks,like this.state.blocksArray.push(/*give the data needed to render Block component*/)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.