How i can replace states ( arrays ) ReactJs - javascript

today I offer you a new challenge
my problem and the next
in the following code
I map objects in my database
I also map a list of items
and so basically I would like to pass my database and not the Items table
basically I want to do exactly the same thing as in the following code except that instead of using the items array, I would like to be able to use the data array which contains my database
do you have any idea how to do this?
I hope I was precise thanks to you for the help Neff
ps: Sorry for the length of the code i try to do my best to clean a little
import React, { Component } from 'react';
import { CardText, Card,Row, Col, Button } from 'reactstrap';
import axios from 'axios'
import GridLayout from 'react-grid-layout';
import SmsForm from './Sms/SMSForm'
import FruitList from './FruitList'
import './AdminPage.scss'
const UP = -1;
const DOWN = 1;
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT+'/api';
class AdminPage extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, name: "orange", bgColor: "#f9cb9c" },
{ id: 2, name: "lemon", bgColor: "#fee599" },
{ id: 3, name: "strawberry", bgColor: "#e06666" }
],
data: [],
}
handleMove = (id, direction) => {
const { items } = this.state;
const position = items.findIndex(i => i.id === id);
if (position < 0) {
throw new Error("Given item not found.");
} else if (
(direction === UP && position === 0) ||
(direction === DOWN && position === items.length - 1)
) {
return; // canot move outside of array
}
const item = items[position]; // save item for later
const newItems = items.filter(i => i.id !== id); // remove item from array
newItems.splice(position + direction, 0, item);
this.setState({ items: newItems });
};
// rest of the component
onHandleChange(event) {
const name = event.target.getAttribute('name');
this.setState({
message: { ...this.state.message, [name]: event.target.value }
});
}
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState({ data: res.data })
}
componentDidMount() {
this.getRandom()
}
render() {
let datas = this.state.data.map(datass => {
const status = JSON.parse(localStorage.getItem("validated-order") || "{}")[datass.id];
return (
<div>
< Col sm="12" key={datass.id} className="wtfFuHereIsForOnlyBackGroundColorForCol12Nice">
<FruitList fruitList={this.state.items} onMove={this.handleMove} />
<GridLayout className="GridlayoutTextOnlyForGridOuiAndHeigthbecauseHeigthWasBug" layout={layout} cols={12} rowHeight={30} width={1200}>
<div key="a">
<Card body className="yoloCardBodyForbackGroundAndRaduisBorderForAdminPageWtf">
<CardText className="cardTextForAdminPageForDataName"> Commande de {datass.name}</CardText>
</Card>
</div>
</ Col>
</div>
)
})
return (
<div> <div>
<Row className="wtfHereIsAgainOnlyForRowAndMarginForAdminPageJustWtf">
<div className="isJustForOnlyPaddingOnRowForAdminPage" >
<div className="navBarGridMenuAdminPage">
<div className="thatIsOnlyForSpaceArroundOnDivAdminPage">
<CardText className="maybeColForAdminPageOuiOui"> Nouvelles commandes </CardText>
</div>
</div>
<div>
{datas}
</div>
</div>
</Row>
</div>
<div className="box">
</div>
</div>
)
}
}
export default AdminPage
here my second components
import React from "react";
const LEFT = -1;
const RIGHT = 1;
class FruitList extends React.Component {
render() {
const { fruitList, onMove } = this.props;
return (
<div style={{ display: "flex" }}>
{fruitList.map(item => (
<div
key={item.id}
style={{
backgroundColor: item.bgColor,
display: "flex"
}}
>
<div className="fruitsArrows">
<a onClick={() => onMove(item.id, LEFT)}>←</a>
</div>
<div className="fruitsId">{item.id}</div>
<div className="fruitsName">{item.name}</div>
<div className="fruitsArrows">
<a onClick={() => onMove(item.id, RIGHT)}>→</a>
</div>
</div>
))}
</div>
);
}
}
export default FruitList;

To delete particular list do like this-
pass your item(object).
<a onClick={() => onMove(item)}>→</a>
handleMove function
handleMove = (row) => {
let filtered = this.state.items.filter(item=>item.id!==row.id);
this.setState({ items: filtered});
};

