Функция, переданная и вызванная в дочернем компоненте, не имеет доступа к значению родительского состояния - PullRequest
2 голосов
/ 10 марта 2019

У меня есть родительский и дочерний компоненты.

У родительского компонента есть переменная состояния, к которой я хочу получить доступ.

Я передаю функцию дочернему компоненту и вызываю ее там onClick.

Однако значение переменной состояния устарело, а не текущее значение.

function Parent(props) {
    const [items, setItems] = useState([]);

    function getItems () {
        console.log(items); // always prints "[]" 
    }

    useEffect(() => {
        thirdPartyApiCall().then((fetchedItems) =>
            let newItems = fetchedItems.map(item => <Child id={item.id} getItems={getItems}/>)
            setItems(newItems); // populates items array. "{items}" elements correctly displayed on web page
        );
    }, []);

}


function Child(props) {
    return <button onClick={props.getItems}>{props.id}</button>
}

Ответы [ 2 ]

2 голосов
/ 10 марта 2019

Ну да, конечно нажимая на логи кнопки [].При этом:

<Child id={item.id} getItems={getItems}/>

getItem оценивается как функция, которая регистрирует элементы, и элементы [], поэтому регистрируется [].Причина, по которой он не регистрирует элементы new , заключается в том, что во время написания кода React не может точно знать, когда следует обновить компонент, поскольку на этапе рендеринга не используется переменная.


Обычно плохая практика использовать элементы JSX вне Render, потому что они не совсем обычные переменные JS с предсказуемым поведением.Тот факт, что он компилируется, является скорее хаком , чем функцией .

Сохранение ванильных переменных в состоянии и предоставление компонентам возможности выполнять JSX-рендеринг.Этот подход немного отличается от вашего, но работает как положено, так как нажатие на любую кнопку регистрирует все элементы:

const fakeApiCall = () => Promise.resolve([
    {id: 10},
    {id: 20},
    {id: 30},
]);

const Parent = () => {
    const [items, setItems] = React.useState([]);
    React.useEffect(() => {
        fakeApiCall().then(setItems)
    })
    const getItems = () => console.log(items);
    return (
        <div>
            {items.map(item => (
                <Child
                id={item.id}
                key={item.id}
                getItems={getItems}/>
            ))}
        </div>
    )
}

const Child = ({id, getItems}) => (<button onClick={getItems}>{id}</button>);

window.onload = () => ReactDOM.render(<Parent/>, document.querySelector('#root'));
<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>

<div id="root"></div>
2 голосов
/ 10 марта 2019

Возможно, вы захотите посмотреть, как работает javascript closure .

Даст базовое представление о том, что происходит.

useEffect будет выполняться только один раз из-за зависимости. Таким образом, когда визуализировались дочерние компоненты, items был пустым массивом ([]). У ваших дочерних компонентов всегда будет этот экземпляр items, так как useEffect не будет вызван с обновленным items.

Надеюсь, это помогло.

EDIT: 1

Возможно, вы захотите, чтобы ваш родительский компонент выглядел следующим образом.

function Parent(props) {
    const [items, setItems] = useState([]);

    function getItems () {
        console.log(items); // always prints "[]" 
    }

    useEffect(() => {
        thirdPartyApiCall().then((fetchedItems) =>
            setItems(newItems);
        );
    }, []);

    return newItems.map(item => <Child key={item.id} id={item.id} getItems={getItems}/>)
}

...