moving components up and down - javascript

I have a simple project.
I want to move components up and down with buttons. How can I do that?
for example; https://codesandbox.io/s/epic-water-k1m4fg

import React, { useState } from "react";
function arraymove(arr, fromIndex, toIndex) {
const arrCopy = [...arr];
var element = arrCopy[fromIndex];
arrCopy.splice(fromIndex, 1);
arrCopy.splice(toIndex, 0, element);
return arrCopy;
}
export default function App() {
const [components, setComponents] = useState([]);
function MoveUp(index) {
if (index === 0) return;
const updatedArray = arraymove(components, index, index - 1);
setComponents(updatedArray);
}
function MoveDown(index) {
if (index === components.length - 1) return;
const updatedArray = arraymove(components, index, index + 1);
setComponents(updatedArray);
}
const MovableComponent = (props) => (
<>
<div>{props.id}</div>
<button onClick={() => MoveUp(props.index)}> Move Up </button>
<button onClick={() => MoveDown(props.index)}> Move Down </button>
<br />
<br />
</>
);
return (
<div className="App">
{components.map(({ id }, index) => (
<MovableComponent key={index} id={id} index={index} />
))}
<button
onClick={() => {
setComponents((prev) => [
...prev,
{ id: `component #${components.length}` }
]);
}}
>
Add
</button>
</div>
);
}
Codesandbox

Related

How can I map through Props consisting of 3 useMemo functions in React?

