Skip to content
Advertisement

React useState conversion

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,
    });
  }
};

Advertisement

Answer

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>
  )
}
Advertisement