How to read form data from the child into the parent? - reactjs

I'm using a manually crafted form generator and using React Final Form to manage the state and data. The problem now is, I need from a component outside the form to read the data before is even submitted to show the user the actual status of how the input is looking.
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { I18n } from 'react-i18nify';
import * as INPUTTYPES from '../../constants/inputTypes';
import { CONCAT_ID_BASES } from '../../constants/config';
import 'babel-polyfill';
import { Form, Field } from 'react-final-form'
const weblog = require('webpack-log');
const log = weblog({ name: 'wds' }) // webpack-dev-server
class FormGenerator extends Component {
static propTypes = {
fields: PropTypes.any,
prefix: PropTypes.any,
children: PropTypes.any
}
state = {
data: {}
}
sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
onSubmit = async values => {
await this.sleep(300)
window.alert(JSON.stringify(values, 0, 2))
}
static simpleMemoize = fn => {
let lastArg
let lastResult
return arg => {
if (arg !== lastArg) {
lastArg = arg
lastResult = fn(arg)
}
return lastResult
}
}
static textValidate = FormGenerator.simpleMemoize(async value => {
if (!value) {
return I18n.t('error-no-text-written');
}
//await sleep(400)
if (value.trim().length() > 0) {
return I18n.t('error-no-text-found');
}
})
static createInput = (newKey, value, validate) => {
let data = {
type: value.type,
//disabled: typeof value.editable !== "undefined" ? !value.editable : false,
className: "form-control",
id: `${newKey}`,
value: value.value
}
return <Field key={newKey} name={data.id} validate={validate}>
{({ input, meta }) => (
<div className="form-group col-md-6">
<label htmlFor={`${newKey}`}>{I18n.t(`${newKey}`)}</label>
<input {...data} {...input} />
{meta.error && meta.touched && <span>{meta.error}</span>}
</div>
)}
</Field>
}
static createSelectInput = (newKey, value) => {
let data = {
type: value.type,
disabled: typeof value.editable !== "undefined" ? !value.editable : false,
className: "form-control",
id: `${newKey}`,
value: value.value
}
return <React.Fragment key={newKey}>
<div className="form-group col-md-6">
<label htmlFor={`${newKey}`}>{I18n.t(`${newKey}`)}</label>
<input {...data} />
</div>
</React.Fragment>
}
initialValues = function () {
let { prefix, fields } = this.props;
prefix = prefix ? prefix + CONCAT_ID_BASES : '';
fields ? fields.map((field) => {
const newKey = `${prefix}${field.key}`
this.setState((prevState) => {
let newData = { ...prevState.data };
newData[newKey] = field.value.value;
return { data: newData };
})
}) : null;
}
componentDidMount() {
this.initialValues();
}
componentDidUpdate() {
//console.log(this.state)
//this.props.suscribeCallback(values)
}
inputGenerator(field, prefix) {
const { key, value } = field;
const { type } = value;
const textValidate = FormGenerator.textValidate;
const newKey = `${prefix}${key}`
let element = null;
const createInput = FormGenerator.createInput;
switch (true) {
case new RegExp(INPUTTYPES.TEXT.join("|"), "i").test(type):
value.type = "text";
element = createInput(newKey, value, textValidate)
break;
case new RegExp(INPUTTYPES.NUMBER.join("|"), "i").test(type):
value.type = "number";
element = createInput(newKey, value, textValidate)
break;
case new RegExp(INPUTTYPES.SELECT.join("|"), "i").test(type):
break;
default:
log.error("DATA NOT ITENDIFIED TYPE:" + type, key, value);
break;
}
return element;
}
render() {
let fields = this.props.fields;
let { prefix } = this.props;
prefix = prefix ? prefix + CONCAT_ID_BASES : ''
const { inputGenerator, onSubmit } = this;
return (
<Form
onSubmit={onSubmit}
initialValues={this.state.data}
render={({ values }) => {
return <div className="form-row">
{fields.map((field) => {
return inputGenerator(field, prefix);
})}
<pre>{JSON.stringify(values, 0, 2)}</pre>
</div>
}} />
)
}
}
export default FormGenerator;
And calling it like this:
{
detalles ? (() => {
return <FormGenerator
suscribeCallback={this.formDataChange}
prefix={this.props.prefix}
fields={detalles} />
})() : null
}
But now the issue is, I need to read values outside the <Form/> so I can read it in it's parent.
If I include a call back and toss it into the render method this.props.suscribeCallback(values) it will try to call it so much it will crash the site. Of course this is not a valid solution but I don't know how to solve it.
I'm kind of new to Reactjs so appologize if this is a beginner's mistake

