how to add object into array with hook state in react js - javascript

I'm trying to set the id property of a state object based on the id attribute of the button I clicked, and then append it into a state array and then store it in the localstorage. but there are weird things happening in my code:
when I clicked the 1st button, the console.log shows the id value of the 1st button (5) but the state object were still empty
then I clicked on the 2nd button, the console.log shows the id value of the 2nd button (12) but the state object now contains the value from the 1st button (5)
then I called the 3rd button, the console.log shows the id value of the 3rd button (100) but the state object contains the value of the 2nd button (12)
why are the Buttons still look like a regular/default button even though I already added the react-bootstrap dependency, imported the Button component, and applied the variant="link" in the Button?
here's my code (and the codesandbox)
import { useState, useEffect } from "react";
import { Button } from "react-bootstrap";
export default function App() {
const [cartList, setCartList] = useState([]);
const [cartItem, setCartItem] = useState({});
useEffect(() => {
let localCart = localStorage.getItem("cartList") || "[]";
console.log("localcart", localCart);
if (localCart) {
localCart = JSON.parse(localCart);
}
setCartList(localCart);
}, []);
const handleClick = (e, item) => {
e.preventDefault();
const arr = e.target.id.split("-");
const selectID = arr[1];
console.log("selectID", selectID);
setCartItem({ ...cartItem, id: selectID });
console.log("cartItem", cartItem);
let itemIndex = -1;
console.log("cartlist", cartList);
for (let i = 0; i < cartList.length; i++) {
if (cartItem && selectID === cartList[i].id) {
itemIndex = i;
}
}
if (itemIndex < 0) {
console.log("added to cartList on index", itemIndex, cartItem);
setCartList([...cartList, cartItem]);
localStorage.setItem("cartList", JSON.stringify(cartList));
}
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Button
variant="link"
id="item-5"
onClick={(e) => handleClick(e, cartItem)}
>
item no.5
</Button>
<Button
variant="link"
id="item-12"
onClick={(e) => handleClick(e, cartItem)}
>
item no.12
</Button>
<Button
variant="link"
id="item-100"
onClick={(e) => handleClick(e, cartItem)}
>
item no.100
</Button>
<div>
{cartList.length > 0 ? (
cartList.map((item, index) => <div key={index}>{item.id}</div>)
) : (
<div>there is no data here</div>
)}
</div>
</div>
);
}

The problems number 1, 2, 3 are all caused by the same reason: setCartItem works asynchronously so when you do this:
setCartItem({ ...cartItem, id: selectID });
console.log("cartItem", cartItem);
The value that you print in the console.log is still the old state because it doesn't wait for the setCartItem to complete the update. So, if you want to do a console.log of the new state each time it changes you can use instead a useEffect hook:
useEffect(() => {
console.log("cartItem", cartItem); // this will be executed each time the cartItem has been changed
}, [cartItem])
About the question number 4, make sure to import the bootstrap css file:
/* The following line can be included in your src/index.js or App.js file*/
import 'bootstrap/dist/css/bootstrap.min.css';
More infos here in the official site:
https://react-bootstrap.github.io/getting-started/introduction/#stylesheets

Related

Adding, deleting, and editing input values dynamically with Reactjs

I have a UserLists component where the user types into an input and adds the value onto the bottom of the screen.
The input value is added into the whitelist state. The state is then mapped and creates more inputs where the user can decide to delete said input or edit it.
I am having trouble deleting the inputs. I thought I could delete each input individually by splicing the state, but my implementation of the deleteItem deletes multiple inputs when any single one of them is clicked.
I also cannot edit any of the inputs because their value is set by my addItem function.
import React, { useEffect, useState } from "react";
export const UserLists = () => {
const [whitelist, setWhiteList] = useState([]);
const addItem = () => {
let newValue = document.getElementById("whiteList").value;
setWhiteList([...whitelist, newValue]);
};
useEffect(() => {
console.log(whitelist, "item changed");
}, [whitelist]);
const deleteItem = (index) => {
let test = whitelist.splice(index, 1);
setWhiteList(test);
console.log("index:", index);
};
const editItem = () => {};
return (
<div>
<h2>WhiteList</h2>
<input id="whiteList" type="text" />
<button onClick={addItem}>Add</button>
{whitelist.map((item, index) => {
return (
<div>
<input type="text" value={item} onChange={editItem} />
<button onClick={() => deleteItem(index)}>Delete</button>
<p>{index}</p>
</div>
);
})}
</div>
);
};
How can I revise my code to successfully individually delete and edit inputs?
My codesandbox
You need to change your editItem and deleteItem functions in order to make your edit and delete functionality work properly. Here's a code sandbox link to the solution to your problem:
https://codesandbox.io/s/whitelist-forked-y51w8
Don't do:
let test = whitelist.slice(index, 1);
setWhiteList(test);
Do this instead:
whitelist.splice(index, 1);
setWhiteList([...whitelist]);

