Incorrect state when onClick fired - reactjs

I'm using AlephJS for the first time, and I think I'm running into a closure issue with my code, but for the life of me I can't figure it out. As you can see I have a modal and a page, onClick I'm dispatching a function, but it's using the inital state, instead of the values updated in the modal. I could probably use useRef(), but, I shouldn't have to? Any info or pointers in the right direction would be much appreciated. Code:
import React, {
useEffect,
useState
} from 'react';
import "./main.css";
import { useGames } from "~/lib/gamesContext.tsx";
// import M from "materialize-css";
export default () => {
const { dispatch } = useGames();
const [modalOpen, setModalOpen] = useState(false);
const [newGame, setNewGame] = useState({
players: [],
type: ""
});
const handleContentLoaded = () => {
const selects = document.querySelectorAll('select');
//#ts-ignore
M.FormSelect.init(selects, {});
const chips = document.querySelectorAll('.chips');
//#ts-ignore
M.Chips.init(chips, {
placeholder: 'Enter a Player',
secondaryPlaceholder: '+Player',
onChipAdd: (element: Element, chip: Element) => updatePlayers(element, chip),
onChipDelete: (element: Element, chip: Element) => updatePlayers(element, chip)
});
};
const updatePlayers = (element: Element, chip: Element) => {
//#ts-ignore
const chipData = element[0].M_Chips.chipsData.map(item => item.tag);
setNewGame(newGame => ({
...newGame,
players: chipData
}));
}
const resetState = () => setNewGame({
players: [],
type: ""
});
useEffect(() => {
handleContentLoaded();
}, []);
console.log("New Game", newGame);
return (
<>
<div className={modalOpen ? "custom-modal" : "custom-modal closed"}>
<div className="card modal-start-container">
<div className="card-content">
<span className="card-title">New Game</span>
<div className="input-field col s12">
<select value={newGame.type} onChange={(event) => {
setNewGame(newGame => ({
...newGame,
type: event.target.value
}))
}}>
<option value="" disabled>Choose a game</option>
<option value="rummy">Rummy</option>
<option value="upAndDown">Up and Down</option>
</select>
<label>Game Type</label>
</div>
<div className="chips chips-placeholder"></div>
</div>
<div className="card-action">
<a
onClick={() => {
console.log("on click: ", newGame);
dispatch({
type: "addGame",
payload: {
...newGame,
index: `${newGame.type}${Date.now()}`
}
});
setModalOpen(false);
resetState();
}}
className="waves-effect waves-green btn-flat">Start</a>
</div>
</div>
</div>
<div className={`container ${modalOpen ? "blur" : ""}`}>
<blockquote>
<h4>"Weird how the scorekeeper always wins..."</h4>
<p>- every sore loser everywhere</p>
</blockquote>
<div className="flex-col">
<p className="flow-text">
Today it's easier to find a tablet or phone, than pen and paper. So
we've created an app that will let you keep score of all your
favorite card games, from your favorite digital device.
</p>
<a className="waves-effect waves-light btn" onClick={() => setModalOpen(true)}>
New Game
</a>
</div>
</div>
</>
);
}
Link to code in the TypeScript playground

Related

How can I call an API and use .map in a filter component?

I'm trying to call an API to my filter component, which it will shows in the component a Carrousel with all the filters (located at the API). The filter component will be called in a father component (where it will be the filter functions).
OBS: in the father component it is called also Cards Components (the cards that will be show depending on the filter selection).
Something like that:
Here are the codes:
Filter Component
import React from "react";
import Carrousel from "react-elastic-carousel";
import "./filtro.css";
function Filtro({ filtros, itensFiltrado }) {
const breakPoints = [
{ width: 1, itemsToShow: 1, pagination: false },
{ width: 600, itemsToShow: 2, pagination: false },
{ width: 860, itemsToShow: 5, pagination: false },
{ width: 1100, itemsToShow: 5, pagination: false },
{ width: 1700, itemsToShow: 6, pagination: false },
];
return (
<div className="filtro__segmento">
<Carrousel breakPoints={breakPoints}>
{filtros.map((filtro, id) => {
return (
<label
className="card__segmento"
key={id}
onClick={() => itensFiltrado(filtro)}
>
<h2 className="segmento__titulo">{filtro}</h2>
<input type="radio" className="input-radio" name="teste" />
<span className="check"></span>
</label>
);
})}
</Carrousel>
</div>
);
}
export default Filtro;
Father component
import React, { useEffect, useState } from "react";
import "./popupOrcamentoSimultaneo.css";
/* import profissionais from "../../../../utils/data-popupOrcamento"; */
import Filtro from "./Filtro";
import Card from "./Card";
import api from "../../../../api";
const allFilters = [
"Todos",
...new Set(api.map((profissional) => profissional.segmento)),
];
function PopupOrcamentoSimultaneo({ setOpenPopup, checkedState }) {
/* const [cardProfissionais, setCardProfissionais] = useState(profissionais); */
const [Profissionais, setProfissionais] = useState([]);
const [filtroProfissionais, setFiltroProfissionais] = useState([]);
const [filtros, setFiltros] = useState(allFilters);
const [value, setValue] = useState(
"'Olá! Vamos nos casar e gostariamos de mais informações sobre seu serviço."
);
useEffect(() => {
api.get("profissionais/listarTodos/").then(({ data }) => {
setProfissionais(data);
setFiltroProfissionais(data);
});
}, []);
let cardsProfissionais = Profissionais;
let listaCardsProfissionais = [];
for (let i = 0; i < cardsProfissionais.length; i++) {
listaCardsProfissionais.push(
<Card dadosProfissionais={cardsProfissionais[i]} />
);
}
const itensFiltrado = (segmentoEmpresa) => {
if (segmentoEmpresa === "Todos") {
setFiltroProfissionais(api);
return;
}
const novosProfissionais = api.filter(
(profissional) => profissional.segmento === segmentoEmpresa
);
setFiltroProfissionais(novosProfissionais);
};
const changeValue = (e) => {
setValue(e.target.value);
};
return (
<div className="popup">
<div className="popup__container">
<div className="close-container" onClick={() => setOpenPopup(false)}>
<i className="fa-solid fa-xmark"></i>
</div>
<h2 className="popup__titulo">
Solicite orçamento à outros profissionais simultaneamente!
</h2>
<Filtro filtros={filtros} itensFiltrado={itensFiltrado} />
<h3 className="popup__subtitulo">
Profissionais recomandados para você.
</h3>
<div className="cards__profissionais">{listaCardsProfissionais}</div>
<div className="input-box">
<label for="input" className="input-label">
Escreva sua mensagem
</label>
<div>
<input
type="text"
id="input"
className="input-text"
placeholder="Olá! Vamos nos casar e gostariamos de mais informações sobre seu serviço."
value={value}
onChange={changeValue}
/>
<i className="fa-solid fa-pencil"></i>
</div>
</div>
</div>
</div>
);
}
export default PopupOrcamentoSimultaneo;
If necessary, here is the Card Component:
import React, { useState } from "react";
import "./card.css";
function Card(props) {
let nomeEmpresa = props.dadosProfissionais.nomeEmpresa;
let segmento = props.dadosProfissionais.segmento;
let valorMinimo = props.dadosProfissionais.valorMinimo;
let cidade = props.dadosProfissionais.cidade;
let estado = props.dadosProfissionais.estado;
let idProfissional = props.dadosProfissionais.idProfissional;
let imagemVitrine = props.dadosProfissionais.imagemMarketplace;
let nomeArquivo = imagemVitrine ? imagemVitrine : "no-image.png";
/* const imagemVitrineProfissional = require(`../../../../../fileContents/imagensVitrineProfissional/${imagemVitrine}`); */
const [checkedState, setCheckedState] = useState({});
const handleOnChange = (e) => {
setCheckedState((p) => ({ ...p, [e.target.name]: e.target.checked }));
};
return (
<>
<div className="card__profissional" key={idProfissional}>
<div className="card__header">
<img
className="card-imagem"
src={imagemVitrine}
alt={`${nomeEmpresa}`}
/>
</div>
<div className="card__body">
<h2 className="card__titulo">{nomeEmpresa}</h2>
<span className="tag">{cidade}</span>
<div className="checkbox">
<label className="checkbox-label">
Solicitar Orçamento
<input
type="checkbox"
className="checkbox-input"
name={nomeEmpresa}
value={nomeEmpresa}
checked={checkedState[nomeEmpresa] || false}
onChange={handleOnChange}
/>
<span className="checkmark"></span>
</label>
</div>
</div>
</div>
<button type="submit" className="botao-orcamento">
Solicitar Orçamento (
{Object.keys(checkedState).filter((i) => checkedState[i]).length})
</button>
</>
);
}
export default Card;
Here is the Error showed in the console:
OBS: I didn't use the .map to call the Cards, but I would like to use in the filter Component.
Notice that I could call the API in the Card component, and it worked allright. But when I try to call the API to the Filter Component, I'm getting an full white page in the browser.
Anyone can help me?
I allready try to do the same as the Card Component, but it didn't work. Maybe the difference is because in the Card, I didn't use the .map function, and in the Filter I want to use.

