I would like to have an Add input button that, when clicked, will add a new Input component. The following is the React.js code that I thought is one way of implementing the logic that I want, but unfortunately it's doesn't work.
The exception that I got is:
invariant.js:39 Uncaught Invariant Violation: Objects are not valid as
a React child (found: object with keys {input}). If you meant to
render a collection of children, use an array instead or wrap the
object using createFragment(object) from the React add-ons. Check the
render method of FieldMappingAddForm.
How do I solve this problem?
import React from 'react';
import ReactDOM from "react-dom";
class Input extends React.Component {
render() {
return (
<input placeholder="Your input here" />
);
}
}
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {inputList: []};
this.onAddBtnClick = this.onAddBtnClick.bind(this);
}
onAddBtnClick(event) {
const inputList = this.state.inputList;
this.setState({
inputList: inputList.concat(<Input key={inputList.length} />)
});
}
render() {
return (
<div>
<button onClick={this.onAddBtnClick}>Add input</button>
{this.state.inputList.map(function(input, index) {
return {input}
})}
</div>
);
}
}
ReactDOM.render(
<Form />,
document.getElementById("form")
);
React Hook Version
Click here for live example
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Input = () => {
return <input placeholder="Your input here" />;
};
const Form = () => {
const [inputList, setInputList] = useState([]);
const onAddBtnClick = event => {
setInputList(inputList.concat(<Input key={inputList.length} />));
};
return (
<div>
<button onClick={onAddBtnClick}>Add input</button>
{inputList}
</div>
);
};
ReactDOM.render(<Form />, document.getElementById("form"));
Remove {}., it is not necessary using it in this case
{this.state.inputList.map(function(input, index) {
return input;
})}
Example
or better in this case avoid .map and just use {this.state.inputList},
Example
const [visible,setVisible] = useState(false);
return(
<>
<button onClick={()=>setVisible(true)}>Hit me to add new div</button>
{
visible ? (
<div id='addThisContainer'>
{/* Your code inside */}
</div>
) : null
}
</>
)
Related
I have the following example where the toggleComponent.js is working perfectly.
The problem here is that I don't want to render the <ContentComponent/> inside the toggle, rather I want the opposite, I want to toggle the <ContentComponent/> that will be called in another component depending on the state of the toggle.
So the <ContentComponent/> is outside the toggleComponent.js, but they are linked together. So I can display it externally using the toggle.
An image to give you an idea:
Link to funtional code:
https://stackblitz.com/edit/react-fwn3rn?file=src/App.js
import React, { Component } from "react";
import ToggleComponent from "./toggleComponent";
import ContentComponent from "./content";
export default class App extends React.Component {
render() {
return (
<div>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && <h1>test</h1>}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && (
<h1>
<ContentComponent />
</h1>
)}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
</div>
);
}
}
Bit tweaked your source.
Modified ToggleComponent
import React from "react";
export default class ToggleComponent extends React.Component {
constructor() {
super();
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
this.setState({ checked: !this.state.checked });
this.props.toggled(!this.state.checked);
};
checkbox = () => {
return (
<div>
<label>Toggle</label>
<span className="switch switch-sm">
<label>
<input type="checkbox" name="select" onClick={this.handleClick} />
<span />
</label>
</span>
</div>
);
};
render() {
return this.checkbox();
}
}
Added OtherComponent with ContentComponent inside.
import React, { Component } from "react";
import ContentComponent from "./content";
export default class OtherComponent extends React.Component {
render() {
return <div>{this.props.show ? <ContentComponent /> : null}</div>;
}
}
Separated as per your requirement.
Modified App
import React, { Component, PropTypes } from "react";
import ToggleComponent from "./toggleComponent";
import OtherComponent from "./otherComponent";
export default class App extends React.Component {
constructor() {
super();
this.toggled = this.toggled.bind(this);
this.state = { show: false };
}
toggled(value) {
this.setState({ show: value });
}
render() {
return (
<div>
<ToggleComponent toggled={this.toggled} />
<OtherComponent show={this.state.show} />
</div>
);
}
}
Working demo at StackBlitz.
If you want to share states across components a good way to do that is to use callbacks and states. I will use below some functional components but the same principle can be applied with class based components and their setState function.
You can see this example running here, I've tried to reproduce a bit what you showed in your question.
import React, { useState, useEffect, useCallback } from "react";
import "./style.css";
const ToggleComponent = props => {
const { label: labelText, checked, onClick } = props;
return (
<label>
<input type="checkbox" checked={checked} onClick={onClick} />
{labelText}
</label>
);
};
const ContentComponent = props => {
const { label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Holder = () => {
return (
<div>
<ContentComponent label="First">
{checked => (
<h1>First content ({checked ? "checked" : "unchecked"})</h1>
)}
</ContentComponent>
<ContentComponent
label="Second"
render={checked => (checked ? <h1>Second content</h1> : null)}
/>
</div>
);
};
PS: A good rule of thumb concerning state management is to try to avoid bi-directional state handling. For instance here in my example I don't use an internal state in ToggleComponent because it would require to update it if given checked property has changed. If you want to have this kind of shared state changes then you need to use useEffect on functional component.
const ContentComponent = props => {
const { checked: checkedFromProps, label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(checkedFromProps || false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
// onChange callback
useEffect(() => {
if (onChange) {
onChange(checked);
}
}, [ checked, onChange ]);
// update from props
useEffect(() => {
setChecked(checkedFromProps);
}, [ checkedFromProps, setChecked ]);
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Other = () => {
const [ checked, setChecked ] = useState(true);
return (
<div>
{ checked ? "Checked" : "Unchecked" }
<ContentComponent checked={checked} onChange={setChecked} />
</div>
);
};
i am sorry for the very basic question as i m new to react js. i have created an array in a react component and render it through map function in one component and i want to change (add/Subject) in the array from an other component on basis of _id. the following is a sample that helps you better to understand what i actually want. Thanks in advance Sir
{*Array Component*}
const ArrayData =[
{
_id:1,
title:"All Searches"
},
{
_id:5,
title:1
},
{
_id:6,
title:"4"
}
]
export default ArrayData;
{*2nd Component*}
import react from "react"
import ArrayData from ArrayComponent
class Parent extends React.Component {
constructor() {
super();
this.state = {
ArrayData:ArrayData,
collapsed: false,
}
}
render() {
const { ArrayData } = this.state;
return (
<>
<FirstChild Data={ArrayData} />
<SecondChild />
</>
);
}
}
export default Parent;
!------------------------------------------!
{*FirstChild*}
class FirstChild extends React.Component {
constructor(props){
super();
this.state={
ArrayData:props.ArrayData
}
}
render() {
const { ArrayData} = this.state;
const renderArray = ArrayData.slice(0, 5).map(Object => {
return <h1>{object._id} </h1>
})
return (
<>
{renderArray}
</>
);
}
}
export default FirstChild;
!-----------------------------------------!
{*SecondChild*}
import { React } from "react";
const SecondChild = () => {
const handleUpdate=(_id, Title) =>{
{*function that can add the inputs as a object into that arrayComponent*}
}
const handleDelete=(_id) =>{
{*function that can delete a object from that arrayComponent having the id given by User in the feild*}
}
return (
<>
<input type='text' name='_id' placeHolder="Which object you want to delete" />
<button type=Submit onClick={handleDelete} >Delete</button>
<br></br>
<input type='text' name='_id' />
<input type='text' name='title' />
<button type=Submit onClick={handleUpdate} >Update</button>
</>
);
}
export default SecondChild;
CODESANDBOX
All you need to do is declare handleDelete and handleUpdate on the Parent component and pass it as a props in SecondChild component. If we place state and its methods in Parent component, then it is easy to track, debug, and easy to maintain. It would be easy to pass the methods if we define another component, let say ThirdComponent and it also contains functionality to perform CRUD operation on the ArrayData array.
In the FirstChild component, you were destructuring the ArrayData const { ArrayData} = this.state; and using it in the render method. It won't update we receive new props because you are rendering the array state that gets created once(as a constructor will get called once) and we want the latest value of ArrayData from the parent component. We can use props directly in the render method. You need to see the react lifecycle methods.
Parent.js
import React from "react";
import ArrayData from "./ArrayComponent";
import FirstChild from "./FirstChild";
import SecondChild from "./SecondChild";
class Parent extends React.Component {
constructor() {
super();
this.state = {
ArrayData: ArrayData,
collapsed: false
};
this.handleDelete = this.handleDelete.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
handleDelete(id) {
const idToDelete = parseInt(id, 10);
this.setState((state) => {
const filteredArrayData = state.ArrayData.filter(
(el) => el._id !== idToDelete
);
return {
ArrayData: filteredArrayData
};
});
}
handleUpdate(newObj) {
console.log(newObj);
this.setState((state) => ({
ArrayData: [...state.ArrayData, newObj]
}));
}
render() {
return (
<>
<FirstChild Data={this.state.ArrayData} />
<SecondChild
handleUpdate={this.handleUpdate}
handleDelete={this.handleDelete}
/>
</>
);
}
}
export default Parent;
FirstChild.js
import React from "react";
class FirstChild extends React.Component {
render() {
return (
<>
{this.props.Data.slice(0, 5).map((el) => {
return <h1 key={el._id}>{el._id}</h1>;
})}
</>
);
}
}
export default FirstChild;
SecondChild.js
import React, { useState } from "react";
const SecondChild = ({ handleUpdate, handleDelete }) => {
const [idToDelete, setIdToDelete] = useState(null);
const [newID, setNewID] = useState(null);
const [newTitle, setNewTitle] = useState("");
return (
<>
<input
name="_id"
type="number"
onChange={(e) => setIdToDelete(e.target.value)}
placeholder="Which object you want to delete"
/>
<button type="submit" onClick={() => handleDelete(idToDelete)}>
Delete
</button>
<br></br>
<br></br>
<br></br>
<input
type="text"
name="_id"
placeholder="id"
onChange={(e) => setNewID(e.target.value)}
/>
<input
type="text"
name="title"
placeholder="title"
onChange={(e) => setNewTitle(e.target.value)}
/>
<button
type="submit"
onClick={() => handleUpdate({ _id: newID, title: newTitle })}
>
Update
</button>
</>
);
};
export default SecondChild;
I am new to React. I am working on a piece of code. I am trying to make a ToDoList kind of thing. So i have created 3 different component , one for taking input and one for displaying the entered code along with App.jsx.
Here are the code for each.
App.jsx
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [arr, setArr] = useState([]);
function getData(note) {
// setArr(previous => [...previous, { title: newTitle, content: newContent }]);
setArr(previous => {return [...previous, note];});
console.log(arr);
}
return (
<div>
<Header />
<CreateArea reply={getData} />
{arr.map((arry, index) => (
<Note
key={index}
id={index}
title={arry.title}
content={arry.content}
/>
))}
<Footer />
</div>
);
}
export default App;
CreateArea.jsx
import React, { useState } from "react";
function CreateArea(props) {
const [note, setNote] = useState({
title: "",
content: ""
});
function handleChange(event) {
const { name, value } = event.target;
setNote(prevNote => {
return {
...prevNote,
[name]: value
};
});
}
function submitNote(event) {
// const newTitle = note.title;
// const newContent = note.content;
// props.reply(newTitle, newContent);
props.reply(note);
setNote({
title: "",
content: ""
});
event.preventDefault();
}
return (
<div>
<form>
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
/>
<textarea
name="content"
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
rows="3"
/>
<button onClick={submitNote}>Add</button>
</form>
</div>
);
}
export default CreateArea;
Note.jsx
import React from "react";
function Note(props) {
return (
<div className="note">
<h1>{props.title}</h1>
<p>{props.content}</p>
<button>DELETE</button>
</div>
);
}
export default Note;
Now somehow i am getting the result right and the new post are being added as required but when I do a console.log for the array which stores inside getData() in App.jsx ,I observe there is a delayy of one click before the array is being added.
Sample problem
In the images attached.I have added new post with random data but when i see the console log the array is still empty. When i click the Add button for the second time , only then after second click on adding new post is the data being shown. I can't seem to figure out the reasoning behind it. Am I doing something wrong or missing something ?
Your setArr() from useState() is an asynchronous function. Hence, the console.log(arr) is called before the state is actually updated.
However, you can log arr in the useEffect() hook which is called on every state change, like this:
useEffect(() => {console.log(arr)})
You can find more information about this hook at https://reactjs.org/docs/hooks-effect.html.
This is delay is because useState mutation is asynchronous, you cannot get updated result in one tick.
try console.log result in useEffect hook like below:
function App() {
const [arr, setArr] = useState([]);
function getData(note) {
// setArr(previous => [...previous, { title: newTitle, content: newContent }]);
setArr(previous => {
return [...previous, note];
});
}
useEffect(() => {
console.log(arr);
});
return (
<div>
<CreateArea reply={getData} />
{arr.map((arry, index) => (
<Note
key={index}
id={index}
title={arry.title}
content={arry.content}
/>
))}
</div>
);
}
more info: hooks-reference.html#useeffect
i have a react component named "List" that renders smaller components "Post" using a button through method "Addpost()" that takes 2 props from the input form. I have saved the input in 2 varables but i don't know how to pass these props to the Addpost() method inside the return of List's render().
//=========== List component ==============
class List extends React.Component{
renderPost(title,content){
return(
<Post titolo={title} contenuto={content}/>
);
}
renderPost just render the Post component in a in the HTML
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" id="inputTitle"/><br></br>
Content:<br></br>
<input type="text" id="inputContent"/>
</form><br></br>
<button className="square"
how can i make this work? title and content are not defined
onClick={() =>
this.addPost(title,content)
Add Post!
</button>
</div>
);
}
}
//=========== Post component ==============
class Post extends React.Component {
render() {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{this.props.titolo}
</span><br></br>
<span>{this.props.contenuto}</span>
</li>
);
}
}
Basically, whenever you're dealing with forms and inputs, you would use refs.
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import PostList from './components/PostList'
import AddPostForm from './components/AddPostForm'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
posts: [] //state is handled here
}
this.addPost = this.addPost.bind(this)
}
addPost(title, content) {
let newPost = { title, content }
this.setState(({ posts }) => { return { posts: [...posts, newPost] } } )
}
render() {
const { posts } = this.state
return (
<div>
<AddPostForm onNewPost={this.addPost} /> //we pass addPost to the component
<br />
<PostList posts={posts} />
</div>
);
}
}
export default App;
Post.js
import React from 'react';
function Post({titolo, contenuto}) {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{titolo}
</span><br></br>
<span>{contenuto}</span>
</li>
);
}
export default Post
AddPostForm.js
import React from 'react';
const addPostForm = ({onNewPost = f => f}) => { //onNewPost method is passed by props from the parent
let _titleInput, _contentInput //these are our refs, see the docs for more information
const submit = (e) => {
e.preventDefault()
onNewPost(_titleInput.value, _contentInput.value) //here we call the addPost function that was passed to the component
_titleInput.value = '' //empty the inputs
_contentInput.value = ''
_titleInput.focus() //set focus
}
return (
<form onSubmit={submit}>
Title:<br></br>
<input type="text" ref={title => _titleInput = title} /><br></br>{/* Note the ref attribute */}
Content:<br></br>
<input type="text" ref={content => _contentInput = content} />
<button className="square">Add a new post</button>
</form>
)
}
export default addPostForm
PostList.js
import React from 'react';
import Post from './Post';
const PostList = ({ posts=[] }) => {
return (
<div className="post-list">
{
posts.map((post, index) =>
<Post key={index} titolo={post.title} contenuto={post.content} />
)
}
</div>
)
}
export default PostList
And the result:
edit
renderPost just render the Post component in a in the HTML
state = { inputTitle: '', inputContent: '' }
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post
component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" value={this.inputTitle} onChnage={event => setState({ inputTitle: event.target.value }) }><br></br>
Content:<br></br>
<input type="text" value={this.inputContent} onChnage={event => setState({ inputContent: event.target.value }) } />
</form><br></br>
<button className="square"
on click function
onClick={() =>
this.addPost(this.inputTitle,this.inputContent)
Add Post!
</button>
</div>
);
}
}
I'm working on a simple list maker, to do list app using create-react-app and I'm having some trouble puzzling out the functionality. What I'm trying to accomplish with this app:
I want to be able to enter text into an input, push the button or press enter, and whatever text will be listed on the body of the app.
I want to be able to create a button that will delete the list items once the task or objective is complete
My code is broken up into these components so far:
App,
ListInput,
ItemList,
Item
The code for App is
import React, { Component } from 'react';
import './App.css';
import Navigation from './components/Navigation';
import ListInput from './components/ListInput';
import ListName from './components/ListName';
import Item from './components/Item';
import ItemList from './components/ItemList';
class App extends Component {
constructor() {
super();
this.state = {
input: '',
items: []
};
}
addItem = () => {
this.setState(state => {
let inputValue = this.input.current.value;
if (inputValue !== '') {
this.setState({
items: [this.state.items, inputValue]
})
}
})
}
onButtonEnter = () => {
this.addItem();
}
render() {
return (
<div className="App">
<Navigation />
<ListName />
<ListInput addItem={this.addItem}
onButtonEnter={this.onButtonEnter} />
<Item />
<ItemList />
</div>
);
}
}
export default App;
The code for ListInput is :
import React from 'react';
import './ListInput.css';
const ListInput = ({ addItem, onButtonEnter }) => {
return (
<div>
<p className='center f2'>
{'Enter List Item'}
</p>
<div className='center'>
<div className='center f3 br-6 shadow-5 pa3 '>
<input type='text'
className='f4 pa2 w-70 center'
placeholder='Enter Here'
/>
<button className='w-30 grow f4 link ph3 pv2 dib white bg-black'
onClick={onButtonEnter}
onSubmit={addItem} >
{'Enter'}
</button>
</div>
</div>
</div>
);
}
export default ListInput;
The code for Item is:
import React from 'react';
const Item = ({text}) =>{
return (
<div>
<ul>{text}</ul>
</div>
)}
export default Item;
And the code for ItemList is :
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{item.map(items => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
In my react app I am returning an error of 'item' is not defined and I'm confused why.
In your App.js you need to pass items as a prop to ItemList component like
<ItemList items={this.state.items} />
Also in addItem function pushing inputValue to items array isn’t correct do something like below
addItem = () => {
this.setState(state => {
let inputValue = this.input.current.value;
if (inputValue !== '') {
this.setState(prevState => ({
items: [...prevState.items, inputValue]
}));
}
})
}
And in ItemList.js do conditional check before doing .map also some typo errors in .map
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{items && items.map(item => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
Try with above changes This would work
Please excuse me if there are any typo errors because I am answering from my mobile
Your ItemList was not correct. Take a look at corrected snippet below you need to map on items and not item (hence the error item is not defined). Also, you need to items as a prop to ItemList in your app.js
import React from 'react';
import Item from './Item';
const ItemList = ({ items }) => {
return (
<div>
{items.map(item => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
export default ItemList;
In app.js add following line. Also, I don't see what is doing in your app.js remove it.
<ItemList items={this.state.items}/>
Seems like you have a typo in ItemList.
It receives items (plural) as prop but you are using item.
const ItemList = ({ items }) => {
return (
<div>
{items.map(items => <Item key={item.id}
text={item.text} />
)}
</div>
)
}
And don't forget to actually pass the items prop to 'ItemList':
<ItemList items={this.state.items} />