简体   繁体   English

在 React 功能组件内的函数中传递状态

[英]Passing state in function inside React functional component

I am still pretty new to react and typescript in general, so there might be some other issues I am not seeing.总的来说,我对反应和打字稿还是很陌生,所以可能还有一些我没有看到的其他问题。 Most of the tutorials I am finding are for class based components instead of functional ones, making it more difficult.我发现的大多数教程都是针对基于类的组件而不是功能组件,这使得它变得更加困难。

I have a component that contains two checkboxes.我有一个包含两个复选框的组件。 When toggling the checkbox, I would also like to post this update to a url.切换复选框时,我还想将此更新发布到 url。 Currently, the toggles are working and I am able to update the state accordingly.目前,切换正在工作,我能够相应地更新状态。 The issue is when attempting to post the update, the updated state is not set in the request, but rather the previous state.问题是在尝试发布更新时,请求中未设置更新状态,而是之前的状态。

Below is the main Document component.下面是主要的Document组件。 I think the issue is with the updateDocument function, since the state has not necessarily been set by setToggles when it is called.我认为问题出在updateDocument函数上,因为setToggles在调用它时不一定会设置状态。 From what I have read, I need to use a callback, but I am unsure how I would implement this.从我读到的内容来看,我需要使用回调,但我不确定如何实现这一点。

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>
    );
};

I just want to be able to pass up-to date values from the "state" provided by useState to a function within a functional component.我只是希望能够将useState提供的“状态”中的最新值传递给功能组件中的函数。

I will also add the whole file for the sake of completeness.为了完整起见,我还将添加整个文件。 But basically it is just a wrapper component around an array of Document s:但基本上它只是一个围绕Document数组的包装组件:

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>
    );
}

UPDATE:更新:

Updated Document component with the change provided by @Ibz.使用@Ibz 提供的更改更新了Document组件。 The only issue now is that the POST request to the update url is run twice if multiple toggles are toggled.现在唯一的问题是,如果切换多个开关,则对更新 url 的POST请求会运行两次。 Toggling only a single component will not do this.仅切换单个组件不会执行此操作。

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>
    );
};

UPDATE 2:更新 2:

Below is the final working code, slightly simplified from the OP.下面是最终的工作代码,从 OP 稍微简化。 Thanks to @Ibz for all the help!感谢@Ibz 的所有帮助!

Regarding the duplicate POST requests: I was using yarn start to run a development server when I was seeing this issue.关于重复的POST请求:当我看到这个问题时,我正在使用yarn start来运行开发服务器。 After building with yarn build and serving the files with the actual server, the issue is no longer present.使用yarn build并使用实际服务器提供文件后,问题不再存在。 This answer on the axios issues page made me try this. axios问题页面上的这个答案让我尝试了这个。

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}
        />
    );
};

With useState, there's no guarantee that the action is executed in order, as your component needs to re-render to have all the up to date state.使用 useState,不能保证操作按顺序执行,因为您的组件需要重新渲染以拥有所有最新状态。

setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)

That means with this piece of code, your component renders, and you have some value in toggles.这意味着使用这段代码,您的组件会呈现,并且您在切换中有一些价值。 When you get to setToggles(...), react queues the update of the state for the next render, so when you get to updateDocument, it's being run with the previous value of toggles.当您到达 setToggles(...) 时,react 会将状态更新排入下一次渲染的队列,因此当您到达 updateDocument 时,它会使用之前的切换值运行。

To get around this, we would usually use useEffect .为了解决这个问题,我们通常会使用useEffect This is a hook which runs some code whenever some other value changes.这是一个钩子,每当其他值发生变化时,它就会运行一些代码。 In your instance, you would want something like:在你的例子中,你会想要这样的:

useEffect(() => {
  updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}, [document.id, document.description, toggles.checked, toggles.expired])

The second argument to useEffect is called the Dependency Array , and is a list of values that when changed, causes the function inside useEffect to run. useEffect 的第二个参数称为Dependency Array ,它是一个值列表,当更改时,会导致 useEffect 中的函数运行。

It can be a little tricky wraping your head around state at first, but I hope this helped.一开始围绕 state 可能有点棘手,但我希望这会有所帮助。 Any other questions, just leave a comment.任何其他问题,只需发表评论。 You can find more information here: https://reactjs.org/docs/hooks-effect.html您可以在此处找到更多信息: https : //reactjs.org/docs/hooks-effect.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将 function 作为道具传递给 Typescript 反应功能组件 - Passing a function as a prop to a Typescript React Functional Component 将 function 传递给 React 中的无状态功能组件时遇到问题 - Trouble passing a function to a stateless functional component in React 反应 function 不会从功能组件传递到功能组件 - React function not passing down from functional to functional component Function 在 React 钩子中的功能组件内 - 性能 - Function inside functional component in React hooks - Performance 反应:在 state 中存储功能组件 - React: Storing functional component in state React 功能组件 state 未更新 - React functional component state is not updating 使用 redux state 导出 function(功能组件)React - Use redux state in exported function (functional component) React 在反应 typescript 功能组件中作为道具传递时,获取 toogleShow 不是 function - Getting toogleShow is not function when passing as props in react typescript functional component 通过 React 功能组件将 function 从父级传递给子级 - Passing down function from parent to child through React functional component 是否可以从不在我的功能组件内的另一个 function 中的功能组件更改 state? - Is it possible to change the state from a functional component in another function that is not inside my functional component?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM