How to properly change the boolean inside of object in array? - reactjs

So, I'm trying toggle the Icon based on the isBadData per email data in the object of array. But I can't seem to find out how could save it back to the state so it can update the Icon image in LeadProfileComponent.
This is what it looks like:
checkIcon = isBadData: false
crossIcon = isBadData: true
Heres my code:
// ModalComponent.js
const [leadProfile, setLeadProfile] = useState([
{
id: 'd114877b-074b-4aa2-a3f0-3b9446885336',
firstName: 'wqe',
lastName: 'wqe',
name: 'wqe wqe',
email: [
{
type: 'personal',
address: 'qwe#hotmail.com',
valid_since: '2010-05-09',
isBadData: true,
},
{
type: 'personal',
address: 'wqe#hotmail.com',
valid_since: '2017-03-09',
isBadData: true,
},
{
type: 'personal',
address: 'wqe#aol.com',
valid_since: '2009-01-12',
isBadData: true,
},
],
},
]);
<LeadProfileComponent leadProfile={leadProfile} setLeadProfile={setLeadProfile} />
// LeadProfileComponent.js
const LeadProfileComponent = (props) => {
const handleChildEmail = (email, index) => {
props.setLeadProfile((prev: any) => {
const value = { ...prev[0].email[index] };
console.log('inside value');
console.log(value);
value.isBadData = !value.isBadData;
console.log(value);
// return prev;
return [value];
});
console.log('props.leadProfile');
console.log(props.leadProfile);
};
return (
<>
{
props.leadProfile.map((lead, index) => (
return(
<>
{lead.email.map(() => {
return (
<button
id="btnCheck"
onClick={() => {
handleChildEmail(email, index);
}}
>
<img
src={
email.isBadData !== true
? checkIcon
: closeIcon
}
/>
</button>
)
})}
</>
)
}
</>
);
}
Heres what it looks like when you console log inside of handChildEmail function:
As you can see, I was able to change the inside boolean of email[0], but I cant save it back to the leadProfile state since I have a missing part in the destructuring part

Break your components in smaller parts, and manage each email individually
LeadProfileEmailComponent.js
const LeadProfileEmailComponent = ({ initialEmailData, ...props }) => {
const [emailData, setEmailData] = useState(initialEmailData);
return (
<button
id="btnCheck"
onClick={() => {
setEmailData({
...emailData,
isBadData: !emailData.isBadData
});
}}
>
<img
src={
emailData.isBadData !== true
? checkIcon
: closeIcon
}
/>
</button>
)
}
Change this in LeadProfileComponent:
{lead.email.map((email) => {
return (
<LeadProfileEmailComponent initialEmailData={email} />
)
})}
The downside is, the state of the parent component will not be updated. However this is standard design pattern practise, you should not rely on the parent component data for this.

Related

How do I remove duplicate code from titles.map and titles.slice?

How do I remove duplicate code from titles.map and titles.slice?
how should be handled with en effect to set the facets that should be displayed. Functionality works as expected, I just want to remove duplicated code.
import { useState } from "react";
const App = () => {
const titles = [
{ id: "0", name: "Title" },
{ id: "5637144579", name: "Miss" },
{ id: "5637144576", name: "Mr." },
{ id: "5637145326", name: "MrandMrs." },
{ id: "5637144577", name: "Mrs." },
{ id: "5637144578", name: "Ms." },
{ id: "5637145330", name: "Br." },
{ id: "5637145327", name: "Dame" },
{ id: "5637144585", name: "Dr." },
{ id: "5637145331", name: "Fr." },
{ id: "5637144582", name: "I" },
];
const [isAllFacets, setIsAllFacets] = useState(false);
const MAX_FACET_COUNT = 5;
const visibleFacetCount = titles.length - 1 === MAX_FACET_COUNT ?
titles.length :
MAX_FACET_COUNT;
const showAllFacet = () => { setIsAllFacets(!isAllFacets); };
return (<>
{isAllFacets ?
titles.map((title: any) => {
return <div key={title.id}>{title.name}</div>;
}) :
titles.slice(0, visibleFacetCount).map((title) => {
return <div key={title.id}>{title.name}</div>;
})}
{titles.length > visibleFacetCount && (<>
{!isAllFacets ? (
<button onClick={showAllFacet}>show all</button>
) : (
<button onClick={showAllFacet}>show less</button>
)}
</>)}
</>);
};
export default App;
One option would be to use the conditional operator to slice the entire existing array in case isAllFacets is false - instead of alternating over the whole JSX to return, alternate over only the index to slice.
A similar approach can be used to simplify your <button> text.
return (
<>
{
titles
.slice(0, isAllFacets ? titles.length : visibleFacetCount)
.map(title => <div key={title.id}>{title.name}</div>)
}
{titles.length > visibleFacetCount && <button onClick={showAllFacet}>show{isAllFacets ? ' all' : ' less'}</button>}
</>
);
You can achieve your goal with useMemo hook. Prepare the data to display and render it. Value will be recalculated when anything inside depsArray is changed.
const titlesToDisplay = useMemo(() => {
return isAllFacets ? titles : titles.slice(0, visibleFacetCount);
}, [titles, isAllFacets, visibleFacetCount]);
return (
<>
{titlesToDisplay.map((title) => {
return <div key={title.id}>{title.name}</div>;
})}
{titles.length > visibleFacetCount && (
<>
{!isAllFacets ? (
<button onClick={showAllFacet}>show all</button>
) : (
<button onClick={showAllFacet}>show less</button>
)}
</>
)}
</>
);
you only need to put all together :
titles.slice(0, isAllFacets?titles.length:visibleFacetCount).map((title) => { return <div key={title.id}>{title.name}
</div>; })
Create a renderTitle function which returns the title div
const renderTitle = title => <div key={title.id}>{title.name}</div>
Then pass it to both .map invocations
titles.map(renderTitle)
and
titles.slice(0, visibleFacetCount).map(renderTitle)
And for your button, simplify it with:
<button onClick={showAllFacet}>show {isAllFacets ? "less" : "all"}</button>

Can I change a element state in react without changing every element state?

im making a portfolio website and have multiple different buttons with skills which contain an img and p tag. I want to show the description of each tag everytime a user clicks on the button. how can I do this? right now everytime user clicks it, all buttons show description.
const Skills = () => {
const [state, setState] = useState(false)
let skills = [
{ id: 1, desc: 'HTML5', state: false, img: htmlIcon },
{ id: 2, desc: 'CSS3', state: false, img: cssIcon },
{ etc....}
const showDesc = (id) => {
console.log(skills[id-1] = !state);
setState(!state)
}
return (
{skills.map(skill => (
<button onClick={(id) => showDesc(skill.id)}>
<img style={ state ? {display:'none'} : {display:'block'}} src={skill.img} />
<p style={ state ? {display:'block'} : {display:'none'}}>{skill.desc}</p>
</button>
))}
I recommend to manipulate element state instead of entry list. But if you really need to manipulate entry list you should add that list to your state. Then when you want to show/hide specific item, you need to find that item in state and correctly update entry list by making a copy of that list (with updated item). For example you can do it like this:
import React, { useState } from 'react';
const Skills = () => {
const [skills, setSkills] = useState([
{
id: 1,
desc: 'HTML5',
state: false,
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
state: false,
img: cssIcon, // your icon
},
]);
const showDesc = (id) => {
const newSkills = skills.map((item) => {
if (item.id === id) {
return {
...item,
state: !item.state,
};
}
return item;
});
setSkills(newSkills);
};
return (
<div>
{skills.map(({
id,
img,
state,
desc,
}) => (
<button type="button" key={id} onClick={() => showDesc(id)}>
<img alt="img" style={state ? { display: 'none' } : { display: 'block' }} src={img} />
<p style={state ? { display: 'block' } : { display: 'none' }}>{desc}</p>
</button>
))}
</div>
);
};
Instead of manipulating all list, you can try to move show/hide visibility to list item itself. Create separate component for item and separate component for rendering that items. It will help you to simplify logic and make individual component responsible for it visibility.
About list rendering you can read more here
For example you can try something like this as alternative:
import React, { useState } from 'react';
const skills = [
{
id: 1,
desc: 'HTML5',
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
img: cssIcon, // your icon
},
];
const SkillItem = ({
img,
desc = '',
}) => {
const [visibility, setVisibility] = useState(false);
const toggleVisibility = () => {
setVisibility(!visibility);
};
const content = visibility
? <p>{desc}</p>
: <img alt="img" src={img} />;
return (
<div>
<button type="button" onClick={toggleVisibility}>
{content}
</button>
</div>
);
};
const SkillList = () => skills.map(({
id,
img,
desc,
}) => <SkillItem img={img} desc={desc} key={id} />);

React hooks: Update an object value within an array in state

What's the best approach to update the values of objects within an array in the state? Can't really wrap my head around hooks yet. The class approach seems to be way clearer for me at least in this case
In the below situation I'd like to change the active value on click to false within the object and also add a date value of when that happened.
handleChangeStatus doesn't work at all, I just get the 'test' on click, no errors.
const App = () => {
const [tasks, setTasks] = useState([
{
text: 'Example 1',
id: 1,
urgent: true,
targetDate: '2021-07-16',
active: true,
finishDate: null,
},
{
text: 'Example 2',
id: 2,
urgent: false,
targetDate: '2021-06-03',
active: false,
finishDate: null,
},
{
text: 'Example 3',
id: 3,
urgent: false,
targetDate: '2021-07-16',
active: true,
finishDate: null,
},
]);
const handleChangeStatus = (id) => {
console.log('test');
const newArr = [...tasks];
newArr.forEach((task) => {
if (task.id === id) {
console.log(task.id);
task.active = false;
task.finishDate = new Date().getTime();
}
});
setTasks(newArr);
};
return (
<div className="App">
<AddTask />
<TaskList tasks={tasks} changeStatus={handleChangeStatus} />
</div>
);
};
TaskList
const TaskList = (props) => {
const active = props.tasks.filter((task) => task.active);
const done = props.tasks.filter((task) => !task.active);
const activeTasks = active.map((task) => (
<Task key={task.id} task={task} changeStatus={props.changeStatus} />
));
const doneTasks = done.map((task) => <Task key={task.id} task={task} />);
return (
<>
<h3>Active Tasks ({active.length})</h3>
<ul>{activeTasks}</ul>
<hr />
<h3>Done Tasks ({done.length})</h3>
<ul>{doneTasks}</ul>
</>
);
};
Task
const Task = (props) => {
const { text, id, urgent, targetDate, active } = props.task;
const style = { color: 'red' };
if (active) {
return (
<p>
<strong style={urgent ? style : null}>{text}</strong>, id: {id}, target
date: {targetDate} <button onClick={props.changeStatus}>Done</button>
</p>
);
} else {
return (
<p>
<strong style={urgent ? style : null}>{text}</strong>, id: {id}, target
date: {targetDate}
</p>
);
}
};
<button onClick={props.changeStatus}>Done</button>
You are sending event object to the function, try sending id
<button onClick={() => props.changeStatus(id)}>Done</button>
Per the React Docs
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
so you could do something like:
const handleChangeStatus = (id) => {
console.log('test');
setTask((prev)=>prev.map((task)=>{
if(task.id === id){
return {...task,active: false, finishDate: new Date().getTime()}
}
else{
return task;
}
})
}

MaterialUI Spinning loader help needed for React/Redux app

import { CircularProgress, FormControl, Input, InputLabel } from
'#material-ui/core';
function toKey(s) {
return s.split("_").map((s, i) => i > 0 ? s.slice(0,1).toUpperCase() +
s.slice(1, s.length) : s).join("")
}
Function to split the returned json object:
function toLabel(s) {
return s.split("_").map((s, i) => s.slice(0,1).toUpperCase() +
s.slice(1, s.length)).join(" ")
}
My class:
class Reports extends Component {
constructor(props) {
super(props);
this.state = {
report: '',
filename: 'my-data.csv',
isLoading: false,
tableHeaderData: [],
reports: [
{ name: 'C3 Report', id: 1, actOn: 'c3'},
{ name: 'C4 Report', id: 2, actOn: 'c4'},
{ name: 'C5 Report', id: 3, actOn: 'c5'}
],
categories: {name: 'Cat 1'},
catSelection: 'Select a Category',
repSelection: 'Select Report Type',
isReportSelected: false,
c4RptFirstInput: '',
c4RptSecondInput: ''
}
}
Not sure about this but went with convention:
componentDidMount () {
const {dispatch, id} = this.props;
}
handleChange (e) {
// this.setState({ input: e.target.value });
}
This is the plugin that I'm using to convert the page into a csv file:
csvHeader () {
const data = this.reportData()
if(data.length === 0) return []
const keys = Object.keys(data[0])
return keys.map((k) => {
const label = toLabel(k)
const key = toKey(k)
return { label, key }
})
}
csvData () {
const data = this.reportData()
if(data.length === 0) return []
const values = Object.entries(data);
const keys = Object.keys(data[0])
const rows = values.map(entries => {
const record = entries[1];
return keys.reduce((acc, key, i) => {
acc[toKey(key)] = record[key]
return acc
}, {})
});
return rows
}
Checks if report or package:
reportData(){
switch(this.state.report) {
case 'channels':
return this.props.channels
case 'packages':
return this.props.packages
default:
return []
}
}
Not sure about this placeholder function but copied it from somewhere:
placeholder () {
return (
<div>
<h1 className="display-3">Reports</h1>
<p className="lead" cursor="pointer" onClick=
{this.loadChannelData}>Svc Configuration</p>
</div>
);
}
Was experimenting with this function but wasn't sure how to use it:
componentWillReceiveProps() {
}
handleCategorySwitch = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value});
console.log(`name ${name}, value ${value}`);
}
This is where the 'subselection' of the second set of drop downs happens:
handleSubselection = (e) => {
this.setState({c4RptSecondInput: e.target.value, })
switch( e.target.value) {
case 'input3':
return this.props.ReportGetAllPackages()
}
}
handleReportSwitch = (e) => {
const selectedAction = e.target.value;
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels'
,isLoading: true
}), this.props.ReportGetAllChannels)
}
if (selectedAction == 'c4') {
this.setState(prevState => ({
report: 'packages'
}))
}
}
Render function:
render () {
const {filename, reports, catSelection, repSelection, isReportSelected,
c4RptFirstInput, c4RptSecondInput} = this.state;
return (
<div className="reports">
{this.placeholder()}
<div className="flexMode">
<span className="spanFlexMode">
<InputLabel htmlFor="catSelection"></InputLabel>
<Select value={catSelection} name={'catSelection'}
onChange={(e) => this.handleCategorySwitch(e)}>
<MenuItem value="select">Select Category</MenuItem>
<MenuItem value={'Cat1'}>Cat 1</MenuItem>
<MenuItem value={'Cat2'}>Cat 2 </MenuItem>
<MenuItem value={'Cat3'}>Cat 3 </MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Report Name:</label>
<Select value={repSelection} name="repSelection"
onChange={(e) => this.handleReportSwitch(e)}>
<MenuItem defaultValue={'select'}>Select
Report</MenuItem>
{reports && reports.map((report, index) => <MenuItem
key={index} value={report.actOn}>{report.name}</MenuItem>)}
</Select>
</span>
</div>
Below are the second set of drop downs that show up conditionally based on selection of a particular field from above select boxes:
{ this.state.report === 'packages' ? (
<div>
<span>
<label>Input 1:</label>
<Select name="c4RptFirstInput" value={c4RptFirstInput}
placeholder={'Select Provider'} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'Provider'}>Provider</MenuItem>
<MenuItem value={'Region'}>Region</MenuItem>
<MenuItem value={'Zone'}>Zone</MenuItem>
</Select>
</span>
<span className="spanFlexMode">
<label>Input 2:</label>
<Select name="c4RptSecondInput" defaultValue=
{c4RptSecondInput} value={c4RptSecondInput} onChange={(e) =>
this.handleSubselection(e)}>
<MenuItem value={'Def'}>Select</MenuItem>
<MenuItem value={'input2'}>Input 2</MenuItem>
<MenuItem value={'input3'}>Input 3</MenuItem>
<MenuItem value={'input4'}>Input 4</MenuItem>
</Select>
</span>
</div>
) : null}
<div>
<CSVLink data={this.csvData()} headers={this.csvHeader()}
filename={filename} target={'_blank'}>
<GetAppIcon />
</CSVLink>
Here is where the spinning loader should do it's thing and disappear once the data is loaded - currently it just keeps on spinning and the data never gets loaded even though I can see that the data has successfully come back from the reducer:
{isLoading
? <CircularProgress />
: (
<Table id="t1">
<TableHeaders data={this.csvHeader()} />
<TableContent data={this.csvData()} />
</Table>
)}
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
ReportGetAllChannels: () => dispatch(ReportGetAllChannels()),
ReportGetAllPackages: () => dispatch(ReportGetAllPackages()),
}
}
const defaultState = ({
state: {},
channels: [],
packages: []
,isLoading: false
})
const mapStateToProps = (state=defaultState) => {
return ({
state: state,
channels: state.RptDetailsReducer.data,
packages: state.RptPackagesReducer.data
,isLoading: false
})
}
isLoading variable is not defined in your render method. I see that you defined it in your component's state and inside your reducer. I assume you are referencing one in your state (Since you said it was keep spinning it is probably the case). You set component's isLoading to true in handleSubselection you have this snippet:
if (selectedAction == 'c3') {
this.setState(prevState => ({
report: 'channels',
isLoading: true
}), this.props.ReportGetAllChannels)
}
This code will set isLoading to true than dispatch ReportGetAllChannels. However your component's state won't be updated. I don't know what ReportGetAllChannels does but I am guessing it sets its own isLoading to false. Which is different variable.
Also you may want to read this https://overreacted.io/writing-resilient-components/#principle-1-dont-stop-the-data-flow. Once you map your state to props you usually want to pass them directly to child components.
Edit:
Quick fix: use this.props.isLoading instead of state, and set isLoading to true inside your dispatched action