React parent component needs child function to return data

I think I need a call back function, but do not understand the proper syntax given a parent component calling a child function.
Here is the stripped down parent component followed by the function FilesUpload.
I need the File.Name from child returned and setState({fileName}) in parent component.
Hopefully painfully obvious to someone who knows how to do this.
Thank you in advance for solution.
Rob
#davidsz - any ideas?
...
//Stripped down ParentComponent.jsx
import React, { Component } from 'react'
import FilesUpload from "../Services/FilesUpload";
class ParentComponent extends Component {
constructor(props) {
super(props)
this.state = {
fileName: null
}
this.changefileNameHandler = this.changefileNameHandler.bind(this);
}
changefileNameHandler= (event) => {
this.setState({fileName: event.target.value});
}
componentDidMount(){
}
render() {
return (
<div>
<td>this.state.fileName </td>
<FilesUpload onUpdate={this.changefileNameHandler}/>
</div>
)
}
}
export default ParentComponent
//functional service FilesUpload.js
import React, { useState, useEffect, useRef } from "react";
import UploadService from "../Services/FileUploadService";
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const UploadFiles = () => {
const [selectedFiles, setSelectedFiles] = useState(undefined);
const [progressInfos, setProgressInfos] = useState({ val: [] });
const [message, setMessage] = useState([]);
const [fileInfos, setFileInfos] = useState([]);
const progressInfosRef = useRef(null)
useEffect(() => {
UploadService.getFiles().then((response) => {
setFileInfos(response.data);
});
}, []);
const selectFiles = (event) => {
setSelectedFiles(event.target.files);
setProgressInfos({ val: [] });
};
const upload = (idx, file) => {
let _progressInfos = [...progressInfosRef.current.val];
return UploadService.upload(file, (event) => {
_progressInfos[idx].percentage = Math.round(
(100 * event.loaded) / event.total
);
setProgressInfos({ val: _progressInfos });
})
.then(() => {
toast.info(file.name + " Uploaded")
setMessage((prevMessage) => ([
...prevMessage,
"Uploaded the file successfully: " + file.name,
]));
})
.catch(() => {
_progressInfos[idx].percentage = 0;
setProgressInfos({ val: _progressInfos });
setMessage((prevMessage) => ([
...prevMessage,
"Could not upload the file: " + file.name,
]));
});
};
const uploadFiles = () => {
const files = Array.from(selectedFiles);
let _progressInfos = files.map(file => ({ percentage: 0, fileName: file.name }));
progressInfosRef.current = {
val: _progressInfos,
}
const uploadPromises = files.map((file, i) => upload(i, file));
Promise.all(uploadPromises)
.then(() => UploadService.getFiles())
.then((files) => {
setFileInfos(files.data);
});
setMessage([]);
};
return (
<div>
{progressInfos && progressInfos.val.length > 0 &&
progressInfos.val.map((progressInfo, index) => (
<div className="mb-2" key={index}>
<span>{progressInfo.fileName}</span>
<div className="progress">
<div
className="progress-bar progress-bar-info"
role="progressbar"
aria-valuenow={progressInfo.percentage}
aria-valuemin="0"
aria-valuemax="100"
style={{ width: progressInfo.percentage + "%" }}
>
{progressInfo.percentage}%
</div>
</div>
</div>
))}
<div className="row my-3">
<div className="col-8">
<label className="btn btn-default p-0">
<input type="file" multiple onChange={selectFiles} />
</label>
</div>
<div className="col-4">
<button
className="btn btn-success btn-sm"
disabled={!selectedFiles}
onClick={uploadFiles}
>
Upload
</button>
</div>
</div>
{message.length > 0 && (
<div className="alert alert-secondary" role="alert">
<ul>
{message.map((item, i) => {
return <li key={i}>{item}</li>;
})}
</ul>
</div>
)}
<div className="card">
{/* <div className="card-header">List of Files</div> */}
<ul className="list-group list-group-flush">
{!fileInfos &&
fileInfos.map((file, index) => (
<li className="list-group-item" key={index}>
{/* <a href={file.url}>{file.name}</a> */}
</li>
))}
</ul>
</div>
<ToastContainer position="top-center" autoClose={1000}/>
</div>
);
};
export default UploadFiles;
...
I'm not quite sure I understand your question perfectly, but do you want to pass down the changefileNameHandler function as a prop to your FilesUpload functional component?
In this case you can just add props as a paremeter:
const UploadFiles = (props) => { ...
and call it wherever you need it:
props.onUpdate(event)

React array mapping, toggles all drop-downs on click, I want to open the dropdown for the clicked card only

TextQuoteCard
import React, {useRef, useState} from 'react'
import {Link} from "react-router-dom";
import {QuoteCardDropdown} from "../../utils/dropdowns";
export const TextQuoteCard = () => {
const [open, setOpen] = useState(false)
const toggle = () => setOpen(!open)
const [textQuote, setTextQuote] = useState([
{
userId: '123',
userName: 'Tr',
userImageUrl: 'https://qph.fs.quoracdn.net/main-thumb-892821828-200-lrcgeycqieflgsovvoxglqawinbcjhtv.jpeg',
quoteId: 'TQ122',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Jhon Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'If there’s no market, about finding market opportunities, or creating opportunities. If there’s no market, then you need to grow one',
quoteImageUrl: 'https://qph.',
bookmarkStatus: 2,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
},
{
userId: '124',
userName: 'nr',
userImageUrl: 'https://qph.fi.jpeg',
quoteId: 'TQ123',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Wall Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'Best thing to do. ',
quoteImageUrl: '',
bookmarkStatus: 1,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
}
])
const handleBookmark = (event) => {
console.log(event)
}
const idGetter = (id) =>{
console.log(id)
}
const test = Object.keys(textQuote).map(item => item)
console.log(test)
return(
<div>
{
textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(!open)}
>
options
</span>
</span>
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))
}
</div>
)
}
**
QuoteCardDropdown.js
import React, {useState} from 'react'
import {Link} from "react-router-dom";
import '../../global/assets/css/dropdowns.css'
export const QuoteCardDropdown = (props) => {
const [ddItems, SetDdItems] = useState([
{
ddOptionIcon: 'icon',
ddOptionText: 'Share',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
},
{
ddOptionIcon: 'icon',
ddOptionText: 'Bookmark',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
}
])
return (
<div>
<div className="quoteCardDropdownPrimaryContainer">
<div className="quoteCardDropdownPrimaryBody">
<div className="quoteCardDropdownPrimaryBodyInner">
{
ddItems.map(item => (
<Link to=
{
item.ddOptionText === 'Edit this Quote' ?
`${'edit/' + props.targetLink}` :
item.ddOptionText === 'Share' ?
`${'share/' + props.targetLink}` : ''
}
>
<div className="quoteCardDropdownContentWrapper">
<div className="quoteCardDropdownContentItem">
<div className="quoteCardDropdownItem" key={item.ddOptionText}>
{item.ddOptionText}
</div>
</div>
</div>
</Link>
))
}
</div>
</div>
</div>
<div className="quoteCardPointer" data-placement='top'> </div>
</div>
)
}
I have array of objects mapping to which showed multiple card on-page/feed. each card has a dropdown that the user can perform several actions for the clicked card. think of FB feed or any other social media feed card that the user can click to open a dropdown and pick option for the card. I am trying to achieve something similar but the problem is when I click on the button to open the dropdown it toggles all the dropdowns for all the cards instead of opening the dropdown for the clicked card.
Expected Behavior: open the dropdown for the clicked card only.
Change the open to take the id:
const [open, setOpen] = useState() // undefined is nothing open
const toggle = id => setOpen(open === id ? undefined : id) // close if currently open
// the JSX
return(
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}>
options
</span>
</span>
{open === quote.quoteId && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))}
</div>
)
Yes, you are trying to control all dropdowns using a single state variable open.
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
When you click on any dropdown it will toggles open and then all dropdowns will open because that single variable controls all of them.
Instead, what you can do is maintain a separate state variable for each dropdown.
I have an example to maintain separate state variable for dropdown-
toggle = (index) => {
this.setState(prevState => {
[`open+${index}`]: !prevState[`open${index}`]
}
}
This way you can keep track of/or toggles open for particular dropdown you just need to change below code -
{
textQuote.map((quote, index) => ( //add 2nd parameter as index
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => this.toggle(index)}
>
options
</span>
</span>
{ this.state[`open${index}`] && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
</div>
))
}
Note - Unfortunately I am not aware of handling state as dynamically inside the function component, but I have given you the exact same use case using class component.
Use an object indexed by quote ID to track a card's open status by the quote ID. I copied only the relevant code, make sure this has all of the other code you need:
export const TextQuoteCard = () => {
const [openById, setOpenById] = useState({});
const toggle = (quoteId) => {
setOpenById((currOpenById) => {
const nextOpenById = { ...currOpenById };
if (currOpenById[quoteId]) {
delete nextOpenById;
} else {
nextOpenById[quoteId] = true;
}
return nextOpenById
})
}
// ...removed code not relevant to example
return (
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span
className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}
>
options
</span>
</span>
{openById[quote.quoteId] && <QuoteCardDropdown targetLink={quote.quoteId} />}
</div>
</div>
))}
</div>
);
};