Related

Uncaught TypeError: Cannot read properties of undefined (reading 'longdescription_title') when page is refreshed

I'm pretty new to react, and I am trying to make an Accordion Component with multiple dropdowns. I am trying to load my data from my database. I had thought I got it to work because it showed my data in the correct areas, but when I refreshed the page I got an Uncaught TypeError: Cannot read properties of undefined (reading 'longdescription_title') error. I'm not sure why this is happening, and I would really appreciate any help or advice on how to fix this problem.
Thank you!
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { detailsProduct } from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import '../components/Accordion.css'
import { IconContext } from 'react-icons';
import { FiPlus, FiMinus } from 'react-icons/fi';
export default function ProductScreen(props) {
const dispatch = useDispatch();
const productId = props.match.params.id;
const [accordionItems, setAccordionItems] = useState([]);
const [accordionTitles, setAccordionTitles] = useState([]);
const [clicked, setClicked] = useState(false);
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, product } = productDetails;
const userSignin = useSelector((state) => state.userSignin);
const { userInfo } = userSignin;
useEffect(() => {
if (product) {
const accordionItems = [product.how_to_use];
accordionItems.unshift(product.ingredients);
accordionItems.unshift(product.longdescription);
setAccordionItems(accordionItems);
}
}, [product]);
useEffect(() => {
if (product) {
const accordionTitles = [product.how_to_use_title];
accordionTitles.unshift(product.ingredients_title);
accordionTitles.unshift(product.longdescription_title);
setAccordionTitles(accordionTitles);
}
}, [product]);
const Items = [...accordionItems];
const Titles = [...accordionTitles];
const accordion = [
{title: product.longdescription_title, body: product.longdescription},
{title: product.ingredients_title, body: product.ingredients},
{title: product.how_to_use_title, body: product.how_to_use},
]
const toggle = index => {
if (clicked === index) {
return setClicked(null);
}
setClicked(index);
};
return (
<div>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<div>
<Link to="/body">Back to result</Link>
<div className="row top">
<div className="col-1">
<ul>
<li>
<h1>{product.name}</h1>
</li>
<li>
<div>
<IconContext.Provider value={{ color: 'black', size: '2vw' }}>
<div className="accordionSection">
<div className = "container">
{accordion && accordion.length ? (
accordion.map((item, index) => {
return (
<>
<div className = "wrap" onClick={() => toggle(index)} key={index}>
<h1>{item.title}</h1>
<span>{clicked === index ? <FiMinus /> : <FiPlus />}</span>
</div>
{clicked === index ? (
<div className="dropdown">
<p>{item.body}</p>
</div>
) :
null}
</>
);
})
) : (
<></>
)}
</div>
</div>
</IconContext.Provider>
</div>
</li>
</ul>
</div>
<li>
<button onClick={addToCartHandler} className="primary block">
Add to Cart
</button>
</li>
</>
)}
</ul>
</div>
</div>
</div>
);
}
Initialize the Items, Titles and accordion variables only when the product has been set (in the useEffect calls).
You should use a separate state to store the accordion array.
Also, no need to use separate useEffect calls:
let Items = [];
let Titles = [];
const [accordion, setAccordion] = useState([]);
useEffect(() => {
if (product) {
const accordionItems = [product.how_to_use];
accordionItems.unshift(product.ingredients);
accordionItems.unshift(product.longdescription);c
setAccordionItems(accordionItems);
Items = [...accordionItems];
const accordionTitles = [product.how_to_use_title];
accordionTitles.unshift(product.ingredients_title);
accordionTitles.unshift(product.longdescription_title);
setAccordionTitles(accordionTitles);
Titles = [...accordionTitles];
setAccordion([
{ title: product.longdescription_title, body: product.longdescription },
{ title: product.ingredients_title, body: product.ingredients },
{ title: product.how_to_use_title, body: product.how_to_use },
]);
}
}, [product]);

clicked button toggle all the items in the list (React)

