Nikulio
@Nikulio
NaN !== NaN

Почему пропсы из Редакса в обработчике Реакт не видит, когда в render(){} видит?

Есть такой компонент :

import React, { Component } from "react";
import NewStory from "../NewStory";
import "./index.scss";
import MaterialIcon from "material-icons-react";
import history from "../../history";
import { connect } from "react-redux";

class Dashboard extends Component {
	state = {
		addNewOpen: false
	};

	handleCreateNew = () => {
		this.setState({
			addNewOpen: !this.state.addNewOpen
		});
	};

	clickHandle(e) {
		e.preventDefault();

		let targetElement = e.currentTarget.dataset.id;
		const { stories } = this.props; // ТУТ НЕ ВИДИТ

		if (stories) {
			Object.keys(stories).map(element => {
				if (targetElement === element) {
					history.push({
						pathname: "/story",
						state: { detail: element }
					});
				}
			});
		}
	}

	render() {
		const { addNewOpen } = this.state;
		const { stories } = this.props; // ТУТ ВИДИТ
		const dashClass = stories ? "dashboard not-empty" : "dashboard empty";
		let elements = stories ? (
			Object.keys(stories).map(key => {
				let imgUrl = stories[key].img ? stories[key].img : "img/no_image.jpg";
				return (
					<a
						key={key}
						href="/"
						onClick={this.clickHandle}
						data-id={key}
						className="stories__element">
						<img className="stories__element-image" src={imgUrl} alt="image" />
						<div className="overlay" />
						<h2 className="stories__element-title">{stories[key].title}</h2>
						<div className="stories__element-labels">{stories[key].labels}</div>
					</a>
				);
			})
		) : (
			<div>Loading...</div>
		);
		const content = stories ? (
			<div className="stories">
				<div
					className="create-new stories__element"
					onClick={this.handleCreateNew}>
					<div className="overlay" />
					<h2 className="create-new__title">
						<MaterialIcon icon="add" size={48} color="#000" />
						Add new
					</h2>
				</div>
				{elements}
			</div>
		) : (
			<div>
				<div className="create-new" onClick={this.handleCreateNew}>
					<MaterialIcon icon="alarm_add" color="#68edc6" size={100} />
					<h2 className="create-new__title">Add new</h2>
				</div>
			</div>
		);
		return (
			<div className={dashClass}>
				<NewStory isOpen={addNewOpen} />
				{content}
			</div>
		);
	}
}

const mapDispatchToProps = {};
export default connect(
	state => ({
		stories: state.stories
	}),
	mapDispatchToProps
)(Dashboard);


Изначально работало без connect(), ибо я передаю объект сверху. Когда не вышло, добавил connect(), но все равно не работает.
Еррор -
main.js:92935 Uncaught TypeError: Cannot read property 'props' of undefined


В чем может быть проблема?
  • Вопрос задан
  • 97 просмотров
Решения вопроса 2
rockon404
@rockon404
Frontend Developer
Вы передаете метод clickHandle в колбек слушателя события click, при вызове метод теряет контекст, так как вызывается не на вашем объекте. this в таком случае ссылается не на ваш объект, а на undefined. Так как babel при трансляции добавляет 'use strict' (иначе, при выполнении в браузере, ссылался бы на window). Исправить это можно несколькими способами:
1. переделать обработчик из метода класса в class field arrow function:
было:
clickHandle(e) {
  // some code
}

стало:
clickHandle = e => {
  // some code
};

class field arrow function получает контекстом экземпляр класса при инициализации и всегда ссылается на него, куда бы вы ее не передали. Это экспериментальная возможность JavaScript и в спецификации ее пока нет. За трансляцию этой конструкции в валидный код отвечает babel.
Результат, который будет получен после трансляции, можно посмотреть тут. Строки с 23 по 28.

2.забиндить его в конструкторе на экземпляр класса:
constructor(props) {
  super(props);
  this.clickHandle = this.clickHandle.bind(this);
}

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

3. обернуть в стрелочную функцию в render:
<SomeComponent onClick={() => this.сlickHandle()} /> // контекст не будет потерян

При оборачивании в стрелочную функцию происходит следующее: сама функция использует как контекст ваш объект, поэтому при вызове метод будет вызван на вашем объекте и this в самом методе будет ссылаться на объект. Этот вариант разумно использовать, только тогда, когда в хандлер необходимо передать свои аргументы, помимо event:
<ListItem
  key={item.id}
  onClick={e => this.сlickHandle(item.id, e)}
>
  {item.name}
</ListItem>

так как движок вынужден определять контекст для стрелочной функции каждый рендер, а на это уходит дополнительное процессорное время.

Как метод теряет контекст.
Разберем на примере объекта. То же самое происходит в случае с классом, но мы будем использовать в примере объект:
var john = {
  firstName: 'John',
  getName() {
    return this.firstName;  // this - контекст вызова и это не всегда наш объект
  }
}


Сценарий 1:
console.log(john.getName()); // john
Тут мы вызываем метод на объекте. Контекстом будет наш объект.

Сценарий 2:
var foo = {
  firstName: 'foo',
};
var foo.getName = john.getName;
console.log(foo.getName()); // foo

Тут мы передаем метод объекта john в свойство объекта foo без вызова и следующей строкой вызываем его на нем. Контекстом в этот раз будет объект foo. Ошибки не будет только потому, что у объекта foo есть свойство fullName

Сценарий 3:
var bar = john.getName;
console.log(bar()); // undefined

В данном случае в стандартном режиме контекстом будет window, а в строгом режиме вылетит исключение:
Cannot read property 'firstName' of undefined
так как this в строгом режиме будет ссылаться на undefined

Когда вы передаете метод в колбек onClick или в любой другой колбек события, вызов идет подобно третьему сценарию. Поэтому вы должны позаботиться о том, чтобы ваш метод не терял контекст, используя один из способов приведенных выше.
Ответ написан
@vshvydky
Либо забинди хендлер либо стрелочную функцию, ты теряешь контекст
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через TM ID
Похожие вопросы