If you need form data outside of the form, I'd suggest using something like the patterns explored in the Redux Example, where a FormStateToRedux component is listening to changes in form state and sending it...somewhere. It needn't be to Redux.

Related

useState array as dependency causing infinite loop

I have been struggling with this for hours, i'm new to React and would appreciate any assistance.
I'm working on something where users can pick regions into an array.
My main problem is that i want the array that users choose to have unique values only.
I have tried using a javascript SET but that can't be mapped through. The array will be mapped through then displayed to the user.
And i have tried setting "if" statements, that check for duplicate values, inside useEffect but the dependency on a useState array creates an infinite loop.
I have read about using useRef on an array to avoid useEffect infinite loops but i find that its normally for static rather than changing arrays.
Below is the important part:
const [regions, setRegions] = useState([]);
const [region, setRegion] = useState("");
useEffect(() => {
if (region) {
if (regions.includes.region) {
return;
} else if (!regions.includes.region) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
}
// setRegions((previousState) => new Set([...previousState, region]));
}, [region, regions]);
The rest of the code for context:
import { useEffect, useState } from "react";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng,
} from "react-places-autocomplete";
export default function Test() {
const [address, setAddress] = useState("");
const [coordinate, setCoordinates] = useState({
lat: null,
lng: null,
});
const [regions, setRegions] = useState([]);
const [region, setRegion] = useState("");
useEffect(() => {
if (region) {
if (regions.includes.region) {
return;
} else if (!regions.includes.region) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
}
// setRegions((previousState) => new Set([...previousState, region]));
}, [region, regions]);
const handleSelect = async (value) => {
const result = await geocodeByAddress(value);
const full_region = result[0].formatted_address;
const part_region = full_region.substring(0, full_region.indexOf(","));
let province = "";
if (result[0].address_components[2].short_name.length <= 3) {
province = result[0].address_components[2].short_name;
} else {
province = result[0].address_components[3].short_name;
}
setAddress(value);
setCoordinates(coordinate);
setRegion(part_region.concat("-", province));
};
const onDelete = (e) => {
const value = e.target.getAttribute("value");
console.log("onDelete: ", value);
setRegions(regions.filter((item) => item !== value));
};
// setRegions(Array.from(new Set(regions)));
return (
<>
<PlacesAutocomplete
value={address}
onChange={setAddress}
onSelect={handleSelect}
searchOptions={{
componentRestrictions: { country: ["za"] },
types: ["(regions)"],
}}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: "Add regions...",
className: "location-search-input",
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map((suggestion) => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "orange", cursor: "pointer" }
: { backgroundColor: "silver", cursor: "pointer" };
return (
<div
key={suggestion.description}
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<p>Regions</p>
<ul>
{regions.map((region) => (
<li
// key={region}
title="remove"
className="cursor-pointer"
onClick={onDelete}
value={region}
>
{region}
</li>
))}
</ul>
</>
);
}
You are using .includes() incorrectly by trying to obtain region as a property: regions.includes.region
This results in:
the second condition else if (!regions.includes.region) always succeeding,
which then results in the state change setRegions() being made,
which then triggers the [regions] in the dependency,
which then loops the useEffect() again, and again.. ..infinitely.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
Instead, it should be passed as a parameter to the method: if(regions.includes(region)) and if(!regions.includes(region))
useEffect(() => {
if (region) {
if (regions.includes(region)) {
return;
}
if (!regions.includes(region)) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
}
}, [region, regions]);
You could probably also simplify it by only modifying the state if the condition doesn't succeed:
useEffect(() => {
if (region) {
if (!regions.includes(region)) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
// else do nothing
}
}, [region, regions]);

React | Collect State Values of Children Array and update Per Object and Save to PouchDB

Stackoverflow
problem
I have separate components that house Tiptap Editor tables. At first I had a save button for each Child Component which worked fine, but was not user friendly. I want to have a unified save button that will iterate through each child Table component and funnel all their editor.getJSON() data into an array of sections for the single doc object . Then finish it off by saving the whole object to PouchDB
What did I try?
link to the repo → wchorski/Next-Planner: a CRM for planning events built on NextJS (github.com)
Try #1
I tried to use the useRef hook and the useImperativeHandle to call and return the editor.getJSON(). But working with an Array Ref went over my head. I'll post some code of what I was going for
// Parent.jsx
const childrenRef = useRef([]);
childrenRef.current = []
const handleRef = (el) => {
if(el && !childrenRef.current.includes(el)){
childrenRef.current.push(el)
}
}
useEffect(() =>{
childrenRef.current[0].childFunction1() // I know this doesn't work, because this is where I gave up
})
// Child.jsx
useImperativeHandle(ref, () => ({
childFunction1() {
console.log('child function 1 called');
},
childFunction2() {
console.log('child function 2 called');
},
}))
Try #2
I set a state counter and passed it down as a prop to the Child Component . Then I update the counter to trigger a child function
// Parent.jsx
export const Planner = ({id, doc, rev, getById, handleSave, db, alive, error}) => {
const [saveCount, setSaveCount] = useState(0)
const handleUpdate = () =>{
setSaveCount(prev => prev + 1)
}
const isSections = () => {
if(sectionsState[0]) handleSave(sectionsState)
if(sectionsState[0] === undefined) console.log('sec 0 is undefined', sectionsState)
}
function updateSections(newSec) {
setsectionsState(prev => {
const newState = sectionsState.map(obj => {
if(!obj) return
if (obj.header === newSec.header) {
return {...obj, ...newSec}
}
// 👇️ otherwise return object as is
return obj;
});
console.log('newState', newState);
return newState;
});
}
useEffect(() => {
setsectionsState(doc.sections)
}, [doc])
return (<>
<button
title='save'
className='save'
onPointerUp={handleUpdate}>
Save to State <FiSave />
</button>
<button
style={{right: "0", width: 'auto'}}
title='save'
className='save'
onClick={isSections}>
Save to DB <FiSave />
</button>
{doc.sections.map((sec, i) => {
if(!sec) return
return (
<TiptapTable
key={i}
id={id}
rev={doc.rev}
getById={getById}
updateSections={updateSections}
saveCount={saveCount}
section={sec}
db={db}
alive={alive}
error={error}
/>
)
})}
</>)
// Child.jsx
export const TiptapTable = ((props, ref) => {
const {id, section, updateSections, saveCount} = props
const [currTimeStart, setTimeStart] = useState()
const [defTemplate, setdefTemplate] = useState('<p>loading<p>')
const [isLoaded, setIsLoaded] = useState(false)
const [notesState, setnotesState] = useState('')
const editor = useEditor({
extensions: [
History,
Document,
Paragraph,
Text,
Gapcursor,
Table.configure({
resizable: true,
}),
TableRow.extend({
content: '(tableCell | tableHeader)*',
}),
TableHeader,
TableCell,
],
// i wish it was this easy
content: (section.data) ? section.data : defTemplate,
}, [])
const pickTemplate = async (name) => {
try{
const res = await fetch(`/templates/${name}.json`,{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const data = await res.json()
setIsLoaded(true)
setdefTemplate(data)
console.log('defTemplate, ', defTemplate);
// return data
} catch (err){
console.warn('template error: ', err);
}
}
function saveData(){
console.log(' **** SAVE MEEEE ', section.header);
try{
const newSection = {
header: section.header,
timeStart: currTimeStart,
notes: notesState,
data: editor.getJSON(),
}
updateSections(newSection)
} catch (err){
console.warn('table update error: ', id, err);
}
}
useEffect(() => {
// 👇️ don't run on initial render
if (saveCount !== 0) saveData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveCount])
useEffect(() => {
setTimeStart(section.timeStart)
setnotesState(section.notes)
if(!section.data) pickTemplate(section.header).catch(console.warn)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, section, isLoaded])
useEffect(() => {
if (editor && !editor.isDestroyed) {
if(section.data) editor.chain().focus().setContent(section.data).run()
if(!section.data) editor.chain().focus().setContent(defTemplate).run()
setIsLoaded(true)
}
}, [section, defTemplate, editor]);
if (!editor) {
return null
}
return isLoaded ? (<>
<StyledTableEditor>
<div className="title">
<input type="time" label='Start Time' className='time'
onChange={(e) => setTimeStart(e.target.value)}
defaultValue={currTimeStart}
/>
<h2>{section.header}</h2>
</div>
<EditorContent editor={editor} className="tiptap-table" ></EditorContent>
// ... non relavent editor controls
<button
title='save'
className='save2'
onPointerUp={() => saveData()}>
Save <FiSave />
</button>
</div>
</nav>
</StyledTableEditor>
</>)
: null
})
TiptapTable.displayName = 'MyTiptapTable';
What I Expected
What I expected was the parent state to update in place, but instead it overwrites the previous tables. Also, once it writes to PouchDB it doesn't write a single piece of new data, just resolved back to the previous, yet with an updated _rev revision number.
In theory I think i'd prefer the useRef hook with useImperativeHandle to pass up the data from child to parent.
It looks like this question is similar but doesn't programmatically comb through the children
I realize I could have asked a more refined question, but instead of starting a new question I'll just answer my own question from what I've learned.
The problem being
I wasn't utilizing React's setState hook as I iterated and updated the main Doc Object
Thanks to this article for helping me through this problem.
// Parent.jsx
import React, {useState} from 'react'
import { Child } from '../components/Child'
export const Parent = () => {
const masterDoc = {
_id: "123",
date: "2023-12-1",
sections: [
{header: 'green', status: 'old'},
{header: 'cyan', status: 'old'},
{header: 'purple', status: 'old'},
]
}
const [saveCount, setSaveCount] = useState(0)
const [sectionsState, setsectionsState] = useState(masterDoc.sections)
function updateSections(inputObj) {
setsectionsState(prev => {
const newState = prev.map(obj => {
// 👇️ if id equals 2, update country property
if (obj.header === inputObj.header)
return {...obj, ...inputObj}
return obj;
});
return newState;
});
}
return (<>
<h1>Parent</h1>
{sectionsState.map((sec, i) => {
if(!sec) return
return (
<Child
key={i}
section={sec}
updateSections={updateSections}
saveCount={saveCount}
/>
)
})}
<button
onClick={() => setSaveCount(prev => prev + 1)}
>State dependant update {saveCount}</button>
</>)
}
// Child.jsx
import React, {useEffect, useState, forwardRef, useImperativeHandle} from 'react'
export const Child = forwardRef((props, ref) => {
const {section, updateSections, saveCount} = props
const [statusState, setStatusState] = useState(section.status)
function modData() {
const obj = {
header: section.header,
status: statusState
}
updateSections(obj)
}
useEffect(() => {
// 👇️ don't run on initial render
if (saveCount !== 0) modData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveCount])
return (<>
<span style={{color: section.header}}>
header: {section.header}
</span>
<span>status: {section.status}</span>
<input
defaultValue={section.status}
onChange={(e) => setStatusState(e.target.value)}
/>
________________________________________
</>)
})
Child.displayName = 'MyChild';

Why am I getting this error: Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks?

I'm new to react but I've encountered enough errors to know not to call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks
I'm trying to import data from mongodb atlas into my app.
From the codes written below, I don't think it has violated the rule. Any help would be great thx!
import { useState, useEffect } from "react";
import axios from "axios";
const Events = () => {
const [allEvents, setEvents] = useState([]);
const fetchEvents = async () => {
try {
const { data } = await axios.get(`http://localhost:5000/get/events`);
return data
} catch (err) {
console.log(err);
}
};
useEffect(() => {
fetchEvents();
}, []);
};
export default Events;
function CalendarComponent() {
//useState declarations
const [newEvent, setNewEvent] = useState({
id: "",
title: "",
start: "",
action: "",
});
const [allEvents, setEvents] = useState(events);
const [toggleAdd, setToggleAdd] = useState(false);
const [msg, setMsg] = useState("");
//handle creating new event on button click
function handleAddSlot() {
const title = newEvent.title;
const start = newEvent.start;
const end = start;
let success = 0;
if (title && newEvent.action === control.ADD) {
// add new event into the list
const id = allEvents.length;
if(data.Events.some(i=>i["Serial Number"]===title)){
// let newArr = [...data.Events]
// const res = newArr.find(e=>e["Serial Number"]===title)["Scheduled Sample Date"]
// console.log(res);
// res.push({id:res.length,start,end})
data.Events.map(item=>item["Serial Number"]===title?{...item}["Scheduled Sample Date"].push({"id":{...item}["Scheduled Sample Date"].length,start}):item)
}
else{
setEvents((prev) => [...prev, { start, end, title }]);
}
setToggleAdd(true);
console.log(data.Events)
success = 1;
}
if (newEvent.action === control.UPDATE) {
// find id of existing event to update
setEvents((currEvents) =>
currEvents.map((event) => {
if (event.id === newEvent.id) {
return {
...event,
start: start,
end: start,
title: title,
};
} else {
return event;
}
})
);
success = 1;
console.log(allEvents);
}
// if action successful, close modal and clear all fields
if (success === 1) {
setToggleAdd(false);
clearFields();
}
}
// handle when user select existing scope event
function handleSelectEvent(event) {
setMsg("Update Scope");
const start = new Date(event.start);
setNewEvent({
...newEvent,
action: control.UPDATE,
title: event.title,
start,
id: event.id,
});
setToggleAdd(true);
}
//handle calendar slot click
function handleClick(e) {
newEvent.start = e.start;
newEvent.action = control.ADD;
setMsg("Add New Scope");
if (
// ? Can be better written
document.getElementById("id01")?.classList.contains("setDisplay") === true
) {
setToggleAdd(true);
}
}
//handle modal close when user clicks on x
function toggleClick() {
if (toggleAdd === true) {
setToggleAdd(false);
clearFields();
}
}
//function to clear all fields
function clearFields() {
newEvent.start = "";
newEvent.title = "";
}
return (
<div className={`calendar-container`}>
<AddModal
id="id01"
toggle={toggleAdd}
newEvent={newEvent}
toggleClick={toggleClick}
handleSelectSlot={handleAddSlot}
setNewEvent={setNewEvent}
msg={msg}
/>
<Calendar
localizer={localizer}
events={allEvents}
popup
defaultView="month"
longPressThreshold={1}
views={["month"]}
eventPropGetter={(event) => {
const identifier = data.Events.find(
(item) => item["Type"] === event.type
);
const backgroundColor = identifier
? data.Color.find((item) => item[`${identifier.Type}`])[
`${identifier.Type}`
]
: "";
return { style: { backgroundColor } };
}}
onSelectEvent={handleSelectEvent}
onSelectSlot={(e) => handleClick(e)}
selectable
style={{ height: 500, margin: "30px" }}
components={{
toolbar: CustomToolbar,
}}
/>
<div className="calendar-sidebar">
<Button text="Add New" button="button" onClick={handleClick} />
<Log type='Schedule' />
</div>
</div>
);
}
I've attached the CalendarComponent. I can't really seem to find the problem in CalendarComponent myself. Hopefully it's not in the react big calendar package and I'm not missing something

React button component asks for key props

I've been coding a flexible button component where I pass some values by param (text, class, size, icon, and such) and when I do a condition (if it's a link or a button) before returning the HTML, it asks me for a key prop.
Why is it asking for a key prop if it's not a list. I'm not even going through any array to return the value.
This is the React button component code:
import React from "react";
import "./button.css";
import { Icon } from '../Icon/icon';
const buttonTypes = [
"button",
"a"
];
const buttonClasses = [
"app-button",
"app-button-filled",
"app-button-outlined",
"app-button-icon"
];
const buttonSizes = [
"app-button-large",
"app-button-icon-large"
];
export const Button = ({
buttonIcon = {
name: '',
style: '',
position: ''
},
buttonText,
buttonType,
buttonTarget,
buttonHref,
buttonOnClick,
buttonClass,
buttonSize
}) => {
const checkClasses = () => {
if(buttonClasses.includes(buttonClass)){
return buttonClasses[0]+" "+buttonClass;
} else {
return buttonClasses[0];
}
}
const checkSizes = () => {
if(buttonSizes.includes(buttonSize)){
return buttonSize;
} else {
return '';
}
}
const checkTypes = () => {
if(buttonTypes.includes(buttonType)){
return buttonType;
} else {
return buttonTypes[0];
}
}
const insertContent = () => {
let content = [],
iconTag = <Icon iconName={buttonIcon.name} iconStyle={buttonIcon.style} iconClass="app-button-svg" />;
if(buttonClass === "app-button-icon"){
content.push(iconTag);
} else {
if(buttonText){ content.push(<span className="app-button-text">{buttonText}</span>); }
if(buttonIcon){
if(buttonIcon.position === "left"){
content.unshift(iconTag);
} else if(buttonIcon.position === "right" || buttonIcon.position !== "left") {
content.push(iconTag);
}
}
}
return content;
}
if(checkTypes() === "button"){
return (<button className={`${checkClasses()} ${checkSizes()}`} onClick={buttonOnClick}>{insertContent()}</button>);
} else if(checkTypes() === "a"){
return (<a className={`${checkClasses()} ${checkSizes()}`} href={buttonHref} target={buttonTarget} >{insertContent()}</a>);
}
}
Warning code:
Warning: Each child in a list should have a unique "key" prop | button.js:84
The condition for this trigger is passing an array in the list of children.
You are doing this near the end:
return (<button className={`${checkClasses()} ${checkSizes()}`} onClick={buttonOnClick}>{insertContent()}</button>);
...
return (<a className={`${checkClasses()} ${checkSizes()}`} href={buttonHref} target={buttonTarget} >{insertContent()}</a>);
Your children here are the result of insertContent(), which returns an array. Because it returns an array, it triggers the warning because no keys are specified.
To solve this, you need to change the function insertContent to include keys on the elements it puts into the content array

I have a question about react array clearing

There is an array in the parent class(TodolistActivity), and the child class(TaskCallFunc) displays the elements in the array. When I use a.list= []; to clear the array, there is no clearing on the page
but a.list.length = 0 is ok. why?
Here is my code:
interface IListData {
list: IActivityData[]
}
interface IActivityData {
id: number,
content: string,
finish: boolean
}
export function TodolistActivity(activty: IListData) {
const [acty, setActivity] = useState(activty);
const [content, setContent] = useState('');
const input_ref = React.createRef<HTMLInputElement>();
const [selectCount, setSelect] = useState(0);
const handleAdd = () => {
if (input_ref.current) {
if (input_ref.current.value === '') {
alert("输入内容 不能为空!");
return;
}
let id = acty.list.length;
acty.list.unshift({ id: id, content: content, finish: false })
let a = { ...acty }
setActivity(a);
input_ref.current.value = "";
}
}
const calcuateSelect = () => {
let a = acty.list.filter((v, i) => { return v.finish === true })
setSelect(a.length);
}
const handleChange = (input: React.ChangeEvent<HTMLInputElement>) => {
setContent(input.target.value);
}
const clearTask = () => {
let a = { ...acty};
a.list= [];
//a.list.length = 0;
setActivity(a);
}
return (
<div>
<input type='text' onChange={handleChange} ref={input_ref} />
<button className="add task" onClick={handleAdd}>add task</button>
<button className="clear task" onClick={clearTask}>clear task</button>
{console.log(acty)}
<TaskCallFunc data={acty} action={() => { calcuateSelect() }} />
<br />
<label htmlFor="">select{selectCount}/{acty.list.length}</label>
</div>
);
}
interface ItaskCell {
data: IListData,
action: () => void
}
function TaskCallFunc(taskData: ItaskCell) {
const [data, setData] = useState(taskData);
const HandleSlecet = (x: number) => {
for (let index = 0; index < data.data.list.length; index++) {
if (data.data.list[index].id === x) {
let newState = { ...data };
newState.data.list[index].finish = !data.data.list[index].finish;
setData(newState);
data.action();
}
}
}
const handleMap = () => {
return data.data.list.map((v, i) => { return <li key={v.id}>{v.id}: {v.content} <input type="checkbox" checked={v.finish} onChange={() => { HandleSlecet(v.id) }} /> </li> });
}
return (
<ul>{handleMap()}</ul>
);
}
If you know the answer, please let me know thank you
TaskCallFunc component doesn't "listen" for changes on the taskData prop to update the local copy stored in state. Use an useEffect hook with a dependency on taskData prop to update the state when it changes.
function TaskCallFunc(taskData: ItaskCell) {
const [data, setData] = useState(taskData);
useEffect(() => {
setData(taskData);
}, [taskData]);
...
You can clear the array easely by doing setActivity({ list: [] }) consider also to add useEffect as Drew says to listen for changes

Resources