ReactJS: Dynamically add a component on click - javascript

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.

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>

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;

Related

How to set a particular state of a parent component from child component in React?

I'm new to React. I'm trying to do simple validation of a form elements. I'm stuck at validating input data and setting the 'name_error' state as the error msg inside <p> tag. Leaving my code below
// App.js
import React, { Component } from 'react';
import ValidateName from './component/validation';
import Modal from './modal';
class Home extends Component {
state = {
show: false,
name: "",
name_error: ""
}
handleChanges = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({[name] : value})
}
render() {
console.log(this)
return (
<div>
<div className='Main'>
{/* < Main /> */}
<button id='add' onClick={()=>{this.setState({show: true})}}>Add</button>
</div>
{this.state.show &&
<Modal>
<form>
<div className='modalContainer'>
<b className='title'>Register</b>
<button id='xmark' onClick={()=>{this.setState({show: false})}} >×</button>
<label for='name' >Name</label><br />
<input type="text" id='name' placeholder='Enter your name here' name="name" onChange={this.handleChanges}/><br />
< ValidateName content={this.state.name} />
<button type='submit'>Sign Up</button>
<button>Cancel</button>
</div>
</form>
</Modal>}
</div>
);
}
}
export default Home;
// Modal.js
import React, { Component } from 'react';
class Modal extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
export default Modal;
//validation.js
class ValidateName extends Component {
err1 ='Please enter any name';
err2 = 'Use only letters in this field';
render() {
const ren = this.props.content.length === 0 ? (<p>{this.err1}</p> ) :
(this.props.content.match(/[a-zA-Z]+$/) ? '' : <p>{this.err2}</p>)
return ren;
}
}
Please suggest an idea to set name_error as 'Please enter any name' or 'Use only letters in this field' when user enters wrong input
State should be like this,
constructor(props){
super(props);
this.state = {
show: false,
name: "",
name_error: ""
}
}
And Also the update state using callback. It would be helpful,
this.setState({[name] : value},() =>{console.log(this.state)})

trouble getting data from react input form

I am just making a simple app to learn react with redux.
I just want to get data input in the react input form on the server-side.
The problem is that the params on the server-side is like this.
{"item"=>{"name"=>"undefined","price"=>"undefined"...}...}
Here is part of my code:
import React from "react";
class ItemForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.update = this.update.bind(this);
}
handleSubmit(e) {
e.preventDefault();
const ItemData = new FormData();
ItemData.append("item[name]", this.props.item.name);
ItemData.append("item[price]", this.props.item.price);
};
update(field) {
return (e) => {
this.setState({ [field]: e.target.value });
};
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<label>
<div>Item</div>
<input
type="text"
value={this.props.item.name}
onChange={this.update("name")}
/>
</label>
<label>
<div>Price</div>
<input
type="number"
value={this.props.item.price}
onChange={this.update("price")}
/>
</label>
<div>
<input type="submit" value="Submit" />
</div>
</div>
</form>
</div>
);
}
}
Shoule I use store function in redux or is there more easy way?
Thanks.
Assumption
I'm assuming that the problem is that the data you can access in your handleSubmit function is not being updated, therefore you always receive the values that you initialized the component with.
Solution
Initialize the state based on the name and price props passed in
Set the value of your input tags to the state values
Access the state in your handleSubmit function
import React from "react";
import { withRouter } from "react-router-dom";
class ItemForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.update = this.update.bind(this);
this.state = {
name: this.props.item.name,
price: this.props.item.price
}
}
handleSubmit(e) {
e.preventDefault();
const ItemData = new FormData();
ItemData.append("item[name]", this.state.name);
ItemData.append("item[price]", this.state.price);
};
update(field) {
return (e) => {
this.setState({ [field]: e.target.value });
};
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<label>
<div>Item</div>
<input
type="text"
value={this.state.name}
onChange={this.update("name")}
/>
</label>
<label>
<div>Price</div>
<input
type="number"
value={this.state.price}
onChange={this.update("price")}
/>
</label>
<div>
<input type="submit" value="Submit" />
</div>
</div>
</form>
</div>
);
}
}
export default withRouter(ItemForm);
Other suggestions
Your redux wrapper component doesn't have any big effects on this, so you can remove it from this question for clarity 😀
This is actually one of the benefits of redux, the connected component (ItemForm) is a regular React component and does not have any knowledge that it will be acted on by redux
Hope that helps! 👍

Error: Functions are not valid as a React child

