I need to call a Component (ExampleComp), and when the button is clicked, call againthe component (ExampleComp). The idea is to call the Component(ExampleComp) as many times as you press the button.
function newComponent{
<ExampleComp/>
}
------
return(
<div>
<ExampleComp/>
<Button className="btnNew" onClick=
{newComponent}> Create a new Component</Button>
</div>
)
Actually i don't know how to do it exactly and i would apreciate your help.
You can use the state for this purpose. Let's say your state is something like this:
this.state = { items: [] };
You can render all the items like the following example:
return (
<div>
{this.state.items.map(item => {
return <ExampleComp exampleProp={item.exampleProp} />;
})}
<Button className="btnNew" onClick={newComponent}>
Create a new Component
</Button>
</div>
);
And finally, you can push an item into the state, and React will take care of the rest.
function newComponent{
newItem = { exampleProp: 'Something?' };
this.setState((state, props) => ({ items: [...items, newItem] }));
}
This will do the job. I just used "exampleProp" to be an example but you don't have to use it. Actually, the state can be just a number too. The important part is using state in every user interface change.
render(){
return (
<Button className="btnNew" onClick={ this.setState({ clicked:true }) }>Create a new Component</Button>
{
this.state.clicked ? {newComponent} : null
}
)
}
This would help but though not recommended by me as setState will re-render(load) the component again onClick.
Related
I am trying to build a ToDoList app and I have two components. I have a main component that handles the state and another button component that renders a delete button next to every task that I render. The problem I have is that i cant seem to connect the delete button to the index of the array and delete that specific item in the array by clicking on the button next to it.
I have tried to connect the index by using the map key id to the delete function.
just need help with how my delete function should look like and how its going to get the index of the item that is next to it and delete it.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
toDoList : []
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.delete = this.delete.bind(this);
}
handleSubmit() {
const itemsArray = this.state.userInput.split(',');
this.setState({
toDoList: this.state.toDoList.concat(itemsArray),
userInput: ''
});
}
handleChange(e) {
this.setState({
userInput: e.target.value
});
}
delete(id) {
this.setState({
toDoList: this.state.toDoList.filter( (item) => id !== item.id )
})
}
render() {
return (
<div>
<textarea
onChange={this.handleChange}
value={this.state.userInput}
placeholder="Separate Items With Commas" /><br />
<button onClick={this.handleSubmit}>Create List</button>
<h1>My Daily To Do List:</h1>
<Button toDoList={this.state.toDoList} handleDelete={this.delete} />
</div>
);
}
};
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item.id}>{item.text} <button onClick={this.props.delete(item.id)}>Done!</button></li> )
}
</ul>
);
}
};
I reviewed your edited code and made a couple of changes.
I don’t get what exactly you want to achieve with you handleSubmit method but items it adds to the list are simple strings and don’t have neither ‘id’ nor ‘text’ properties you’re referring to in other places. Possibly you’re going to change this later but while your to do items are just strings I’ve edited your code so that it work properly under this condition.
Edited delete method now accepts not item.id as a parameter but the whole item object. Yet I'm using functional form of setState as it was correctly suggested by #Hamoghamdi
delete(itemToDelete) {
this.setState(state => ({
toDoList: state.toDoList.filter( (item) => itemToDelete !== item)
}))
}
Edited render method of Button class now displays items as text and properly bind delete handler...
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item}>
{item}
<button onClick={() => this.props.handleDelete(item)}>Done!</button>
</li> )
}
</ul>
);
}
BTW Button is a bad naming for the component that isn’t exactly a button. Yet it’s better to implement it as a functional component. Use class components only if the component has its own state.
you should try using an anonymous function with setState() instead of returning an object literal directly, specially when you want to do something affected by the previous or current state
using this.state inside of setState() won't give you any good results.
here, try this:
delete = (id) => {
this.setState((prevState) => {
return { toDoList: prevState.filter( (task) => id !== task.id )}
});
You need to bind the method in constructor for example:
constructor(props) {
//...
this.handleDelete = this.handleDelete.bind(this)
}
also you can find another ways how to bind methods
In terms of handling the deleting the items, you can use
handleDelete(index) {
// Use the splice array function: splice(index, deleteCount)
this.todoList.splice(index, 1);
}
And that is all that easy
import React, { Component } from 'react';
import axios from 'axios';
import { Button, Card } from 'semantic-ui-react';
class Games extends Component {
state = { games:[], showGames: false }
componentDidMount() {
axios.get('/api/board_games')
.then(res => {
this.setState({games: res.data});
})
}
toggleGames = () => {
this.setState({ showGames: !this.state.showGames })
}
gamesList = () => {
const {games} = this.state
return games.map( game =>
<Card key={game.id}>
<Card.Content>
<Card.Header>{game.title}</Card.Header>
<Card.Description>Players: {game.min_players} - {game.max_players}</Card.Description>
<Card.Description>Company: {game.company}</Card.Description>
<Card.Description>Time Needed: {game.time_needed}</Card.Description>
</Card.Content>
<Card.Content extra>
<Button basic color='green'>
Add to Library
</Button>
</Card.Content>
</Card>
)
}
render() {
const showGames = this.state
return (
<div>
<h1>Games</h1>
<h3>Your Games</h3>
{ showGames ? (
<div>
<Card.Group itemsPerRow={4}>{this.gamesList()}</Card.Group>
</div>
)
: (
<button onClick={this.toggleGames()}>Add a Game</button>
)
}
</div>
)
}
}
export default Games;
In my mind, the render return should be checking if showGames is true or false. It's defaulted to false in the state at the beginning. For that reason, it should render the "add a game" button. But if you click that button, it should toggle the showGames to true and render the game cards. Instead, it automatically renders the cards when I arrive on the page. I would also like to add Done Adding to the first part of the if/else, but when I do that I get " Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."
the way you're setting the onClick event is causing it to be constantly called. you should format it either like this:
onClick={this.toggleGames}
or like this:
onClick={() => this.toggleGames()}
Just edit the following line in your render() :
const showGames = this.state.showGames;
or
const { showGames } = this.state;
At the moment your showGames constant is an object instead of a boolean. Hence you are not able to use it conditionally.
Hope this helps!
you have set wrong onClick action from the button
// the onClick is executing in every render.
<button onClick={this.toggleGames()}>Add a Game</button>
// right way
button onClick={() => this.toggleGames()}>Add a Game</button>
i'm creating a simple react to do list, I'm currently working on a delete button, I have created an array then passed this array into a prop, I then need to splice that item from the prop array when the user clicks the delete button. I was able to store the array number but I cant seem to update the array after its deleted.
CLASS CALL:
<TodoList items={this.state.items} deleteItems={this.deleteItem}/>
SUB-CLASS CODE:
class TodoList extends Component {
constructor(props) {
super(props);
this.removeItem = this.removeItem.bind(this);
}
render() {
return (
<div>
{ this.props.items.map((item, i) => (
<div className={"col-12"} key={item.id}>
<div className={"card text-white"}>
<div className={item.priority}>
<div className={"col-12 card-body"}>
<h1>{item.title}</h1>
<p>{item.text}</p>
<button onClick={() => { this.removeItem(item, i)}} key={i} className={"col-12 btn btn-primary bg-red"}>Delete</button>
</div>
<div/>
</div>
</div>
</div>
))}
</div>
);
}
removeItem(e, i) {
this.props.items.splice(i, '');
console.log(i);
}
}
I have been looking at different stack questions but none of the solutions seem to apply to this, thanks for any constructive feedback :)
I believe <TodoList /> component should have its own state. However, if you can't do so, there's 2 solutions to this problem:
Keep <ToDoList /> component's state and props in sync (In case the parent component modifies the state passed down as items). Then modify the <TodoList /> 's state.
Declare a method that removes the item inside the parent component which has the
state, and pass it down as props (Recommended)
Example code:
class ParentComponent extends Component {
state = {
items: [1, 2, 3]
}
removeItem = index => () => {
this.setState(prevState => ({
items: prevState.items.filter((_, i) => i !== index) //Filter the items
}));
};
render() {
return (
<TodoList items={this.state.items} deleteItems={this.removeItem} />
);
}
}
Important: Always use pure functions to modify the state. Do not use .splice() or .push() (If you haven't cloned the state yet). It's always safer to use .filter(), .map(), .concat(), etc.
I'm trying to render dynamically a collection of component using componentDidUpdate.
This is my scenario:
var index = 0;
class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
componentList: [<ComponentToRender key={index} id={index} />]
};
this.addPeriodHandler = this.addPeriodHandler.bind(this);
}
componentDidUpdate = () => {
var container = document.getElementById("container");
this.state.componentList.length !== 0
? ReactDOM.render(this.state.componentList, container)
: ReactDOM.unmountComponentAtNode(container);
};
addHandler = () => {
var array = this.state.componentList;
index++;
array.push(<ComponentToRender key={index} id={index} />);
this.setState = {
componentList: array
};
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.addHandler}>
Add Component
</button>
<div id="container" />
</div>
);
}
}
The problem is that componentDidUpdate work only one time, but it should work every time that component's state change.
Thank you in advance.
This is not how to use react. With ReactDOM.render() you are creating an entirely new component tree. Usually you only do that once to initially render your app. Everything else will be rendered by the render() functions of your components. If you do it with ReactDOM.render() you are basically throwing away everything react has already rendered every time you update your data and recreate it from scratch when in reality you may only need to add a single node somewhere.
Also what you actually store in the component state should be plain data and not components. Then use this data to render your components in the render() function.
Example for a valid use case:
class MyComponent extends Component{
state = {
periods: []
};
handleAddPeriod = () => {
this.setState(oldState => ({
periods: [
...oldState.periods,
{/*new period data here*/}
],
});
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.handleAddPeriod}>
Add Component
</button>
<div id="container">
{periods.map((period, index) => (
<ComponentToRender id={index} key={index}>
{/* render period data here */}
</ComponentToRender>
))}
</div>
</div>
);
}
}
}
Also you should not work with global variables like you did with index. If you have data that changes during using your application this is an indicator that is should be component state.
try
addHandler = () =>{
var array = this.state.componentList.slice();
index++;
array.push(<ComponentToRender key={index} id={index}/>);
this.setState=({
componentList: array
});
}
if that works, this is an issue with the state holding an Array reference that isn't changing. When you're calling setState even though you've added to the Array, it still sees the same reference because push doesn't create a new Array. You might be able to get by using the same array if you also implement shouldComponentUpdate and check the array length of the new state in there to see if it's changed.
I tried to find out myself how to pass value of key to the reducer but without succes. My intention is to edit on button the chosen element. For now I cant catch id of element and all the elements are changing. Could somebody tell me how it works?
my code is here:
for the container:
const mapDispatchToProps = dispatch => {
return {
onEditComponent: (component, id) => dispatch({type: actionTypes.EDIT_COMPONENT, data: {componentToReducer: component, ind: id}})
}
}
and for reducer:
case actionTypes.EDIT_COMPONENT:
return {
...state,
components: state.components.map((component,i) => i === action.data.ind ?
{...component, co: action.data.componentToReducer} : component
)
};
There is also a code when I am building a structure html:
render() {
const edit = this.props.compons.map((comp, index) =>(
<div
key={comp.id}>
<EditComponent
clicked={this.props.onEditComponent}/>
</div>
));
return (
<div>
<AddComponent
click={this.props.onAddComponent}
/>
{
this.props.compons.map((component)=>(
<div key={component.id}
>
<p
onClick={this.showTrue}
className={classes.Component}>{component.co}
</p>
<button
onClick={()=>this.props.onDeleteComponent(component.id)}>
Delete component
</button>
</div>
))
}
{this.state.show ? edit : null}
</div>
)
}
You are not passing the id and the component to onEditComponent action, pass it on to the EditComponent
const edit = this.props.compons.map((comp, index) =>(
<div
key={comp.id}>
<EditComponent
comp ={comp}
clicked={this.props.onEditComponent}/>
</div>
));
and inside EditComponent when you click on edit, you would write
handleClick= () => {
this.props.clicked(this.props.comp, this.props.comp.id)
}
So, I have a solution. EditComponent must be connected with reducer at state (mapStateToProps), and then I can pass it to this reducer to dispatch action and finally edit element from list. Thank you for attention. :)