React Hook select multiple items with icons

I am trying to implement a popover with many items where a user can multi select them. When a user clicks a item, a font-awesome icon is shown to the right.
A user is able to select multiple items and an icon is shown on the right to show it has been checked. This icon toggles when clicking. My problem is my event handler is tied to all the items and whenever I click one, all gets checked.
I am new to hook and react. I am also trying to assign the Id of the selected item in an array. It won't append.
const SettingsComponent = (props) => {
const urlStofTyper = stofTyperUrl;
const stofTyper = [];
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [isItemChecked, setItemChecked] = useState(false);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(null);
const [stoftype, setStoftyper] = useState({ DataList: [] });
const toggle = () => setPopoverOpen(!isPopoverOpen);
const sectionClicked = (e) => {
setItemChecked(!isItemChecked);
let secId = e.target.parentNode.getAttribute("data-section");
if (!isItemChecked) {
stofTyper.push(secId);
} else {
stofTyper.filter((sec) => sec == secId);
}
};
useEffect(() => {
fetchStoftyper({ setError, setLoading, setStoftyper });
}, []);
const fetchStoftyper = async ({ setError, setLoading, setStoftyper }) => {
try {
setLoading(true);
const response = await Axios(urlStofTyper);
const allStofs = response.data;
setLoading(false);
setStoftyper(allStofs);
} catch (error) {
setLoading(false);
setError(error);
}
};
return (
<React.Fragment>
<div className='list-header-icons__fontawesome-icon' id='PopoverClick'>
<FontAwesomeIcon icon={faCog} />
</div>
<Popover
isOpen={isPopoverOpen}
placement='bottom'
toggle={toggle}
target='PopoverClick'>
<PopoverHeader>formatter</PopoverHeader>
<div className='popover-body'>
<ul className='individual-col--my-dropdown-menu-settings'>
{stoftype.DataList.map((item) => (
<li key={item.Id} className='section-items'>
<a
onClick={sectionClicked}
className='dropdown-item'
data-section={item.Sections[0].SectionId}
data-format={
item.Formats.length > 0
? item.Formats[0].FormatId
: ""
}
aria-selected='false'>
<span className='formatter-name'>
{item.Name}
</span>
{isItemChecked && (
<span className='formatter-check-icon'>
<FontAwesomeIcon icon={faCheck} size='lg' />
</span>
)}
</a>
</li>
))}
</ul>
</div>
</Popover>
</React.Fragment>
);
Right now you are using one boolean variable to check if the icon should be displayed, it will not work because every item in your DataList should have it's own individual indicator.
One of the possible solution is to use new Map() for this purposes and store item.id as and index and true/false as a value, so your selected state will be something like this:
Map(3) {1 => true, 2 => true, 3 => false}
After that you can check if you should display your icon as follow:
!!selected.get(item.id)
It will return true if the value in your HashTable is true, and false if it's false or doesn't exist at all. That should be enough to implement the feature you asked for.
For the real example you can check flatlist-selectable section from official Facebook docs They show how to achieve multi-selection using this technique. Hope it helps.
Finally, I have a solution for my own question. Even though, I did not need above solution but I though it would be a good practice to try solve it. Popover is not relevant as that was used only as a wrapper. The below solution can still be placed in a Popover.
CodeSandBox link: Demo here using Class component, I will try to re-write this using hooks as soon as possible.
This solution dependency is Bootstrap 4 and Fontawesome.
import React, { Component } from "react";
import Cars from "./DataSeed";
class DropDownWithSelect extends Component {
constructor(props) {
super(props);
this.toggleDropdown = this.toggleDropdown.bind(this);
this.state = {
isDropDownOpen: false,
idsOfItemClicked: []
};
}
toggleDropdown = evt => {
this.setState({
isDropDownOpen: !this.state.isDropDownOpen
});
};
toggleItemClicked = id => {
this.setState(state => {
const idsOfItemClicked = state.idsOfItemClicked.includes(id)
? state.idsOfItemClicked.filter(x => x !== id)
: [...state.idsOfItemClicked, id];
return {
idsOfItemClicked
};
});
};
render() {
return (
<div className="dropdown">
<button
onClick={this.toggleDropdown}
className="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Dropdown button
</button>
{this.state.isDropDownOpen && (
<div
className="dropdown-menu show"
aria-labelledby="dropdownMenuButton"
>
{Cars.map(x => {
return (
<div
key={x.id}
onClick={() => this.toggleItemClicked(x.id)}
className="dropdown-item d-inline-flex justify-content-between"
>
<span className="d-flex"> {x.name} </span>
<span className="d-flex align-self-center">
{this.state.idsOfItemClicked.includes(x.id) && (
<i className="fa fa-times" aria-hidden="true" />
)}
</span>
</div>
);
})}
</div>
)}
</div>
);
}
}
export default DropDownWithSelect;
Data i.e., ./DataSeed
const cars = [
{
id: 1,
name: "BMW",
cost: 450000
},
{
id: 2,
name: "Audi",
cost: 430000
},
{
id: 3,
name: "Mercedes",
cost: 430000
}
];
export default cars;

How to use map function for hooks useState properties

I tried to use map function for looping my list data in react-hooks useState but I stuck with an error that "TypeError: Cannot read property 'map' of undefined"
//1.Initial declaration
const App = props=> {
const [state, changeState]= useState ({
name:"",
eventTitle:"",
details:"",
objdata:{},
list:[],
toggleIndex:"",
editName: "",
editEventTitle: "",
editDetails: "",
editObj: {}
});
//2.logic comes here
//3.tried using map
{(state.list.map((data,id)=>{
console.log ('loop data',data)
}))}
As we suspected you are not setting your state in the right way. I tried to explain in my comment, with hooks when you set your state it does not merge the updated properties with the current one. So, you should think about that. Right now you are setting your state like that:
const handleName = name => {
changeState({
name: name.target.value
});
};
Here, you are setting the name property and lose other parts of your state. Hence, when you set your state, you lose list as well as other parts of your state. This is how you should do it:
const handleName = name => {
const { target } = name;
changeState(state => ({
...state,
name: target.value,
}));
};
You take the old state, keep the other properties by spreading it, then update the relevant part. I would use here event instead of name. It is not "name", it is "event" after all actually :)
const handleName = event => {
const { target } = event;
changeState(state => ({
...state,
name: target.value,
}));
};
Also, you have a few other problems and unnecessary parts in your code. For example, you are struggling too much to handle the submit and add an object to your list. You don't need an extra objdata in your state to push it to the list. If you want to construct an extra object, you can do it in the function itself.
Here is a very simple way to do it:
const submitHandle = () => {
const { name, eventTitle, details } = state;
const obj = { name, eventTitle, details };
changeState(state => ({
...state,
list: [ ...state.list, obj ],
}))
};
Again, we are using spread operator to keep both the other parts of the state and while updating the list, to keep other objects. Do not set your state as you do in your submitHandle function. Try to think it simple :)
Also, you don't need to bind your functions when it is not necessary. You can find a working copy of the code below. I just removed unnecessary parts and fix the issues.
import React, { useState } from "react";
import ReactDOM from "react-dom";
const App = props => {
const [state, changeState] = useState({
name: "",
eventTitle: "",
details: "",
list: [],
toggleIndex: "",
editName: "",
editEventTitle: "",
editDetails: "",
editObj: {}
});
const handleName = event => {
const { target } = event;
changeState(state => ({
...state,
name: target.value
}));
};
const handleEventTitle = event => {
const { target } = event;
changeState(state => ({
...state,
eventTitle: target.value
}));
};
const handleDetails = event => {
const { target } = event;
changeState(state => ({
...state,
details: target.value
}));
};
const submitHandle = () => {
const { name, eventTitle, details } = state;
const obj = { name, eventTitle, details };
changeState(state => ({
...state,
list: [...state.list, obj]
}));
};
const resetHandle = () =>
changeState(state => ({
...state,
name: "",
eventTitle: "",
details: ""
}));
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container">
<h1 className="display-5 text-center">Let's set your reminders</h1>
</div>
</div>
<div className="bg-dark container-fluid">
<div className="row">
<div className="col-sm-12 col-md-4 col-lg-4 " />
<div className="col-sm-12 col-md-4 col-lg-4 ">
<div className="card login-card ">
<div className=" card-header ">
<h3 className="text-center"> TO-DO LIST FORM</h3>
</div>
<div className="card-body">
<form className="form-elements">
<input
value={state.name}
className="form-control form-inputs form-elements"
type="text"
onChange={handleName}
placeholder="user name"
/>
<input
value={state.eventTitle}
className="form-control form-inputs form-elements"
type="text"
onChange={handleEventTitle}
placeholder="Event Title"
/>
<input
value={state.details}
className="form-control form-inputs form-elements"
type="text"
onChange={handleDetails}
placeholder="Details "
/>
</form>
</div>
<div className="card-footer ">
<button
type="submit"
onClick={submitHandle}
className="btn-primary offset-lg-1 offset-md-0 btn-sm "
>
Create
</button>
<button
type="reset"
onClick={resetHandle}
className="btn-primary offset-lg-5 offset-md-0 btn-sm"
>
cancel
</button>
</div>
</div>
</div>
<div className="col-sm-12 col-md-4 col-lg-4 " />
</div>
<div className="container-fluid bg-dark">
<div className="row ">
{state.list.map(data => (
<div style={{ border: "1px black solid" }}>
<p>{data.name}</p>
<p>{data.eventTitle}</p>
<p>{data.details}</p>
</div>
))}
</div>
</div>
</div>
<div
className="footer footer-copyright"
style={{ background: "#e9ecef" }}
>
<div className="container">
<h6 className=" text-center">Just make it work ;)</h6>
</div>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
if your list is empty you will get this error just check if list is empty or not to solve the problem:
{if(state.list){
state.list.map((data,id)=>{
console.log ('loop data',data)
})
}}

Resources