在 React 功能组件内部的函数中传递状态
Passing state in function inside React functional component
总的来说,我对 React 和 TypeScript 还是很陌生,所以可能还有一些我没有看到的其他问题。我找到的大多数教程都是针对基于 class 的组件而不是功能性组件,这使得它变得更加困难。
我有一个包含两个复选框的组件。切换复选框时,我还想 post 此更新为 url。目前,切换正在工作,我能够相应地更新状态。问题是在尝试 post 更新时,请求中未设置更新后的状态,而是之前的状态。
下面是主要的 Document
组件。我认为问题出在 updateDocument
函数上,因为调用时状态不一定由 setToggles
设置。根据我的阅读,我需要使用回调,但我不确定我将如何实现它。
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
const {name, checked} = data;
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
我只想能够将 useState
提供的“状态”中的最新值传递给功能组件中的函数。
为了完整起见,我还将添加整个文件。但基本上它只是围绕 Document
s:
数组的包装器组件
const _DOCS: IDocument[] = [{id: "666666666666", description: "TESTDESC", checked: false, expired: false}]
const MainAppView = () => {
return (
<div>
<DocumentViewBox documents={_DOCS}/>
</div>
);
}
interface IDocument {
id: string;
description: string;
checked: boolean;
expired: boolean;
}
// DocumentViewBox is used to display a list of documents.
const DocumentViewBox: FC<{ documents: IDocument[] }> = ({documents}): ReactElement => {
return (
<div>
{documents.map(doc => {
return <Document key={doc.id} document={doc}/>
})}
</div>
);
};
interface DocumentToggles {
checked: boolean;
expired: boolean;
}
const _documentToggles: DocumentToggles = {checked: false, expired: false}
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
const {name, checked} = data;
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
const DocumentCheckboxes: FC<{ checked: boolean, expired: boolean, handleToggle: (e: FormEvent<HTMLInputElement>, data: any) => void }> = ({checked, expired, handleToggle}): ReactElement => {
return (
<Container textAlign="left">
<Divider hidden fitted/>
<Checkbox
toggle
label="Checked"
name="checked"
onChange={handleToggle}
checked={checked}
/>
<Divider hidden/>
<Checkbox
toggle
label="Expired"
name="expired"
onChange={handleToggle}
checked={expired}
/>
<Divider hidden fitted/>
</Container>
);
}
更新:
使用@Ibz 提供的更改更新了 Document
组件。现在唯一的问题是,如果切换多个开关,POST
对更新 url 的请求是 运行 两次。仅切换单个组件不会执行此操作。
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
e.preventDefault()
setToggles(prevState => {
const newState = {...prevState, [data.name]: data.checked};
updateDocument('http://example.com/update', document.id, document.description, newState.checked, newState.expired);
return newState;
})
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
更新 2:
下面是最终的工作代码,从 OP 中略微简化。感谢@Ibz 的所有帮助!
关于重复的 POST
请求:当我看到这个问题时,我正在使用 yarn start
到 运行 开发服务器。使用 yarn build
构建并使用实际服务器提供文件后,问题不再存在。 axios
问题页面上的 This answer 让我尝试了这个。
import React, {Component, useState, useEffect, FC, ReactElement, MouseEvent, FormEvent, ChangeEvent} from "react";
import {Container, Segment, Label, Checkbox, CheckboxProps} from "semantic-ui-react";
import axios from "axios";
interface IDocument {
id: string;
description: string;
checked: boolean;
expired: boolean;
}
const _DOCS: IDocument[] = [{id: '0', description: '', checked: false, expired: false}]
const MainAppView = () => {
return (
<div>
<DocumentViewBox documents={_DOCS}/>
</div>
);
}
const DocumentViewBox: FC<{ documents: IDocument[] }> = ({documents}): ReactElement => {
return (
<div>
{documents.map(doc => <Document key={doc.id} document={doc}/>)}
</div>
);
};
const defaultDocumentProps: IDocument = {id: '', description: '', checked: false, expired: false};
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [documentProps, setDocumentProps] = useState<IDocument>(defaultDocumentProps);
// Run only once and set data from doc
// as the initial state.
useEffect(() => {
setDocumentProps(document)
}, []);
const updateDocument = (uri: string, updateDoc: IDocument) => {
axios.post(uri, updateDoc).then(response => {
console.log('updateDocument response:')
console.log(response.data)
}).catch(err => {
console.log('updateDocument error:' + err)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: CheckboxProps) => {
e.preventDefault()
setDocumentProps(prevState => {
const {name, checked} = data;
const newState = {...prevState, [name as string]: checked};
console.log('handleToggle new state:')
console.log(newState)
updateDocument('http://example.com/update', newState);
return newState;
});
}
return (
<Checkbox
toggle
label='Checked'
name='checked'
onChange={handleToggle}
checked={documentProps.checked}
/>
);
};
使用 useState 时,无法保证操作会按顺序执行,因为您的组件需要重新呈现才能拥有所有最新状态。
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
这意味着通过这段代码,您的组件会呈现,并且您在切换中有一些价值。当您到达 setToggles(...) 时,反应会将状态更新排队等待下一个渲染,因此当您到达 updateDocument 时,它是 运行 与先前的切换值。
为了解决这个问题,我们通常会使用 useEffect
。这是一个钩子,每当其他值发生变化时,它 运行 都会发送一些代码。在你的例子中,你会想要这样的东西:
useEffect(() => {
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}, [document.id, document.description, toggles.checked, toggles.expired])
useEffect 的第二个参数称为 Dependency Array
,它是一个值列表,更改后会导致 useEffect 中的函数变为 运行。
一开始围绕状态可能有点棘手,但我希望这对您有所帮助。任何其他问题,请发表评论。您可以在此处找到更多信息:https://reactjs.org/docs/hooks-effect.html
总的来说,我对 React 和 TypeScript 还是很陌生,所以可能还有一些我没有看到的其他问题。我找到的大多数教程都是针对基于 class 的组件而不是功能性组件,这使得它变得更加困难。
我有一个包含两个复选框的组件。切换复选框时,我还想 post 此更新为 url。目前,切换正在工作,我能够相应地更新状态。问题是在尝试 post 更新时,请求中未设置更新后的状态,而是之前的状态。
下面是主要的 Document
组件。我认为问题出在 updateDocument
函数上,因为调用时状态不一定由 setToggles
设置。根据我的阅读,我需要使用回调,但我不确定我将如何实现它。
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
const {name, checked} = data;
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
我只想能够将 useState
提供的“状态”中的最新值传递给功能组件中的函数。
为了完整起见,我还将添加整个文件。但基本上它只是围绕 Document
s:
const _DOCS: IDocument[] = [{id: "666666666666", description: "TESTDESC", checked: false, expired: false}]
const MainAppView = () => {
return (
<div>
<DocumentViewBox documents={_DOCS}/>
</div>
);
}
interface IDocument {
id: string;
description: string;
checked: boolean;
expired: boolean;
}
// DocumentViewBox is used to display a list of documents.
const DocumentViewBox: FC<{ documents: IDocument[] }> = ({documents}): ReactElement => {
return (
<div>
{documents.map(doc => {
return <Document key={doc.id} document={doc}/>
})}
</div>
);
};
interface DocumentToggles {
checked: boolean;
expired: boolean;
}
const _documentToggles: DocumentToggles = {checked: false, expired: false}
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
const {name, checked} = data;
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
const DocumentCheckboxes: FC<{ checked: boolean, expired: boolean, handleToggle: (e: FormEvent<HTMLInputElement>, data: any) => void }> = ({checked, expired, handleToggle}): ReactElement => {
return (
<Container textAlign="left">
<Divider hidden fitted/>
<Checkbox
toggle
label="Checked"
name="checked"
onChange={handleToggle}
checked={checked}
/>
<Divider hidden/>
<Checkbox
toggle
label="Expired"
name="expired"
onChange={handleToggle}
checked={expired}
/>
<Divider hidden fitted/>
</Container>
);
}
更新:
使用@Ibz 提供的更改更新了 Document
组件。现在唯一的问题是,如果切换多个开关,POST
对更新 url 的请求是 运行 两次。仅切换单个组件不会执行此操作。
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
e.preventDefault()
setToggles(prevState => {
const newState = {...prevState, [data.name]: data.checked};
updateDocument('http://example.com/update', document.id, document.description, newState.checked, newState.expired);
return newState;
})
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
更新 2:
下面是最终的工作代码,从 OP 中略微简化。感谢@Ibz 的所有帮助!
关于重复的 POST
请求:当我看到这个问题时,我正在使用 yarn start
到 运行 开发服务器。使用 yarn build
构建并使用实际服务器提供文件后,问题不再存在。 axios
问题页面上的 This answer 让我尝试了这个。
import React, {Component, useState, useEffect, FC, ReactElement, MouseEvent, FormEvent, ChangeEvent} from "react";
import {Container, Segment, Label, Checkbox, CheckboxProps} from "semantic-ui-react";
import axios from "axios";
interface IDocument {
id: string;
description: string;
checked: boolean;
expired: boolean;
}
const _DOCS: IDocument[] = [{id: '0', description: '', checked: false, expired: false}]
const MainAppView = () => {
return (
<div>
<DocumentViewBox documents={_DOCS}/>
</div>
);
}
const DocumentViewBox: FC<{ documents: IDocument[] }> = ({documents}): ReactElement => {
return (
<div>
{documents.map(doc => <Document key={doc.id} document={doc}/>)}
</div>
);
};
const defaultDocumentProps: IDocument = {id: '', description: '', checked: false, expired: false};
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [documentProps, setDocumentProps] = useState<IDocument>(defaultDocumentProps);
// Run only once and set data from doc
// as the initial state.
useEffect(() => {
setDocumentProps(document)
}, []);
const updateDocument = (uri: string, updateDoc: IDocument) => {
axios.post(uri, updateDoc).then(response => {
console.log('updateDocument response:')
console.log(response.data)
}).catch(err => {
console.log('updateDocument error:' + err)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: CheckboxProps) => {
e.preventDefault()
setDocumentProps(prevState => {
const {name, checked} = data;
const newState = {...prevState, [name as string]: checked};
console.log('handleToggle new state:')
console.log(newState)
updateDocument('http://example.com/update', newState);
return newState;
});
}
return (
<Checkbox
toggle
label='Checked'
name='checked'
onChange={handleToggle}
checked={documentProps.checked}
/>
);
};
使用 useState 时,无法保证操作会按顺序执行,因为您的组件需要重新呈现才能拥有所有最新状态。
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
这意味着通过这段代码,您的组件会呈现,并且您在切换中有一些价值。当您到达 setToggles(...) 时,反应会将状态更新排队等待下一个渲染,因此当您到达 updateDocument 时,它是 运行 与先前的切换值。
为了解决这个问题,我们通常会使用 useEffect
。这是一个钩子,每当其他值发生变化时,它 运行 都会发送一些代码。在你的例子中,你会想要这样的东西:
useEffect(() => {
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}, [document.id, document.description, toggles.checked, toggles.expired])
useEffect 的第二个参数称为 Dependency Array
,它是一个值列表,更改后会导致 useEffect 中的函数变为 运行。
一开始围绕状态可能有点棘手,但我希望这对您有所帮助。任何其他问题,请发表评论。您可以在此处找到更多信息:https://reactjs.org/docs/hooks-effect.html