How to collect Switch value in React JS + Ant Design

I'm sorry for my basic knowledge and my basic english :)
My Question is: How to concenate switch value [I use Switch for each record in table]
This is the table
https://ibb.co/3TVLBK6
I've created table with one cell/field using Switch (On/Off)
And i want get the switch value when they are on
And here is the code
const columnsTableDepartmentModal = [
{
title: 'No',
dataIndex: 'no',
key: 'no',
},
{
title: 'Department',
dataIndex: 'department',
key: 'department',
},
{
title: 'Select',
dataIndex: 'select_department',
key: 'select_department',
render: (e, record) => (
<Switch
defaultChecked={e}
onChange={
(value) => onChangeSwitch(value,record)
}
checkedChildren="Yes"
unCheckedChildren="No"
/>
),
}];
This is what i now try
function onChangeSwitch(isSelect,record){
console.log(e); // True / False
console.log(record); // True / False
if(isSelect){
// push data to array
}
if(!isSelect){
// pop data from array
}
}
This is how i show the table
<Modal
title={modalDepartmentTitle}
visible={visibleDepartment}
width={800}
onOk={handleOkDepartment}
onCancel={handleCancelDepartment}
footer={[
<Button key="submit" type="primary" onClick={handleOkDepartment}>OK</Button>,
<Button key="button" type="danger" onClick={handleDeleteDepartment}>DELETE</Button>,
<Button key="back" onClick={handleCancelDepartment}>CANCEL</Button>,
]}
>
<Table
columns={columnsTableDepartmentModal}
dataSource={stateDepartment.data}
pagination={false}
scroll={{y: 325}}
/>
</Modal>
Expected result: 1,3,4
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
import Switch from 'react-switch';
class App extends Component {
constructor() {
super();
this.state = {
checkedPos: [],
info: [],
};
}
componentWillMount()
{
let tmp = []
let pos = []
for (var i = 1; i < 6; i++) {
tmp.push("Info " + i)
pos.push(false);
}
this.setState({
info: tmp,
checkedPos: pos
})
}
handleChange(index, info)
{
if (!this.state.checkedPos[index])
{
this.setState(prev => ({
checkedPos: prev.checkedPos.map((val, i) => !val && i === index ? true : val),
}))
}
else if (this.state.checkedPos[index])
{
this.setState( prev => ({
checkedPos: prev.checkedPos.map((val, i) => val && i === index ? false : val),
}))
}
}
render() {
const listItems = this.state.info.map((item, index) =>
<div>
{item}
<Switch checked={this.state.checkedPos[index]}
onChange={ () => this.handleChange(index, this.state.info[index])}/>
{" Value is " + this.state.checkedPos[index]}
</div>
);
return (
<div>
{listItems}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Demo here
Acording to #jose answer i've implemented to hook concept with this code
const [theArray, setTheArray] = useState([]);
const addEntry = useCallback((value) => {
setTheArray([...theArray, `${value}`]);
});
Then in function we can add value
function onChangeSwitch(isSelect,record){
console.log(isSelect); // True / False
console.log(record);
addEntry(record.no);
if(isSelect){
// push data to array
}
if(!isSelect){
// pop data from array
}
}
So when we display {enty}
<div key="adkfkdfkda">{theArray.map(entry =>
<span key={entry}>{entry},</span>
)}
</div>
We got value merged in string format
https://ibb.co/nQFK1C4
Thanks.

Resources