I have 3 separate functions using useMemo that return a different type of alert for a user in a dashboard.
These alerts appear at the top of the user dashboard if they are truthy, but if there is more than 1 alert that should appear, they are placed within a slideshow that slides automatically.
I have created a Slideshow component and pass all 3 functions in through props. My question is how I can map through props in the Slideshow component to render out these alerts?
I have tried to do so with 'props.map' but receive an error about map missing from PropTypes so am unsure if I am doing this correctly by trying to render out the alerts with a .map?
This is my main file with the 3 useMemo alerts:
export const TransactionsPage = (props) => {
const { t } = props;
const { data: profile } = apiService.useGetSingleAccountQuery();
const { data: accountInfo } = apiService.useGetPlanQuery();
const renderMaxManualTransactionWarning = useMemo(() => {
if (accountInfo?.exceed_tx_capability)
return (
<>
<div className={"free-trial-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>{t("pages.transactions.warning.transactions_limit")}</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/account/subscription/change_plan")}
>
{t("pages.transactions.warning.action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
);
}, [t, accountInfo]);
// }, [t]);
const renderMissingTransferTransactionWarning = useMemo(() => {
if (unlinkedTransferTx?.items?.length > 0)
return (
<>
<div className={"import-warning"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.transfer_transactions_missing").replace(
"[num]",
unlinkedTransferTx.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/transfer/list")}
>
{t("pages.transactions.warning.transfer_transactions_missing_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
);
}, [t, unlinkedTransferTx]);
const renderSuggestedLinksWarning = useMemo(() => {
if (suggestedLinks?.items?.length > 0)
return (
<div className={"suggested-links-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.suggested_links").replace(
"[num]",
suggestedLinks.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/links/suggestions")}
>
{t("pages.transactions.warning.links_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
);
}, [t, suggestedLinks]);
return (
<LoggedInLayout className={"transactions-page"}>
<Slideshow
renderMaxManualTransactionWarning={renderMaxManualTransactionWarning}
renderMissingTransferTransactionWarning={renderMissingTransferTransactionWarning}
renderSuggestedLinksWarning={renderSuggestedLinksWarning}
/>
</LoggedInLayout>
);
};
TransactionsPage.propTypes = {
t: func,
};
export default withTranslation()(TransactionsPage);
And my SlideShow component:
/* eslint-disable no-unused-vars */
import React, { useEffect, useState, useRef, useMemo } from "react";
import "./index.scss";
import PropTypes from "prop-types";
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 10000;
export const Slideshow = (props) => {
const [index, setIndex] = useState(0);
const timeoutRef = useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() => setIndex((prevIndex) => (prevIndex === colors.length - 1 ? 0 : prevIndex + 1)),
delay
);
return () => {
resetTimeout();
};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider" style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}>
{/* Map through the functions and render them here */}
</div>
<div className="slideshowDots">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshowDot${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
};
Slideshow.propTypes = {
};
export default Slideshow;
Put simply, I just want to map through the functions coming from props and render them in the Slideshow.
Any advice on this would be appreciated. Many thanks in advance!
Instead of pass the alerts as a props you can do it as a children
1:
export const TransactionsPage = (props) => {
const { t } = props;
const { data: profile } = apiService.useGetSingleAccountQuery();
const { data: accountInfo } = apiService.useGetPlanQuery();
const renderMaxManualTransactionWarning = useMemo(() =>
accountInfo?.exceed_tx_capability ?
<>
<div className={"free-trial-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>{t("pages.transactions.warning.transactions_limit")}</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/account/subscription/change_plan")}
>
{t("pages.transactions.warning.action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
: null
, [t, accountInfo]);
// }, [t]);
const renderMissingTransferTransactionWarning = useMemo(() =>
unlinkedTransferTx?.items?.length > 0 ?
<>
<div className={"import-warning"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.transfer_transactions_missing").replace(
"[num]",
unlinkedTransferTx.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/transfer/list")}
>
{t("pages.transactions.warning.transfer_transactions_missing_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
: null
, [t, unlinkedTransferTx]);
const renderSuggestedLinksWarning = useMemo(() =>
suggestedLinks?.items?.length > 0 ?
<div className={"suggested-links-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.suggested_links").replace(
"[num]",
suggestedLinks.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/links/suggestions")}
>
{t("pages.transactions.warning.links_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
:null
, [t, suggestedLinks]);
return (
<LoggedInLayout className={"transactions-page"}>
<Slideshow>
{renderMaxManualTransactionWarning}
{renderMissingTransferTransactionWarning}
{renderSuggestedLinksWarning}
</Slideshow>
</LoggedInLayout>
);
};
TransactionsPage.propTypes = {
t: func,
};
export default withTranslation()(TransactionsPage);
2:
import React, { useEffect, useState, useRef, useMemo } from "react";
import "./index.scss";
import PropTypes from "prop-types";
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 10000;
export const Slideshow = ({children}) => {
const [index, setIndex] = useState(0);
const timeoutRef = useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() => setIndex((prevIndex) => (prevIndex === colors.length - 1 ? 0 : prevIndex + 1)),
delay
);
return () => {
resetTimeout();
};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider" style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}>
{children}
</div>
<div className="slideshowDots">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshowDot${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
};
Slideshow.propTypes = {
};
export default Slideshow;

How can I prevent these values of color and stocks to be duplicated when I add more sizes?

Users can add more text fields of size, color, and stocks. If I'll add more sizes, the values for the color and stocks will duplicate from what was first entered.
Expected output:
1st Size : small
color: red, stocks: 10
color: green, stocks: 3
2nd Size: medium
color: white, stocks: 3
color: red, stocks: 6 the sizes field.
What it currently does is in the 2nd size, it will just duplicate whatever value was entered from the first size. How can I fix this? Thank you.
How can I combine the indexes of the colorList loop and sizeList loop to avoid the duplicates of the value of the textfields?
Link: https://codesandbox.io/s/form-2-add-more-size-ddqqo?file=/demo.js
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import { TextField, Button } from "#mui/material";
export default function BasicSelect() {
const [productName, setProductName] = useState();
const [sizeList, setSizeList] = useState([{ size: "" }]);
const [colorList, setColorList] = useState([{ color: "", colorStocks: "" }]);
//sizes
const handleServiceChange = (e, index) => {
const { name, value } = e.target;
const list = [...sizeList];
list[index][name] = value;
setSizeList(list);
};
const handleServiceRemove = (index) => {
const list = [...sizeList];
list.splice(index, 1);
setSizeList(list);
};
const handleServiceAdd = () => {
setSizeList([...sizeList, { service: "" }]);
};
// color
const handleColorChange = (e, index) => {
const { name, value } = e.target;
const list = [...colorList];
list[index][name] = value;
setColorList(list);
// console.log(colorList);
};
const handleColorStocksChange = (e, index) => {
const { name, value } = e.target;
const list = [...colorList];
list[index][name] = value;
setColorList(list);
// console.log(colorList);
};
// const handleColorChange = (e, index) => {
// const { value } = e.target;
// const arr = [...colorList]; //Shallow copy the existing state
// arr[index].color = value; //Update the size to the selected size
// console.log(arr[index].value);
// setColorList([...arr]); //Set the updated array to be the new state
// };
// const handleColorStocksChange = (e, index) => {
// const { value } = e.target;
// console.log(value);
// const arr = [...colorList];
// arr[index].colorStocks = value;
// // console.log(arr)
// setColorList([...arr]);
// };
const handleColorRemove = (index) => {
const list = [...colorList];
list.splice(index, 1);
setColorList(list);
};
const handleColorAdd = () => {
setColorList([...colorList, { color: "", colorStocks: "" }]);
};
const handleSubmit = async (e) => {
e.preventDefault();
console.log("Product: ", productName, "size: ", sizeList, colorList);
};
return (
<Box sx={{ minWidth: 120 }}>
<form onSubmit={handleSubmit}>
<TextField
label="Product Name"
name="name"
type="text"
id="productName"
value={productName}
onChange={(e) => setProductName(e.target.value)}
required
/>
{sizeList.map((singleSize, index) => (
<div key={index}>
<TextField
label="Size"
name="size"
type="text"
id="size"
required
value={singleSize.size}
onChange={(e) => handleServiceChange(e, index)}
/>
{colorList.map((singleColor, index) => (
<div key={index}>
<TextField
label="color"
name="color"
type="text"
id="color"
required
value={singleColor.color}
onChange={(e) => handleColorStocksChange(e, index)}
/>
<TextField
label="Stocks"
name="colorStocks"
type="text"
id="colorStocks"
required
value={singleColor.colorStocks}
onChange={(e) => handleColorChange(e, index)}
/>
{colorList.length !== 1 && (
<Button onClick={() => handleColorRemove(index)}>
Remove
</Button>
)}
<br />
{colorList.length - 1 === index && (
<Button onClick={handleColorAdd}>Add Color</Button>
)}
<br /> <br />
{/* add or remove sizes */}
</div>
))}
{sizeList.length - 1 === index && (
<Button type="button" onClick={handleServiceAdd}>
Add size
</Button>
)}
{sizeList.length - 1 === index && (
<Button type="button" onClick={() => handleServiceRemove(index)}>
Remove Size
</Button>
)}
</div>
))}
<br />
<Button type="submit">Submit </Button>
</form>
<div className="output">
<h2>Output</h2>
<h3>Sizes:</h3>
{sizeList &&
sizeList.map((singleSize, index) => (
<ul key={index}>{singleSize.size && <li>{singleSize.size}</li>}</ul>
))}
<br />
<h3>Color:</h3>
{colorList &&
colorList.map((singleSize, index) => (
<ul key={index}>
{singleSize.color && (
<li>{singleSize.color + " - " + singleSize.colorStocks}</li>
)}
</ul>
))}
</div>
</Box>
);
}
Also in: https://www.reddit.com/r/reactjs/comments/sl41p5/handling_dynamic_textfields_values_are_duplicated/
Since you need an array of inputs, you need to maintain the states in that order.
See my updated sandbox code https://codesandbox.io/s/form-2-add-more-size-forked-yvrgg?file=/demo.js
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import { TextField, Button } from "#mui/material";
export default function BasicSelect() {
const [productName, setProductName] = useState();
const [sizeList, setSizeList] = useState([{ size: "", colorList: [{ color: "", colorStocks: "" }] }]);
const [colorList, setColorList] = useState([{ color: "", colorStocks: "" }]);
//sizes
const handleServiceChange = (e, index) => {
const { name, value } = e.target;
const list = [...sizeList];
list[index] = { ...list[index] }; // copy the item too
list[index][name] = value;
setSizeList(list);
};
const handleServiceRemove = (index) => {
const list = [...sizeList];
list.splice(index, 1);
setSizeList(list);
};
const handleServiceAdd = () => {
setSizeList([...sizeList, { service: "", colorList: [{ color: "", colorStocks: "" }] }]);
};
// color
const handleColorChange = (e, index, innerIndex) => {
const { name, value } = e.target;
const list = [...sizeList];
list[index].colorList[innerIndex][name] = value;
setSizeList(list)
};
const handleColorRemove = (index) => {
const list = [...colorList];
list.splice(index, 1);
setColorList(list);
};
const handleColorAdd = (index) => {
const list = [...sizeList];
list[index].colorList.push({ color: "", colorStocks: "" });
setSizeList(list)
};
const handleSubmit = async (e) => {
e.preventDefault();
console.log("Product: ", productName, "size: ", sizeList, colorList);
};
return (
<Box sx={{ minWidth: 120 }}>
<form onSubmit={handleSubmit}>
<TextField
sx={{mb:2}}
label="Product Name"
name="name"
type="text"
id="productName"
value={productName}
onChange={(e) => setProductName(e.target.value)}
required
/>
{sizeList.map((singleSize, index) => (
<div key={index}>
<TextField
label="Size"
type="text"
name={`size${index}`}
id={`size${index}`}
required
value={singleSize.size}
onChange={(e) => handleServiceChange(e, index)}
/>
{singleSize.colorList.map((singleColor, innerIndex) => (
<div key={index}>
<TextField
label="color"
name="color"
type="text"
id="color"
required
value={singleColor.color}
onChange={(e) => handleColorChange(e, index, innerIndex)}
/>
<TextField
label="Stocks"
name="colorStocks"
type="text"
id="colorStocks"
required
value={singleColor.colorStocks}
onChange={(e) => handleColorChange(e, index, innerIndex)}
/>
{colorList.length !== 1 && (
<Button onClick={() => handleColorRemove(index)}>
Remove
</Button>
)}
<br />
{colorList.length - 1 === index && (
<Button onClick={()=>handleColorAdd(index)}>Add Color</Button>
)}
<br /> <br />
{/* add or remove sizes */}
</div>
))}
{sizeList.length - 1 === index && (
<Button type="button" onClick={handleServiceAdd}>
Add size
</Button>
)}
{sizeList.length - 1 === index && (
<Button type="button" onClick={() => handleServiceRemove(index)}>
Remove Size
</Button>
)}
</div>
))}
<br />
<Button type="submit">Submit </Button>
</form>
<div className="output">
<h2>Output</h2>
<h3>Sizes:</h3>
{sizeList &&
sizeList.map((singleSize, index) => (
<ul key={index}>{singleSize.size && <li>{singleSize.size}</li>}</ul>
))}
<br />
<h3>Color:</h3>
{colorList &&
colorList.map((singleSize, index) => (
<ul key={index}>
{singleSize.color && (
<li>{singleSize.color + " - " + singleSize.colorStocks}</li>
)}
</ul>
))}
</div>
</Box>
);
}
If you do something like below you wont have inputs with the same name
name=`size${index}`
id=`id${index}`

How to connect two components in a same component created on click of add button

I am creating multiple counters on click and I want to connect the two counters, When I increase the value in the first counter it should automatically decrease in the second counter. Can you suggest any solution where I can communicate with multiple counters as I can generate multiple counters on click?
const Counter = ({ value, onIncrement, onDecrement, hideIncrement }) => {
return (
<div>
<span>{value}</span>
{value > 0 && (
<button
onClick={() => {
onDecrement();
}}
>
-
</button>
)}
{hideIncrement === false && value < 10 && (
<button
onClick={() => {
onIncrement();
}}
>
+
</button>
)}
</div>
);
};
const Counters = () => {
const [counters, setCounters] = React.useState([4, 0, 0, 5]);
const sum = counters.reduce((acc, item) => acc + item, 0);
return (
<div>
<p>Sum: {sum}</p>
<button
onClick={() => {
setCounters([...counters, 0]);
}}
>
Add counter
</button>
<br />
<div>
{counters.map((value, index) => (
<Counter
value={value}
hideIncrement={sum >= 20}
onIncrement={() => {
const countersCopy = [...counters];
countersCopy[index] += 1;
setCounters(countersCopy);
}}
onDecrement={() => {
const countersCopy = [...counters];
countersCopy[index] -= 1;
setCounters(countersCopy);
}}
/>
))}
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Counters />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can pass state to Couter component:
try this:
const Counter = ({value, onIncrement, onDecrement, hideIncrement,
addStatus, counterIndex, index}) => {
useEffect(() => {
if (counterIndex === index - 1) onDecrement(addStatus);
}, [counterIndex, addStatus])
return (
<div>
<span>{value}</span>
{value > 0 && (
<button
onClick={() => {
onDecrement();
}}
>
-
</button>
)}
{hideIncrement === false && value < 10 && (
<button
onClick={() => {
onIncrement();
}}
>
+
</button>
)}
</div>
);
};
const Counters = () => {
const [counters, setCounters] = React.useState([4, 4, 4, 5]);
const [addStatus, setAddStatus] = useState(0);
const [counterIndex, setCounterIndex] = useState(0);
const sum = counters.reduce((acc, item) => acc + item, 0);
return (
<div>
<p>Sum: {sum}</p>
<button
onClick={() => {
setCounters([...counters, 0]);
}}
>
Add counter
</button>
<br/>
<div>
{counters.map((value, index) => (
<Counter
value={value}
index={index}
addStatus={addStatus}
counterIndex={counterIndex}
hideIncrement={sum >= 20}
onIncrement={() => {
const countersCopy = [...counters];
countersCopy[index] += 1;
setAddStatus(a => a + 1)
setCounterIndex(index)
setCounters(countersCopy);
}}
onDecrement={() => {
const countersCopy = [...counters];
countersCopy[index] -= 1;
setCounters(countersCopy);
}}
/>
))}
</div>
</div>
);
};

on click function not working because its in another return statement

I am trying to make history by pushing on button click
onclick function of the li is not working
as u can see in the code <SuggestionsList/> is in the last return statement its funtions are rendered in const SuggestionsList = (props) => { . The onclick funtion is comimg inside the const SuggestionsList funtion, this is making the onclick funtion not working
i created the exact working in codesand and its working there without any problem i dont get why its not working in my local
enter link description here
function finddoctor(e) {
console.log(e);
history.push(`/detiled/${e} `);
}
const onChange = (event) => {
const value = event.target.value;
setInputValue(value);
setShowResults(false);
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.firstname
.toString()
.toLowerCase()
.includes(value.toLowerCase()) ||
suggestion.id.toString().toLowerCase().includes(value.toLowerCase())
);
setFilteredSuggestions(filteredSuggestions);
setDisplaySuggestions(true);
};
const onSelectSuggestion = (index) => {
setSelectedSuggestion(index);
setInputValue(filteredSuggestions[index]);
setFilteredSuggestions([]);
setDisplaySuggestions(false);
};
const SuggestionsList = (props) => {
const {
suggestions,
inputValue,
onSelectSuggestion,
displaySuggestions,
selectedSuggestion,
} = props;
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list" style={styles.ulstyle}>
{suggestions.map((suggestion, index) => {
const isSelected = selectedSuggestion === index;
const classname = `suggestion ${isSelected ? "selected" : ""}`;
return (
<>
<li
style={styles.listyle}
onClick={finddoctor(suggestion.id)}
key={index}
className={classname}
>
{suggestion.firstname}
</li>
</>
);
})}
</ul>
);
} else {
return <div>No suggestions available...</div>;
}
}
return <></>;
};
useEffect(() => {
axios
.get("admin-panel/all-doctors-list/")
.then((res) => {
const data = res.data;
setShowSerch(data);
});
}, []);
return (
<>
<div className="note-container" style={styles.card}>
<div style={styles.inner}>
<p style={{ textAlign: "left" }}>Search Doctors</p>
<form className="search-form" style={{}}>
{showResults ? (
<FontAwesomeIcon
style={{ marginRight: "-23px" }}
icon={faSearch}
/>
) : null}
<input
onChange={onChange}
value={inputValue}
style={styles.input}
type="Search"
/>
<SuggestionsList
inputValue={inputValue}
selectedSuggestion={selectedSuggestion}
onSelectSuggestion={onSelectSuggestion}
displaySuggestions={displaySuggestions}
suggestions={filteredSuggestions}
/>
</form>
</div>
</div>
</>
);
};
Instead of onClick={finddoctor(suggestion.id)} (Here just function invocation is happening and expected to have the callback method)
should be
onClick={() => finddoctor(suggestion.id)}

Add prop when onClick in ReactJS

I would like to change current li item color when I click it.
How to add prop to item(using array map), when I click it? I use styled-components
const Li = styled.li`
color: ${props => (props.checked ? "red" : "green")};
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: []
};
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index}>
{item}
<button onClick={() => this.deleteItemHandler(index)}> Delete</button>
</Li>
);
});
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);
}
}
Check out this code working on CodeSandBox
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import styled from "styled-components";
const Li = styled.li`
color: ${props => (props.checked ? "red" : "green")};
`;
class App extends Component {
state = {
value: "",
items: [],
selected: -1
};
handleChange = e => {
this.setState({
[e.currentTarget.name]: e.currentTarget.value
});
};
handleAdd = () => {
const { items, value } = this.state;
this.setState({
items: [...items, value],
value: ""
});
};
handleRemove = index => {
const { items, selected } = this.state;
items.splice(index, 1);
if (index < selected) index = selected - 1;
if (index === selected) index = -1;
if (index > selected) index = selected;
this.setState({
items: items,
selected: index
});
};
handleActiveItem = index => {
this.setState({ selected: index });
};
render() {
const { value, items, selected } = this.state;
return (
<div>
<input
type="text"
value={value}
name="value"
onChange={this.handleChange}
/>
<button
style={{ margin: "0px 5px" }}
disabled={!value}
className="btn btn-sm btn-success"
onClick={this.handleAdd}
>
+
</button>
<ul className="li">
{items.map((item, index) => (
<Li key={index} checked={selected === index}>
<span onClick={() => this.handleActiveItem(index)}>{item}</span>
<button
style={{ margin: "1px 5px" }}
className="btn btn-sm btn-danger"
onClick={() => this.handleRemove(index)}
>
X
</button>
</Li>
))}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Ignore the handlers if you don't need them. Thanks to this effort I learnt about styled-components and discovered CodeSandBox.
EDIT :
Added a <span> inside <li> to avoid nested onClick, previously <li> (parent) and <button> (child) both had onClick attribute. On button Click two onClick events were fired resulting in unexpected behaviour in some use cases. You must correct this in your code.
Added logic to keep item selected when an item before it is deleted.
Added button validation to avoid adding empty string/items in list.
Also updated CodeSandBox Code to reflect above changes.
So you need keep track of the active index, and use it too change the color of the active component color.
state ={
activeIndex: void 0
}
const Li = styled.li`
color: ${props => props.checked ? "red" : "green"};
;`
deleteItemHandler = (index) => {
this.setState({
activeIndex: index
})
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index} checked={index === this.state.activeIndex} > {item} < button onClick={() => this.deleteItemHandler(index)
}> Delete</button ></Li >
)
})
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);
Try this
const Li = styled.li`
color: ${props => props.checked ? "red" : "green"};
;`
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: [],
currentChecked: null
};
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index} checked={index === this.state.currentChecked} >
{item}
<button onClick={() => this.setState({currentChecked: index})}>Delete</button >
</Li >
)
})
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);

Categories

Resources