I have an array of job descriptions that I want to hide a part of each description and show it completely when a button is clicked using React hooks.
I am iterating over the array( consists of id and description) to show all the descriptions as a list in the component. There is a button right after each paragraph to show or hide the content.
readMore is used to hide/show the content and
activeIndex is used to keep track of clicked item index.
This is what I have done so far:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [readMore, setReadMore] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{readMore ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
setActiveIndex(index);
if (activeIndex === id) {
setReadMore(!readMore);
}
}}
>
{readMore ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
The problem is that when I click one button it toggles all the items in the list.
I want to show/hide content only when its own button clicked.
Can somebody tell me what I am doing wrong?
Thanks in advance.
Your readMore state is entirely redundant and is actually causing the issue. If you know the activeIndex, then you have all the info you need about what to show and not show!
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndex === index ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
if (activeIndex) {
setActiveIndex(null);
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Edit: The aforementioned solution only lets you open one item at a time. If you need multiple items, you need to maintain an accounting of all the indices that are active. I think a Set would be a perfect structure for this:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndices, setActiveIndices] = useState(new Set());
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndices.has(index) ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
const newIndices = new Set(activeIndices);
if (activeIndices.has(index)) {
newIndices.delete(index);
} else {
newIndices.add(index);
}
setActiveIndices(newIndices);
}}
>
{activeIndices.has(index) ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Try this
{readMore && (activeIndex === id) ? description : `${description.substring(0, 250)}...`}
function Destination() {
const travels = [
{
title: "Home"
},
{
title: "Traveltype",
subItems: ["Local", "National", "International"]
},
{
title: "Contact",
subItems: ["Phone", "Mail", "Chat"]
}
];
const [activeIndex, setActiveIndex] = useState(null);
return (
<div className="menu-wrapper">
{travels.map((item, index) => {
return (
<div key={`${item.title}`}>
{item.title}
{item.subItems && (
<button
onClick={() => {
if (activeIndex) {
if (activeIndex !== index) {
setActiveIndex(index);
} else {
setActiveIndex(null);
}
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? `Hide` : `Expand`}
</button>
)}
{activeIndex === index && (
<ul>
{item.subItems &&
item.subItems.map((subItem) => {
return (
<li
key={`li-${item.title}-${subItem}`}
>
{subItem}
</li>
);
})}
</ul>
)}
</div>
);
})}
</div>
);
}
<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>

React Hooks, Conditionally change state inside onClick function, updates on the next click

Hello I have a single list component which render some <Card> components that has a prop isSelected . Because alot of things happens when a <Card> Component has isSelected === true I added the state on the <List> component, and I want when someone clicks a card to check:
1) If there are no previously selected items ( state===null to add that item's id to state )
2) If someone clicks the same item or another item while there is already an item selected in state, to just unselected the active item.
import {Card} from "./Card";
import cloneDeep from 'lodash/cloneDeep';
const List = () => {
const [selectedCard, setSelectedCard] = useState(null);
const onCardClick = id => {
console.debug(selectedCard, id)
const newSelectedCard = cloneDeep(selectedCard);
// if he clicks another item while an item is active
// or if he clicks the same item while active
// should just make it inactive
if (newSelectedCard !== null || newSelectedCard === id) {
setSelectedCard(null)
} else {
setSelectedCard(id)
}
console.debug(selectedCard, id)
}
return (
<ul className="card-list">
{cardData.map(card => (
<Card
onClick={() => onCardClick(card.id)}
key={card.id}
isSelected={selectedCard === card.id}
{...card}
/>
))}
</ul>
)
}
export const CardList = () => (
<List/>
);
The issue is that the 2 console.debugs print the same values which means that the state doesnt update imediately and Im experiencing some strange behaviours here and there. Am I missing something here?
Basically you need to follow 3 condition as below
if(newSelectedCard === null){
setSelectedCard(id)
}
else if(newSelectedCard === id){
setSelectedCard(null);
}
else{
setSelectedCard(id)
}
Here is the Complete example:
import cloneDeep from 'lodash/cloneDeep';
import React, {useState} from "react";
const List = () => {
const [cardData, setCardData] = useState([
{id: 1, title: 'First Card'},
{id: 2, title: 'Second Card'},
{id: 3, title: 'Third Card'},
{id: 4, title: 'Fourth Card'},
]);
const [selectedCard, setSelectedCard] = useState(null);
const onCardClick = id => {
console.log(selectedCard, id);
const newSelectedCard = cloneDeep(selectedCard);
// if he clicks another item while an item is active
// or if he clicks the same item while active
// should just make it inactive
if(newSelectedCard === null){
setSelectedCard(id)
}
else if(newSelectedCard === id){
setSelectedCard(null);
}
else{
setSelectedCard(id)
}
console.log(selectedCard, id)
};
return (
<ul className="card-list">
{cardData.map(card => (
<Card
onClick={() => onCardClick(card.id)}
key={card.id}
isSelected={selectedCard === card.id}
{...card}
/>
))}
</ul>
)
};
export const CardList = () => (
<List/>
);
const Card = (props) => {
const backColor = props.isSelected? '#F9740E' : '#3FB566';
return (
<div onClick={() => props.onClick()}>
<div style={{backgroundColor: backColor, border: '1px solid darkgreen', color: 'white', padding: 10, marginBottom: 10}}>
<h3>{props.id}</h3>
<h4>{props.title}</h4>
</div>
</div>
);
};
Update
Here is Code SandBox
Not sure why you need to use cloneDeep.
const onCardClick = id => {
if (selectedCard === id) {
setSelectedCard(null);
} else {
setSelectedCard(id);
}
}

Downshift: Set inputValue to value of currently highlighted item on keyboard navigation

Using Downshift, how would one implement settting the inputValue to the value of the currently highlighted item on ArrowUp/ArrowDown while persisting the filtered items until the user manually augments the inputValue?
e.g:
The aforementioned behaviour can be implemented leveraging the stateReducer and useCombobox hook and as follows:
import React, { useState } from "react";
import { render } from "react-dom";
import { useCombobox } from "downshift";
import { items, menuStyles } from "./utils";
function stateReducer(state, actionAndChanges) {
switch (actionAndChanges.type) {
case useCombobox.stateChangeTypes.InputChange:
return {
...actionAndChanges.changes,
userInput: actionAndChanges.changes.inputValue
};
case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
case useCombobox.stateChangeTypes.InputKeyDownArrowUp:
if (!actionAndChanges.changes.inputValue) return actionAndChanges.changes;
return {
...actionAndChanges.changes,
userInput: actionAndChanges.changes.inputValue,
inputValue: actionAndChanges.getItemNodeFromIndex(
actionAndChanges.changes.highlightedIndex
).innerText
};
default:
return actionAndChanges.changes; // otherwise business as usual.
}
}
function DropdownSelect() {
const [inputItems, setInputItems] = useState(items);
const {
isOpen,
getToggleButtonProps,
getLabelProps,
getMenuProps,
getInputProps,
getComboboxProps,
highlightedIndex,
getItemProps
} = useCombobox({
items: inputItems,
stateReducer,
onInputValueChange: ({ userInput, inputValue }) => {
if (userInput === inputValue) {
const filteredItems = items.filter(item =>
item.toLowerCase().startsWith(inputValue.toLowerCase())
);
setInputItems(filteredItems);
}
}
});
return (
<React.Fragment>
<label {...getLabelProps()}>Choose an element:</label>
<div style={{ display: "inline-block" }} {...getComboboxProps()}>
<input {...getInputProps()} />
<button {...getToggleButtonProps()} aria-label="toggle menu">
↓
</button>
</div>
<ul {...getMenuProps()} style={menuStyles}>
{isOpen &&
inputItems.map((item, index) => (
<li
style={
highlightedIndex === index ? { backgroundColor: "#bde4ff" } : {}
}
key={`${item}${index}`}
{...getItemProps({ item, index })}
>
{item}
</li>
))}
</ul>
</React.Fragment>
);
}
render(<DropdownSelect />, document.getElementById("root"));
View the Code sandbox here

Hide/Show content using ReactJS

I am trying to implement to Show More/Show Less. So far I have was able to bring up a ItemViewer Component where in I display the list of items. For each section there would be Show More / Show Less links. Show More should be visible whenever the number of items is greater than 3 and it should be able to toggle(Show More/ Show Less). When the number of items is less than 3 dont show the link. Also when there is no data display "No data found".
I have come up with a sandbox : https://codesandbox.io/s/pensive-kirch-1fgq3
Can someone help me here?
import React from "react";
import ReactDOM from "react-dom";
import ItemViewer from "./Item";
const item1 = ["i1d1", "i2d2", "i3d3"];
const item2 = ["i2d1", "i2d2", "i2d3", "i2d4"];
const item3 = ["i3d1", "i3d2", "i3d3", "i3d4", "i3d5"];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
item1: [],
item2: [],
item3: []
};
}
componentDidMount() {
this.setState({
item1,
item2,
item3
});
}
render() {
let { item1, item2, item3 } = this.state;
return (
<>
<ItemViewer index="1" item="item1" itemData={item1} />
<ItemViewer index="2" item="item2" itemData={item2} />
<ItemViewer index="3" item="item3" itemData={item3} />
</>
);
}
}
import React, { useState } from "react";
const ItemViewer = props => {
function renderItems(list, itemType) {
if (list && list.length > 0) {
return (
<>
<ul>
{list.map(function(item) {
return <li key={item}>{item}</li>;
})}
</ul>
</>
);
} else {
return <p>No Items Found</p>;
}
}
return (
<div>
<span>
{props.index}: {props.item}
</span>
<div>
Show more
</div>
<div>
Show Less
</div>
<div>{renderItems(props.itemData, props.item, props.itemDetailData)}</div>
</div>
);
};
export default ItemViewer;
You can keep a toggle state inside ItemViewer component, and using that value you can decide to show more or show less.
Codesandbox
import React, { useState } from "react";
const ItemViewer = ({ index, itemData, item }) => {
const [toggle, setToggle] = useState(false);
function renderItems(list) {
if (list && list.length > 0) {
if (list.length > 3 && toggle === false) {
return renderList(list.slice(0, 3), "Show More");
} else if (list.length > 3 && toggle === true) {
return renderList(list, "Show Less");
} else if (list.length === 3) {
return renderList(list, "", false);
}
} else {
return <p>No Items Found</p>;
}
}
function renderList(list, buttonText, showButton = true) {
return (
<div>
<ul>
{list.map(function(item) {
return <li key={item}>{item}</li>;
})}
</ul>
{showButton && (
<div>
<button onClick={toggleHandler}>{buttonText}</button>
</div>
)}
</div>
);
}
const toggleHandler = () => {
setToggle(prev => !prev);
};
return (
<div>
<span>
{index}: {item}
</span>
<div>{renderItems(itemData)}</div>
</div>
);
};
export default ItemViewer;
use component state and onClick listener:
function renderItems(list, itemType) {
if (list && list.length > 0 && this.state.showMore1) {
...
<div>
{" "}
<a onClick={() => this.setState({ showMore1: !this.state.showMore1 )} href="#">Show {this.state.showMore1 ? 'Less' : 'More'}</a>
</div>
Use a handler for show events.
https://codesandbox.io/s/blissful-swirles-rchv0
handleShowEvents(index) {
let showMore = [...this.state.showMore];
showMore[index] = !showMore[index];
this.setState({
...this.state,
showMore: showMore
});
}
Also, use a custom list builder.
itemBuilder() {
let items = [];
for (let i = 0; i < this.state.showMore.length; i++) {
const item = `item${i + 1}`;
if (this.state.showMore[i]) {
items.push(
<ItemViewer
index={i}
item={item}
itemData={this.state.items[i]}
handleShowEvents={this.handleShowEvents}
/>
);
} else {
items.push(
<ItemViewer
index={i}
item={item}
itemData={this.state.items[i].slice(0, 3)}
handleShowEvents={this.handleShowEvents}
/>
);
}
}
return items;
}
Check out this https://codesandbox.io/s/smoosh-shape-vinp6
Let me know if it works for you.
Happy Coding:)

Categories

Resources