2

Goal: I am trying to toggle the visibility of a 3D model using React reducer and context. 3D rendering is done with React hooks version of a framework called Xeokit. Models are rendered into a canvas element (3DCanvas.js) by giving them as props to a Xeokit Viewer element (XKTModel.js).

The problem is, once a dispatch is sent, the state becomes undefined. The context works only with the default value given to the reducer. Doesn't seem to matter which component sends the dispatch.

I have written into console the state for each component using the UseContext hook to see what is going on. Same thing with the reducer.

I did a lot of searching but could not find a similar issue being addressed. Can someone more experienced spot the mistake I have made?

Context creation:

import React from "react";

const ViewControlsContext = React.createContext()

export {ViewControlsContext as default}

Reducer:

const ViewControlsReducer = (state, action) => {
    switch (action.type) {
      case 'POPULATE_STATE':
          return action.state, console.log("populate dispatch: ", action, state)
      case 'TOGGLE_SITE_VISIBILITY':
        return {
            ...state,
            //site_visibility: !state.site_visibility
            //^commented out, as will cause an error because state undefined
        }, console.log("toggle dispatch: ", action, state)
      default:
        return state, console.log("default: ", action, state)
    }
  }

  export {ViewControlsReducer as default}

App:

import React from 'react';

import './App.scss'

import ThreeDeeCanvas from './Components/3DCanvas'

function App(){
  return (<div>
    <ThreeDeeCanvas />
  </div>)
};

export default App;

3DCanvas (component into which models are rendered, provides Context):

import React, { useEffect, useReducer } from 'react'
import { Container, Col, Row } from 'react-bootstrap'

import XKTModel from './XKTModel';
import ThreeDeeControls from './3DControls';
import LogoCanvas from './LogoCanvas'
import ViewControlsContext from '../Context/ViewControlsContext';
import ViewControlsReducer from '../Reducers/ViewControlsReducer';

const ThreeDeeCanvas = () => {

    const def = {
        site_visibility: false
    }

    const [viewControls, dispatch] = useReducer(ViewControlsReducer, def)

    useEffect(() => {
        dispatch({type: 'POPULATE_STATE', state: def})
        dispatch({type: 'POPULATE_STATE', state: def}) //test, with this state is undefined
    }, [])
    
    console.log("3dcanvas: ", viewControls)

    return (
        <div>
            <LogoCanvas/>
            <Col className='ThreeDeeWindow'>
            <ViewControlsContext.Provider value={{viewControls, dispatch}} >
                <XKTModel/>
                <ThreeDeeControls/>
            </ViewControlsContext.Provider>
            </Col>
        </div>
    )
}

export {ThreeDeeCanvas as default}

XKTModel (source and props for 3D models, uses Context):

import React, { useContext } from 'react';
import { navCubeSettings } from '../models';
import { XKTViewer } from 'xeokit-react';

import ViewControlsContext from '../Context/ViewControlsContext';
import { IFCObjectDefaultColors } from '../default_colors';

const mainModel = {
  id: 'main_model',
  src: './xkt/Showcase.xkt',
  metaModelSrc: './xkt/Showcase.json',
  edges: true,
  objectDefaults: IFCObjectDefaultColors,
}

const siteModel = {
  id: 'site_model',
  src: './xkt/ShowcaseSite.xkt',
  metaModelSrc: './xkt/ShowcaseSite.json',
  edges: true
}

const XKTModel = () => {

  const {viewControls} = useContext(ViewControlsContext)
  console.log("XKTModel: ", viewControls)

  //siteModel.visible = viewControls.site_visibility
  //^commented out, as will cause an error because state undefined
  
 
  return (
    <XKTViewer
    canvasID="modelCanvas"
    width={800}
    height={800}
    models={[mainModel, siteModel]}
    navCubeSettings={navCubeSettings}
    />
  ) 
};

export default XKTModel;

3DControls (Container for individual 3D controls):

import React, { useContext } from 'react'
import { Container, Col, Row } from 'react-bootstrap'

import ThreeDeeControl from './3DControl'
import ViewControlsContext from '../Context/ViewControlsContext'

const ThreeDeeControls = () => {

    const {viewControls} = useContext(ViewControlsContext)
    
    console.log("3dcontrols: ", viewControls)

    return (
        <Container className='ThreeDeeControls p-0 d-flex justify-content-between'>
            <ThreeDeeControl id='site' label='Show site'/>
        </Container>
    )
}

export {ThreeDeeControls as default}

3DControl (individual toggles for changing 3D view props, sends dispatch):

import React, {  useState, useContext, useReducer } from 'react'
import { Container, Form } from 'react-bootstrap'

import ViewControlsContext from '../Context/ViewControlsContext'


const ThreeDeeControl = ({id, label}) => {

    const {viewControls, dispatch} = useContext(ViewControlsContext)

    console.log("3dsinglecontrol: ", viewControls)

    const [isSwitchOn, setIsSwitchOn] = useState(false);

    const onSwitchAction = () => {
    setIsSwitchOn(!isSwitchOn);
    dispatch({type: 'TOGGLE_SITE_VISIBILITY'})
    }

    return (
            <Form className='ThreeDeeControl'>
                <Form.Check  
                    type="switch"
                    id={id}
                    label={label}
                    checked={isSwitchOn}
                    onChange={onSwitchAction}
                />
            </Form>
    )
}

export {ThreeDeeControl as default}
Koib
  • 23
  • 4
  • 1
    Comma operator returns the last value. If you have return foo, console.log, it returns undefined as console.log returns undefined in your reducer. – Wiktor Zychla Apr 22 '22 at 18:00

1 Answers1

0

You are returning the result of the comma operator, i.e. the result of console.log which is a void return.

Comma Operator

The comma operator (,) evaluates each of its operands (from left to right) and returns the value of the last operand. This lets you create a compound expression in which multiple expressions are evaluated, with the compound expression's final value being the value of the rightmost of its member expressions.

Solution

Log the state and action independent of the reducer function return.

const ViewControlsReducer = (state, action) => {
  switch (action.type) {
    case 'POPULATE_STATE':
      console.log("populate dispatch: ", { action, state });
      return action.state;

    case 'TOGGLE_SITE_VISIBILITY':
      console.log("toggle dispatch: ", { action, state });
      return {
        ...state,
        site_visibility: !state.site_visibility
      };

    default:
      console.log("default: ", { action, state });
      return state;
  }
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181