useEffect(() => {
changeColumnsState(mapOrder({array: columnsState, order: columnOrder, key: 'name'}))
}, [JSON.stringify(columnOrder)])
What is the best way to check if array changed? Not length, but order
columnOrder is array of numbers.
export const ColumnsCustomizeModalForm: FunctionComponent<
ColumnsCustomizeModalFormProps
> = ({ columns, onClose, modalProps }) => {
const columnInitial = columns.map((column) => column.name)
const [columnOrder, setColumnOrder] =
useState<string[]>(columnInitial)
const [columnsState, changeColumnsState] = useState(mapOrder({array: columns, order: columnOrder, key: 'name'}))
useEffect(() => {
changeColumnsState(mapOrder({array: columnsState, order: columnOrder, key: 'name'}))
}, [JSON.stringify(columnOrder)])
const setColumnVisibility = (name: string) => {
const newColumnsState = columnsState.map((column) => {
if(column.name === name) {
return {...column, isHidden: !column.isHidden}
}
return column
})
changeColumnsState(newColumnsState)
}
return (
<ColumnsCustomizationModalView
columns={columnsState}
handleChangeOrder={setColumnOrder}
handleColumnVisibility={setColumnVisibility}
handleClose={onClose}
modalProps={modalProps}
/>
)
}
export const mapOrder = <ObjectType, KeyType extends keyof ObjectType>({array, order, key}: {array: ObjectType[], order: ObjectType[KeyType][], key: KeyType}) => {
const sortedArray = array.sort( function (a, b) {
var A = a[key], B = b[key];
if (order.indexOf(A) > order.indexOf(B)) {
return 1;
} else {
return -1;
}
});
return sortedArray;
};
What is the best way to check if array changed?
The best way is not to check at all. You don't need that columnOrder array state and an initialisation and an effect depending on it. Just directly set the order of your columnsState array in the handler function:
export const ColumnsCustomizeModalForm: FunctionComponent<
ColumnsCustomizeModalFormProps
> = ({ columns, onClose, modalProps }) => {
const [columnsState, changeColumnsState] = useState(columns);
const setColumnOrder = (columnOrder: string[]) => {
changeColumnsState(array => mapOrder({array, order: columnOrder, key: 'name'}))
};
const setColumnVisibility = (name: string) => {
changeColumnsState(cols => cols.map(column => {
if (column.name === name) {
return {...column, isHidden: !column.isHidden}
}
return column
}));
};
return (
<ColumnsCustomizationModalView
columns={columnsState}
handleChangeOrder={setColumnOrder}
handleColumnVisibility={setColumnVisibility}
handleClose={onClose}
modalProps={modalProps}
/>
);
}
Also fix your mapOrder function to create a new array (and use correct comparison):
export function mapOrder<ObjectType, KeyType extends keyof ObjectType>({array, order, key}: {array: ObjectType[], order: ObjectType[KeyType][], key: KeyType}) {
return array.slice().sort((a, b) =>
// ^^^^^^^^
order.indexOf(a[key]) - order.indexOf(b[key])
);
}
Related
I am trying to create a hook that takes in a set of props, including a searchQuery, filters, and searchKeys (for searching through data). It will then filter through the data based on the searchQuery and filters and return the data that is filtered.
The problem is that useEffect is going into an infinite loop and I'm not sure why.
import React, { useEffect, useState } from 'react';
type PropsType<T> = {
searchQuery?: string;
filters?: Array<{ filter: string, filterKey: keyof T }>;
searchKeys?: (keyof T)[];
} & (
{ searchQuery: string, searchKeys: (keyof T)[] } |
{ filters: Array<{ filter: string, filterKey: keyof T }> }
)
const useHandleData = <T>(props: PropsType<T>, data: Array<T>) => {
const { searchQuery, filters = [], searchKeys = [] } = props;
const [displayData, setDisplayData] = useState<Array<T>>([]);
useEffect(() => {
let filteredData = data;
if (searchQuery && searchKeys.length > 0) {
filteredData = searchThroughData(filteredData, searchQuery, searchKeys);
}
if (filters.length > 0) {
filters.forEach(filter => {
filteredData = filterThroughData(filteredData, filter.filter, filter.filterKey);
});
}
setDisplayData(filteredData);
}, [searchQuery, filters, data]);
return { displayData };
}
const searchThroughData = <T>(data: Array<T>, searchQuery: string, searchKeys: (keyof T)[]) => {
if (!searchQuery || searchKeys.length === 0) return data;
return data.filter(item => {
for (let key of searchKeys) {
if (item[key]?.toString().toLowerCase().includes(searchQuery.toString().toLowerCase())) return true;
}
return false;
});
}
const filterThroughData = <T>(data: Array<T>, filter: string, filterKey: keyof T) => {
if (!filter || !filterKey) return data;
return data.filter(item => item[filterKey] === filter);
}
export { useHandleData };
const [searchQuery, setSearchQuery] = useState("")
const [filterQuery, setFilterQuery] = useState<{ filter: string, filterKey: keyof item }[]>([])
const { displayData } = useHandleData({
searchQuery: searchQuery,
searchKeys: ['name', 'itemName'],
filters: filterQuery
}, itemList)
I'm new to the JavaScript and I face a problem. I don't understand how to create an event which will remove items from the Cart page. I created addCartEvent but I don't know how to remove products. I already created minus button component. In project I'm using Firebase.
Here's a code from the Cart page:
export default function CartPage() {
const { user, loading } = useAuth();
const { data } = useCart();
const cartLength = Object.keys(data).reduce((a, b) => a + data[b].length, 0);
const cartItems =
cartLength > 0
? Object.keys(data)
.map((item) => {
return data[item].map((size) => {
return {
name: item,
size,
};
});
})
.flat(1)
: [];
const sizeCount = cartItems.reduce(
(acc, value) => ({
...acc,
[value.name + '__size__' + value.size]:
(acc[value.name + '__size__' + value.size] || 0) + 1,
}),
{}
);
const cartItemsArray = [
...new Set(
cartItems.filter(
(v, i, a) =>
a.findIndex((t) => t.name === v.name && t.size === v.size) === i
)
),
].map((item) => {
return { ...item, count: sizeCount[item.name + '__size__' + item.size] };
});
const addCartEvent = (id, size) => {
const newCart = size
? {
...data,
[id]: data.hasOwnProperty(id) ? [...data[id], size] : [size],
}
: {
...data,
[id]: data.hasOwnProperty(id) ? [...data[id], '-'] : ['-'],
};
addToCart(newCart);
};
const router = useRouter();
}
And code from the firebase/product.js:
import { auth, db } from '../config/firebase';
function addToCart(newCart) {
const currentUser = auth.currentUser.uid;
return db.collection('Users').doc(currentUser).update({
cart: newCart,
});
}
export { addToCart };
I have created two tabs that when clicked need to show a different set of products and a different set of filters for each selection. My problem is that when I click either tab and call setOptions within changeTab, I need to click each tab twice before it will update 'options', 'options' needs to contain each filter.
Obviously calling setOptions within the click handler is not correct but I can't figure out where or how to correctly update 'options'. Help greatly appreciated.
In the console logs below 'dedupedOptions' updates correctly on click
function filterProducts() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const {
productData: {
products: { products }
}
} = useContext(AppContext);
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
let dedupedOptions = [];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!dedupedOptions.find((dedupedOption) => dedupedOption.value === value)
) {
dedupedOptions = [
...dedupedOptions,
{
label: titleCase(value),
value,
selected: false
}
];
}
});
});
const [options, setOptions] = useState(dedupedOptions);
console.log(dedupedOptions);
console.log(options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
setOptions(dedupedOptions);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
}
And the two elements set up as tabs to handle the click events. These are rendered within the same filterProducts function.
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
I reproduced your question by some changes in variable declarations in state.
be careful to declare variables in state and do the updates by listening the variable changes inside the useEffect.
here is the working code:\
https://codesandbox.io/s/quirky-http-e264i?file=/src/App.js
import "./styles.css";
import { useState, useContext, useCallback, useEffect } from "react";
export default function App() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const [productsByCategory, setProductsByCategory] = useState([]);
const [dedupedOptions, setDedupedOptions] = useState([]);
const [options, setOptions] = useState(dedupedOptions);
const products = [
{ tags: ["category:Feline"], name: "one" },
{ tags: ["category:Canine"], name: "two" }
];
useEffect(() => {
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
setProductsByCategory(productsByCategory);
}, [categoryType]);
useEffect(() => {
let tmp_dedupedOptions = [];
const tagKeysToDisplay = ["category"];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!tmp_dedupedOptions.find(
(dedupedOption) => dedupedOption.value === value
)
) {
tmp_dedupedOptions = [
...tmp_dedupedOptions,
{
label: value,
value,
selected: false
}
];
}
});
});
setDedupedOptions(tmp_dedupedOptions);
setOptions(tmp_dedupedOptions);
}, [productsByCategory]);
console.log("options: ", options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
// }
return (
<div>
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
</div>
);
}
I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues
I have following data structure:
const library = [
{
procedureName:'Build Foundations',
id:1,
tasks:[
{
taskName:'dig at least 1m deep', isCompleted:false, procedureId:1
},
{
taskName:'At least 50m wide digging', isCompleted:false, procedureId:1
},
{
taskName:'Buy megazords', isCompleted:false, procedureId:1
}
]
},
{
procedureName:'Building the roof',
id:2,
tasks:[
{
taskName:'Constructed according to the project', isCompleted:false, procedureId:2
},
{
taskName:'Protect wood from humidity', isCompleted:false, procedureId:2
},
{
taskName:'Roof build with the correct angle', isCompleted:false, procedureId:2
}
]
}
]
I want my function onTaskToggle to setState of library.tasks.isCompleted like this: library.tasks.isCompleted: !library.tasks.isCompleted
(I want to be able to distinguish the click of certain item as well)
I have no idea how get to this state.
Should I build my data structure differently or is there something I am missing?
onTaskToggle = (task) => {
const findProcedure = library => library.filter(procedure => procedure.id === task.procedureId);
const findTask = tasks => tasks.filter(singleTask => singleTask.taskName === task.taskName);
const toggledTask = findTask(findProcedure(this.state.library)[0].tasks);
const procedureIndex = this.state.library.indexOf(findProcedure(this.state.library)[0]);
const toggledTaskIndex = this.state.library[procedureIndex].tasks.indexOf(toggledTask[0]);
const insideProcedure = this.state.library[procedureIndex];
const insideTask = this.state.library[procedureIndex].tasks.indexOf(toggledTask[0]);
this.setState({
// Stuck there ...library
})
}
I'm not sure if it is needed, but that's the way I'm passing the data:
// Main component
class App extends Component {
constructor(props){
super(props);
this.state = {
projects,
library,
}
}
onTaskToggle = (task) => {
const findProcedure = library => library.filter(procedure => procedure.id === task.procedureId);
const findTask = tasks => tasks.filter(singleTask => singleTask.taskName === task.taskName);
const toggledTask = findTask(findProcedure(this.state.library)[0].tasks);
const procedureIndex = this.state.library.indexOf(findProcedure(this.state.library)[0]);
const toggledTaskIndex = this.state.library[procedureIndex].tasks.indexOf(toggledTask[0]);
const insideProcedure = this.state.library[procedureIndex];
const insideTask = this.state.library[procedureIndex].tasks.indexOf(toggledTask[0]);
this.setState({
// ...library
})
}
render() {
const {projects, library} = this.state;
return (
<div>
<Procedures library={library} onTaskToggle={this.onTaskToggle}/>
</div>
);
}
}
export default class Procedures extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
{
<ProceduresList library={this.props.library} onTaskToggle={this.props.onTaskToggle}/>
}
</div>
);
}
}
//Mapping through procedures
const ProceduresList = props => {
const list = props.library.map(procedure => {
const { id, procedureName, tasks } = procedure;
return(
<div key={id}>
<h4>{procedureName} </h4>
<div><ListingTasks tasks={tasks} onTaskToggle={props.onTaskToggle}/></div>
</div>
)
})
return <div>{list}</div>
}
export default ProceduresList;
// Last Component where all the magic shall happen
const ListingTasks = (props) => {
const list = props.tasks.map(task => { // under this line goes mapping through procedures
const taskName = task.taskName;
return <div key={taskName} onClick={() => props.onTaskToggle(task)}>{taskName}</div>
})
return <div>{list}</div>
}
export default ListingTasks
onTaskToggle(task) {
const { library } = this.state
const procedure = Object.assign({}, library.find(proc => (proc.id === task.procedureId)))
const procedureIndex = library.findIndex(prec => prec.id === procedure.id)
procedure.tasks = procedure.tasks.map((tsk) => {
if (tsk.taskName === task.taskName) {
tsk.isCompleted = !tsk.isCompleted
}
return tsk;
})
library.splice(procedureIndex, 1, procedure)
this.setState({ library })
}