How do I add the ability to edit text within a react component?

So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.

React JS which button is clicked how many times?

I have 2 buttons and 1 h1 I need to count how many times the button is clicked and display the button name with no. of count it clicked in h1 with the name of the button. Right now I am getting the name of button correctly but not able to display the click count for the button. please help
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
export default class App extends Component {
state={
button:"",
countClick:0
}
buttonClick=(e)=>{
e.preventDefault();
this.setState({button:e.target.name, countClick:this.state.countClick+1});
}
render() {
return (
<Fragment>
<h1>
{this.state.button} clicked {this.state.countClick} times
</h1>
<button name="Button 1" type="button" onClick={this.buttonClick}>Button 1</button>
<button name="Button 2" type="button" onClick={this.buttonClick}>Button 2</button>
</Fragment>
)
}
}
ReactDOM.render(<App />,document.getElementById('root'));
Thank You
Some notice points:
build an Array to store each button's click amount status.
optionally use {id, value} structure for the array items, since we need to display the clicked button with its click amount.
use map() to prevent from repeating yourself about <button />.
pass the identity index as a param inside the nested handler function.
Full code:
import React from "react";
import "./styles.css";
const amount = 2;
export default function App() {
const [clickList, setClickList] = React.useState(
[...Array(amount).keys()].map(x => ({ id: `Button ${x + 1}`, value: 0 }))
);
const [activeButton, setActiveButton] = React.useState("");
const buttonClick = idx => e => {
const result = [...clickList];
result[idx].value += 1;
setClickList(result);
setActiveButton(e.target.name);
};
return (
<>
<h1>
{`${activeButton} clicked ${clickList.find(x => x.id === activeButton)?.value ?? 0} times`}
</h1>
{clickList.map((x, idx) => (
<button name={x.id} type="button" onClick={buttonClick(idx)}>
{x.id}
</button>
))}
</>
);
}
setState can take an object or a callback with previous state as parameter.
Change your setState like this:
this.setState(prevState => ({button:e.target.name, countClick: prevState + 1}));

Array not dynamically updating due to mis-use of states