I'm aware that a duplicate question exists to this one, but that didn't prove of any help to me. Here's my index.js:
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
class App extends React.Component {
constructor(props) {
super(props);
this.formSet = this.formSet.bind(this);
this.state = { editing: false, ename: "Edit", sname: "Save" };
}
formNormal() {
return (
<div>
<h4>{this.state.ename}</h4>
<p>Hello there!</p>
<button onClick={!this.formSet}>{this.props.etext}</button>
</div>
);
}
formEdit() {
return (
<div>
<h4>{this.state.sname}</h4>
<input type="textarea" defaultValue="Hello there!" />
<button onClick={this.formNormal}>{this.props.stext}</button>
</div>
);
}
formSet() {
return (this.setState({ editing: true }));
}
render() {
if (this.state.editing) {
return (this.formEdit);
}
else {
return (this.formNormal);
}
}
}
render(<App etext="Edit" stext="Save" />, document.getElementById('root'));
and here's the error:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
in App
I am kind of new to React.
Couple of mistakes:
Switch
return (this.formEdit)
to
return (this.formEdit())
and return (this.formEdit); to return (this.formEdit());.
this.formEdit is a function, what you want to render in page is what the function returns i.e. by executing this.formEdit function by this.formEdit(). You cannot render a function, but can render what a function returns i.e. a valid JSX.
Bindings are not used correctly. So i just added the whole example which will work. Have a look.
class App extends React.Component {
constructor(props) {
super(props);
this.formSet = this.formSet.bind(this);
this.formNormal = this.formNormal.bind(this);
this.formEdit = this.formEdit.bind(this);
this.state = { editing: false, evalue: 'Hello There!', ename: this.props.eText, sname: this.props.sname };
}
formNormal() {
return (
<div>
<h4>{this.state.ename}</h4>
<p>{this.state.evalue}</p>
<button onClick={this.formSet}>{this.props.etext}</button>
</div>
);
}
handleChange = (e) => {
this.setState({
evalue: e.target.value,
});
}
formEdit() {
return (
<div>
<h4>{this.state.sname}</h4>
<input type="textarea" value={this.state.evalue} onChange={this.handleChange} />
<button onClick={this.formSet}>{this.props.stext}</button>
</div>
);
}
formSet() {
this.setState({ editing: !this.state.editing });
}
render() {
if (this.state.editing) {
return (this.formEdit());
}
else {
return (this.formNormal());
}
}
}
ReactDOM.render(
<App etext="Edit" stext="Save" />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>

Clear Textfield material-ui ReactJS

I have two text fields and a button using Material-UI, what I want to achieve is to clear the contents of the text fields when I click the button but I don't know how to do it, I'm new to React-JS.
This is the code I have:
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
export default class CreateLinksave extends React.Component {
render() {
return (
<div clssName="container">
<div>
<TextField floatingLabelText="Receipt Desc" />
</div>
<div>
<TextField floatingLabelText="Triggers Required" />
</div>
<RaisedButton label="Clear" />
</div>
);
}
};
Can someone please help me on this?
the text should be handled by the state
therefore you must only edit the state of the component so that your changes are shown
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
export default class CreateLinksave extends React.Component {
constructor(props){
super(props);
// initial state
this.state = this.getDefaultState();
}
getDefaultState = () => {
return { text1: '', text2: '' };
}
clear = () => {
// return the initial state
this.setState(this.getDefaultState())
}
render() {
return (
<div className="container">
<div>
<TextField
value={this.state.text1}
onChange={(e)=>{this.setState({text1: e.target.value})}}
floatingLabelText="Receipt Desc"
/>
</div>
<div>
<TextField
onChange={(e)=>{this.setState({text2: e.target.value})}}
value={this.state.text2}
floatingLabelText="Triggers Required"
/>
</div>
// use the clear function
<RaisedButton label="Clear" onClick={this.clear}/>
</div>
);
}
}
If anyone has the same issue with the functional components in React, then you have to handle the value of the Textfield component with a state.
Doesn't matter whether you use Formik library or not.
Simple control the value property of the text field using a state variable.
import React from 'react';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const sampleTextControl = () => {
const [value, setValue] = useState(''); //Initial value should be empty
const handleSubmit = (e)=> {
alert('The value: ' + value);
setValue(''); //To reset the textfield value
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<Textfield id="standard-basic" value={value} onChange={(e)=>setValue(e.target.value)}/>
<Button variant="contained" type="submit" value="Submit">
Submit
</Button>
</form >
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<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>
If you don't want to manage state for every text field then you should use refs:
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
export default class CreateLinksave extends React.Component {
constructor(props) {
super(props);
this.receiptRef = React.createRef('');
this.triggersRef = React.createRef('');
}
handleClick = () => {
this.receiptRef.current.value = null;
this.triggersRef.current.value = null;
}
render() {
return (
<div clssName="container">
<div>
<TextField floatingLabelText="Receipt Desc" />
</div>
<div>
<TextField floatingLabelText="Triggers Required" />
</div>
<RaisedButton label="Clear" onClick={this.handleClick}/>
</div>
);
}
};

Rendering A List in React

I am creating a blog using React and Firebase. I have a component called Blogger that creates blog posts and then saves them in firebase. Now, I am trying to render a list of all of the blog posts that have been saved into firebase. I can't seem to get a list to render.
So far, I have created a parent component called Blogger and a child component called List. I want List to render a list of blog post titles within the Blogger component. I've passed the prop title to List like this
What am I doing wrong?
My stack is webpack + React + React Router + Flux + Firebase
I get this error:
The error message I recieve
This is my parent object in which the list is created:
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import List from './List.jsx'
import Firebase from 'firebase'
const rootURL = 'https://incandescent-fire-6143.firebaseio.com/';
export default class Blogger extends React.Component {
constructor(props) {
super(props);
this.firebaseRef = new Firebase(rootURL + 'items/');
this.state = {
title: '',
text: ''
};
this.firebaseRef.on('value', function(snapshot) {
console.log(snapshot.val());
});
}
handleInputChange = () => {
this.setState({
title: this.refs.title.value,
text: this.refs.text.value});
}
handleClick = () => {
this.firebaseRef.push({
title: this.state.title,
text: this.state.text,
done: false
})
this.setState({title: '',
text: ''
});
}
render() {
return (
<div>
<div className="row panel panel-default">
<div className="col-md-8 col-md-offset-2">
<h2>
Create a New Blog Post
</h2>
</div>
</div>
<h2>Blog Title</h2>
<div className="input-group">
<input
ref="title"
value={this.state.title}
onChange = {this.handleInputChange}
type="text"
className="form-control"/>
<span className="input-group-btn">
</span>
</div>
<h2>Blog Entry</h2>
<div className="input-group">
<textarea
ref="text"
value={this.state.text}
onChange = {this.handleInputChange}
type="text"
className="form-control"/>
</div>
<div className="blog-submit input-group-btn">
<button onClick={this.handleClick}
className="btn btn-default" type="button">
Publish Blog Post
</button>
</div>
<List title={this.state.title} />
</div>
);
}
}
This is the child object to which I want to pass the props:
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import Blogger from './Blogger'
export default class List extends React.Component {
constructor(props) {
super(props);
console.log(Object.keys(this.props.title));
}
render: () => {
return (
if(this.props.title && Object.keys(this.props.title).length === 0) {
return <h4>enter a blog entry to get started</h4>
} else {
var children = [];
for(var key in this.props.title) {
children.push(
<li>
{title.text}
</li>
)
}
}
);
}
}
The error in your screenshot is quite clear. It's a syntax error.
The following is not legal JavaScript:
function foo () {
return ( if (true) return 'hello )
}
Nesting return statements like this will crash.
The pattern you are looking for is more like this:
function foo () {
if (cond) {
return <List />
}
return <SomethingElse />
}
Additionally the way you are writing render is incorrect. Class functions should just be:
render() {
// return stuff
}
Finally your render method should something like this:
render() {
if (this.props.title && Object.keys(this.props.title).length === 0) {
return <h4>enter a blog entry to get started</h4>
}
return Object.keys(this.props.title).map(key =>
<li>{this.props.title[key]}</li>
)
}
Here is the solution
Blogger.jsx
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import List from './List.jsx'
import Firebase from 'firebase'
const rootURL = 'https://incandescent-fire-6143.firebaseio.com/';
export default class Blogger extends React.Component {
constructor(props) {
super(props);
this.firebaseRef = new Firebase(rootURL + 'items/');
this.state = {
title: '',
text: ''
};
this.firebaseRef.on('value', function(snapshot) {
console.log(snapshot.val());
});
}
handleInputChange = () => {
this.setState({
title: this.refs.title.value,
text: this.refs.text.value});
}
handleClick = () => {
this.firebaseRef.push({
title: this.state.title,
text: this.state.text,
done: false
})
this.setState({title: '',
text: ''
});
}
render() {
return (
<div>
<div className="row panel panel-default">
<div className="col-md-8 col-md-offset-2">
<h2>
Create a New Blog Post
</h2>
</div>
</div>
<h2>Blog Title</h2>
<div className="input-group">
<input
ref="title"
value={this.state.title}
onChange = {this.handleInputChange}
type="text"
className="form-control"/>
<span className="input-group-btn">
</span>
</div>
<h2>Blog Entry</h2>
<div className="input-group">
<textarea
ref="text"
value={this.state.text}
onChange = {this.handleInputChange}
type="text"
className="form-control"/>
</div>
<div className="blog-submit input-group-btn">
<button onClick={this.handleClick}
className="btn btn-default" type="button">
Publish Blog Post
</button>
</div>
<List title={this.state.title} />
</div>
);
}
}
List.jsx
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import Blogger from './Blogger'
export default class List extends React.Component {
constructor(props) {
super(props);
// console.log(this.props.blog);
}
render() {
console.log(this.props.blog)
// Object.keys(this.props.title[key]).map(key) =>
// <li>{this.props.title}</li>
// )
return (
<div>
<li>{this.props.blog.title}</li>
</div>
)
}
}

Categories