0

I made a static webpage app that I have been slowly converting to React (MERN stack) to make it more dynamic/so I won't have to configure each and every HTML document. It's a product configurator that uses Google's model-viewer.

I'm fairly new to using a full-stack workflow but have found it pretty fun so far! I am having trouble however understanding on how to convert some of my vanilla JS to work within React. This particular script will change a source/3D model when a user clicks on a button. Below is a code snipit of what I have working currently on a static webpage.

import {useEffect, useState} from "react";
import {useSelector, useDispatch} from "react-redux";

// Actions
import {getProductDetails} from "../redux/actions/productActions";

const ProductScreen = ({match}) => {

    const dispatch = useDispatch();

    const [currentSrc, setCurrentSrc] = useState()
    const [srcOptions, setSrcOptions] = useState()


    const productDetails = useSelector((state) => state.getProductDetails);
    const {loading, error, product} = productDetails;


    useEffect(() => {
      if (product && match.params.id !== product._id) {
        dispatch(getProductDetails(match.params.id));
        setCurrentSrc(product.src);
        setSrcOptions(product.srcList);
      }
    }, [dispatch, match, product]);
return (
<div className="productcreen">
  {loading ? (
  <h2> Loading...</h2>) : error ? (
  <h2>{error}</h2>) : (
  <>
    <div className='sizebuttons'>
    {srcOptions.map((src) => (
        <button onClick={() => setCurrentSrc(src)}>{src}{product.size}</button>
    ))}
    {srcOptions.map((src) => (
        <button onClick={() => setCurrentSrc(src)}>{src2}{product.size2}</button>
    ))}
    {srcOptions.map((src) => (
        <button onClick={() => setCurrentSrc(src)}>{src3}{product.size3}</button>
    ))}
    </div>
    <div className="productscreen__right">
      <model-viewer id="model-viewer" src={currentSrc} alt={product.name} ar ar-modes="scene-viewer quick-look" ar-placement="floor" shadow-intensity="1" camera-controls min-camera-orbit={product.mincameraorbit} max-camera-orbit={product.maxcameraorbit} interaction-prompt="none">
        <button slot="ar-button" className="ar-button">
                    View in your space
                  </button>
      </model-viewer>
    </div>
    </> )} )};

Here is what the DB looks like: CurrentDB

The "product.size" is being pulled in from MongoDB, and I'm wondering if I could just swap models with: "product.src","product.src2","product.src3" (which is also defined in the DB already) I'm assuming I need to use useState in order to switch the source, but I am unsure. Any help would be greatly appreciated! If you'd like to see the static webpage of what I'm trying to accomplish, you can view it here if that helps at all.

Here is how the products are being exported in redux:

import * as actionTypes from '../constants/productConstants';
import axios from 'axios';

export const getProductDetails = (id) => async(dispatch) => {
  try {dispatch({type: actionTypes.GET_PRODUCT_DETAILS_REQUEST});

    const {data} = await axios.get(`/api/products/${id}`);

    dispatch({
      type: actionTypes.GET_PRODUCT_DETAILS_SUCCESS,
      payload: data,
    });
  } catch (error) {
    dispatch({
      type: actionTypes.GET_PRODUCT_DETAILS_FAIL,
      payload: error.response && error.response.data.message ?
        error.response.data.message :
        error.message,
    });
  }
};

1 Answers1

2

You can use the useState hook from React to create the state. After you fetch your product from the DB you can set the initial value with setCurrentSrc or if it's coming from props, you can set the initial value like this: const [currentSrc, setCurrentSrc] = useState(props.product.src).

Then change the src of your model-viewer to use the state value so it will automatically rerender if the state value changes. Lastly, add onClick handlers to some buttons with the setCurrentSrc function to change the state.

const ProductViewer = (props) => {
  const [currentSrc, setCurrentSrc] = useState()
  const [srcOptions, setSrcOptions] = useState()

  const dispatch = useDispatch()
  const { loading, error, product } = useSelector(
    (state) => state.getProductDetails
  )
  useEffect(() => {
    if (product && match.params.id !== product._id) {
      dispatch(getProductDetails(match.params.id))
    }
  }, [dispatch, match, product])

  // update src and srcOptions when product changes
  useEffect(() => {
    setCurrentSrc(product.src)
    setSrcOptions(product.srcList)
  }, [product])

  return (
    <div className="productscreen__right">
      <model-viewer
        id="model-viewer"
        src={currentSrc}
        alt={product.name}
        ar
        ar-modes="scene-viewer quick-look"
        ar-placement="floor"
        shadow-intensity="1"
        camera-controls
        min-camera-orbit={product.mincameraorbit}
        max-camera-orbit={product.maxcameraorbit}
        interaction-prompt="none"
      >
        <button slot="ar-button" className="ar-button">
          View in your space
        </button>

        {/* add your switch buttons somewhere... */}
        {/* this assumes you have a srcList, but this could also be hardcoded */}
        {srcOptions.map((src) => (
          <buttton onClick={() => setCurrentSrc(src)}>{src}</buttton>
        ))}
      </model-viewer>
    </div>
  )
}
RyanDay
  • 1,856
  • 16
  • 23
  • Thank you for the comment! I will see what I can achieve and report back! I am using useEffect, useSelector, and useDispatch to retrieve my parameters. Like so: `const dispatch = useDispatch(); const productDetails = useSelector((state) => state.getProductDetails); const { loading, error, product } = productDetails; useEffect(() => { if (product && match.params.id !== product._id) { dispatch(getProductDetails(match.params.id)); } }, [dispatch, match, product]);` – Wade Morrison Nov 02 '21 at 20:04
  • Ok, cool. I just edited the code sample based on your comment. Try it out and lmk. – RyanDay Nov 02 '21 at 20:18
  • Alright, I played around with it last night and some more this morning, and I get no error in the VSCode terminal when I save it! However, the browser throws an error saying `Cannot read properties of undefined (reading 'map')` I'm assuming it's because I don't have a srcList like you mentioned above. Instead everything is inside one JSON document, so maybe I just need to re-format my DB? I've edited my question above on how the DB looks as well as how I'm exporting that out in redux and such. – Wade Morrison Nov 03 '21 at 12:01
  • Ayyyy I got it after more fumbling around with it!! Thank you so much for pointing me in the right direction! Now to move onto the other problem haha. I basically used what you had originally but left out `setSrcOptions(product.srcList)` as I had no srcList, and then I had to tweak the button slightly to ` setCurrentSrc(product.src)}>` I also left out the `{srcOptions.map((src) => ())}` part as that was causing it to not work either. I will go ahead and mark yours as the answer though as I'm sure someone else could have a similar/different setup than me. – Wade Morrison Nov 03 '21 at 20:28