When a radio button is clicked it changes a value from 0 to 1 or vice versa.
This is done through 2 components Test.js and Graph_Test.js.
Test.js is where the radio buttons are created and the array is filled. E.g when radio button 1 is pressed, I want it to change the value in array[1] from 0 to 1 or 1 to 0.
So any time a radio button is pressed it will dynmaically change the array.
This array is then used to build a graph in Graph_Test.js, depending on which indexes have 1 values beside it will make corresponding lines on the graph.
For example if array = [0,1,0,0] , a line will be drawn for region 1.
So as this array dynamically changes so will the lines on the graph.
I am testing my code. In Graph_test I've it outputting array[0] and when a radio button[0] is touched this value should update. However this is not happening.
In test.js, i've used states this is where my problem is as the inital array is being dleivered as a prop but it is not dynamically updating.
Graph_test.js has two props which is sent through array and testing, testing will be used to build the graph later on . But is not currently needed.
Ive tried many attempts, and still not getting anywhere, any help would be greatly appreciated/needed.
Code:
Test.js:
// When radio buttons checked, change corresponding value in array if 0 change to 1 and if 1 change to 0
// This will be used in the graph component, and will enable the functionality of selcting one region or multiple region.
// As the graph will be plotted based on which regions are noted 1 in the array
import $ from "jquery";
import Graph_Test from "./Graph_Test.js";
import React, { useState } from "react";
const Test = props => {
const total_regions = (JSON.parse(JSON.stringify(props.test)).length); // gets the number of regions
const [array, setArray] = useState(Array(total_regions.length).fill(0));
//when a radio button is clicked change its corresponding in the array
//when a radio button is clicked change its corresponding in the array
const handleClick = (item, idx) => {
if (array[idx] == 1) {
array[idx] = 0;
} else {
array[idx] = 1;
}
setArray(array);
};
return ( // displays radio buttons depending on the number of objects in json
<div>
<div>
<Graph_Test testing={[]} arrays={array}/>
</div>
<div>
{props.test.map((item, idx) => {
return (
<label key={idx}>
<input className="region" type="radio" value={idx} onClick={() => handleClick(item, idx)}/>
<span>{idx}</span>
</label>
);
})}
</div>
</div>
);
};
export default Test;
Graph_Test.js
import React from 'react';
import $ from "jquery";
//<h1>{props.testing.map}</h1>
const Graph_Test = props => {
return(
<div>
<div>
{props.arrays && props.arrays.length > 0 && <p>{props.arrays[0]}</p> }
</div>
</div >
);
};export default Graph_Test;
App.js
import "bootstrap/dist/css/bootstrap.css";
import React from "react";
import ReactPlayer from 'react-player'
import LeftPane from "./components/LeftPane.js";
import Video from "./components/Video.js";
//import Footer from "./components/Footer.js";
import Test from "./components/Test.js";
import Graph_Test from "./components/Graph_Test.js";
//import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { apiResponse: [] };
this.state = {
clicked: "no"
};
}
// Comunicate with API
callAPI() {
fetch("http://localhost:9000/IntensityAPI") //React app talks to API at this url
.then(res => res.json())
.then(res => this.setState({ apiResponse: res }));
}
handleClick = () => {
this.setState({ ...this.state, isClicked: "yes" });
console.log("clicked");
};
componentWillMount() {
this.callAPI();
}
render() {
return (
<div className="App">
<div class="row fixed-top fixed-bottom no-gutters" >
<div class="col-3 fixed-top fixed-bottom">
<LeftPane></LeftPane>
</div>
<div class="offset-md-3 fixed-top fixed-bottom" >
<Video></Video>
</div>
<div class=" col-3 fixed-bottom">
<Test test = {this.state.apiResponse} handler={this.handleClick}/>
<Graph_Test testing = {this.state.apiResponse} arrays={[]} {...this.state}/>
</div>
</div>
</div>
);
}
}
export default App;
// <Footer test = {this.state.apiResponse}/>
AFAIK React does not perform deep comparison with the state, so since you're reusing the already existing array it has the same memory reference as the previous one.
Try something like this:
const handleClick = (item, idx) => {
const newArray = [...array]
if (newArray[idx] == 1) {
newArray[idx] = 0;
} else {
newArray[idx] = 1;
}
setArray(newArray);
};

React returns older state value onClick

I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.
Is there a way to have the current state recognized within that delete function?
https://codesandbox.io/s/twilight-water-jxnup
import React, { useState } from "react";
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => deleteSpan(props.index)}>DELETE</button>
Length: {spans.length}
</div>
);
};
//set initial span w/ useState
const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
//add new span
const addSpan = () => {
let key = Math.random();
setSpans([...spans, <Span key={key} index={key} />]);
};
//delete span
const deleteSpan = index => {
console.log(spans);
console.log(spans.length);
};
//clear all spans
const clearInputs = () => {
setSpans([]);
};
return (
<>
{spans}
<button onClick={() => addSpan()}>add</button>
<button onClick={() => clearInputs()}>clear</button>
</>
);
}
UPDATE - Explaining why you are facing the issue descibed on your question
When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.
This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.
Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.
You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
Length: {spans.length}
</div>
);
};
const [spans, setSpans] = React.useState([]);
return (
<>
{spans.length
? spans.map((span, index) => (
<Span key={span} index={index} span={span} />
))
: null}
<button onClick={() => setSpans([
...spans,
new Date().getTime(),
])}>add</button>
<button onClick={() => setSpans([])}>clear</button>
</>
);
}
I hope this helps you find your way.

Categories

Resources