简体   繁体   中英

React props out of date in event handler

We are building a React-Redux web app that will display multiple Three JS scenes. These scenes come in pairs, and each pair will have synchronized zooming. To facilitate that, we're storing camera data in the Redux store.

Here is our React class (take a deep breath, it's a little long for a SO question), which uses react-three-renderer to produce Three JS objects:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Vector3 } from 'three';
import React3 from 'react-three-renderer';
import ReferenceGrid from './ReferenceGridVisual';
import ResourceGroup from './resourceGroups/ResourceGroup';
import { initializeRayCastScene } from './viewportMath/RayCastScene';
import zoomCamera from './viewportMath/CameraZoom';
import { registerCamera, zoom } from './actions/cameraActions';
import { InitThreeJsDomEvents, UpdateDomCamera } from './domUtility/ThreeJSDom';

class ThreeJsScene extends Component {
  constructor(props) {
    super(props);

    this.ZoomAmount = 150;
    this.ZoomMaxCap = 1000;
    this.ZoomMinCap = 6;
    this.zoomPadding = 10;
    this.minimumZoom = 45;
  }

  componentWillMount() {
    initializeRayCastScene();

    this.props.registerCamera(this.props.sceneName);
  }

  componentDidMount() {
    // eslint-disable-next-line no-underscore-dangle
    InitThreeJsDomEvents(this.camera, this.canvas._canvas);
  }

  onWheel = (event) => {
    // eslint-disable-next-line
    this.zoom(event.clientX, event.clientY, event.deltaY);
  }

  setCameraRef = (camera) => {
    UpdateDomCamera(camera);
    this.camera = camera;
  }

  zoom(screenPosX, screenPosY, zoomAmount) {
    const size = {
      width: this.props.width,
      height: this.props.height,
    };
    const result = zoomCamera(screenPosX, screenPosY, zoomAmount, this.camera.position,
      size, this.props.distance, this.camera, this.props.cameraType, this.ZoomMaxCap,
      this.ZoomMinCap);
    this.ZoomAmount = (result.ZoomAmount) ? result.ZoomAmount : this.ZoomAmount;
    this.props.zoom(this.props.sceneName, result.distanceChangeFactor, result.newCameraPosition);
  }

  render() {
    let position;
    if (this.props.cameraPosition != null) {
      position = new Vector3(
        this.props.cameraPosition.x,
        this.props.cameraPosition.y,
        this.props.cameraPosition.z
      );
    } else {
      position = new Vector3();
    }
    const left = -this.props.width / 2;
    const right = this.props.width / 2;
    const top = this.props.height / 2;
    const bottom = -this.props.height / 2;

    return (
      <div
        style={{ lineHeight: '0' }}
        onWheel={this.onWheel}
      >
        <React3
          width={this.props.width}
          height={this.props.height}
          mainCamera="camera"
          antialias
          pixelRatio={1}
          ref={(canvas) => { this.canvas = canvas; }}
        >
          <scene ref={(scene) => { this.scene = scene; }}>
            <orthographicCamera
              name="camera"
              left={left}
              right={right}
              top={top}
              bottom={bottom}
              near={0.01}
              far={1400}
              position={position}
              ref={this.setCameraRef}
            />
            <ambientLight
              color={0xaaaaaa}
            />
            <directionalLight
              color={0xaaaaaa}
              intensity={1.1}
              position={new Vector3(3, 4, 10)}
              lookAt={new Vector3(0, 0, 0)}
            />
            <ReferenceGrid xActive yActive zActive={false} store={this.props.store} />
            <ResourceGroup store={this.props.store}>
              {this.props.children}
            </ResourceGroup>
          </scene>
        </React3>
      </div>
    );
  }
}

const mapStateToProps = (state, ownProps) => {
  const ownCamera = state.cameras.get(ownProps.sceneName);
  if (ownCamera == null) {
    console.log('own camera null');
    return { cameraAvailable: false };
  }
  console.log('has own camera');
  const cameraPosition = ownCamera.position;
  const cameraType = ownCamera.type;
  const distance = ownCamera.distance;
  return {
    cameraAvailable: true,
    cameraPosition,
    cameraType,
    distance,
  };
};

const mapDispatchToProps = dispatch => ({
  registerCamera: (cameraName) => {
    dispatch(registerCamera(cameraName));
  },
  zoom: (cameraName, factor, newCameraPosition) => {
    dispatch(zoom(cameraName, factor, newCameraPosition));
  },
});

export default connect(mapStateToProps, mapDispatchToProps)(ThreeJsScene);

Additionally, for reference, here are the action creators:

export const registerCamera = cameraName => (dispatch) => {
  dispatch({ type: 'REGISTER_CAMERA', newCameraName: cameraName });
};

export const zoom = (cameraName, factor, newCameraPosition) => (dispatch, getState) => {
  const state = getState();
  const zoomFactor = state.cameras.get(cameraName).distance * (1 - factor);
  dispatch({ type: 'CAMERA_ZOOM', cameraName, factor: zoomFactor, newCameraPosition });
};

And the reducer:

import { Map } from 'immutable';

const defaultCameraProperties = {
  distance: 150,
  type: 'orthogonal',
  position: { x: 0, y: 10, z: 50 },
  rotation: { x: 0, y: 0, z: 0, w: 1 },
};

const initialState = Map();

export default (state = initialState, action) => {
  switch (action.type) {
    case 'REGISTER_CAMERA': {
      const newCamera = {
        ...defaultCameraProperties,
        ...action.newCameraProperties,
      };
      return state.set(action.newCameraName, newCamera);
    }

    case 'CAMERA_ZOOM': {
      const updatedDistance = action.factor;
      const updatedCameraPosition = {
        ...state.get(action.cameraName).position,
        ...action.newCameraPosition,
      };
      const updatedCamera = {
        ...state.get(action.cameraName),
        position: updatedCameraPosition,
        distance: updatedDistance,
      };
      return state.set(action.cameraName, updatedCamera);
    }

    default: {
      return state;
    }
  }
};

The challenge is in the zoom function in the React class, the React props are not what I would expect, and therefore zooming is failing. Here is a summary of the sequence of relevant events as I understand them:

  1. componentWillMount is called, which dispatches the REGISTER_CAMERA method. (We do this rather than having camera data by default in the store because these pairs of scenes are generated dynamically - there is not a static number of them.)
  2. The React render method is called.
  3. The React render method is called again since the REGISTER_CAMERA action has now modified the store and we have new props - the camera related props are now available.
  4. I trigger zoom with my mouse wheel. The onWheel handler calls the zoom function, but breakpointing in that method reveals that the camera related props - like this.props.cameraType - are undefined . The React props appear as they do in 2. ( zoomCamera does some calculations. Since these properties are unavailable, zooming fails.)

I can't figure out why this is. My suspicion is I'm misunderstanding something about what this context is bound to the zoom method.

In short my question is why are my props not up to date and how can I make the updated version available to the zoom function?

Turns out it was an error with hot module reloading. Running our build cold does not exhibit the issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM