简体   繁体   中英

Setting state before render react

So I'm writing a simple App that looks like the following: 3 different color buttons that once pressed change the background of the parent div they're housed in and when pressed again, change the background back to the original.

The background change is an animation that originates from the button itself (using clip-path circle css) and after feeding the correct coordinates to the style, it does the job.

Problem is, every new click on a button doesn't set the state before the render and so the green background originates from the blue background button because those are the old coordinates and currently the state.

I've read that in react there is no before, only after and I've looking into the componentwillmount function but that was advised me not to set state in it so I'm staying clear of it.

Is there any way to fix this issue? Or is this just something react cant' do?

The following is my App code (the useEffect bit is just so that the background doesn't originate from coordinates 0 0 which is the default in the css and sets the state based off of certain coordinates).

import './App.css';
import {useEffect, useState} from 'react';

function App() {
  const [box, setBox] = useState({active: false, color: '', left: '', top: ''});
  // const [globalCoords, setGlobalCoords] = useState({x: 0, y: 0})
  useEffect(() => {
    const handleWindowMouseMove = event => {
      if ((event.x < 350 && event.x > 250) && (event.y > 250 && event.y < 350)) {
        setBox({left: '300px', top: '300px'})
      } else if ((event.x < 1600 && event.x > 1500) && (event.y > 50 && event.y < 150)) {
        setBox({left: '1550px', top: '100px'})
      } else if ((event.x < 1100 && event.x > 1000) && (event.y > 500 && event.y < 600)) {
        setBox({left: '1050px', top: '550px'})
      }
    };
    document.addEventListener('mousemove', handleWindowMouseMove);

    return () => {
      document.removeEventListener('mousemove', handleWindowMouseMove);
    };
  }, []);
  // console.log(globalCoords);

  return (
    <section>
      <div style={{'--color': box.color, '--left': box.left, '--top': box.top}} className={box.active ? 'box active' : 'box'}></div>
      <div style={{backgroundColor: box.color === 'orange' ? 'black' : ''}} onClick={() => setBox({active: !box.active, color: 'orange', left: '300px', top: '300px'})} className={box.active ? (box.color === 'orange' ? 'pulse1' : 'pulse1 disable') : 'pulse1'}>
        <span style={{'--i': 0}}></span>
        <span style={{'--i': 1}}></span>
        <span style={{'--i': 2}}></span>
        <span style={{'--i': 3}}></span>
      </div>
      <div style={{backgroundColor: box.color === 'turquoise' ? 'black' : ''}} onClick={() => setBox({active: !box.active, color: 'turquoise', left: '1550px', top: '100px'})} className={box.active ? (box.color === 'turquoise' ? 'pulse2' : 'pulse2 disable') : 'pulse2'}>
        <span style={{'--i': 0}}></span>
        <span style={{'--i': 1}}></span>
        <span style={{'--i': 2}}></span>
        <span style={{'--i': 3}}></span>
      </div>
      <div style={{backgroundColor: box.color === 'red' ? 'black' : ''}} onClick={() => setBox({active: !box.active, color: 'red', left: '1050px', top: '550px'})} className={box.active ? (box.color === 'red' ? 'pulse3' : 'pulse3 disable') : 'pulse3'}>
        <span style={{'--i': 0}}></span>
        <span style={{'--i': 1}}></span>
        <span style={{'--i': 2}}></span>
        <span style={{'--i': 3}}></span>
      </div>
    </section>
  );
}

export default App;

The following is my css:

:root {
  --i: 0;
  --top: 0;
  --left: 0;
  --color: transparent;
}

*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

section {
  min-height: 100vh;
  width: 100vw;
  background-color: #102e37;
  position: absolute;
  top: 0;
  left: 0;
}

.box {
  position: absolute;
  height: 100%;
  width: 100%;
  background-color: var(--color);
  transition: clip-path 1s linear;
  clip-path: circle(0% at var(--left) var(--top));
}

.box.active {
  clip-path: circle(150% at var(--left) var(--top));
}

.pulse1 {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: orange;
  border-radius: 50%;
  top: 250px;
  left: 250px;
  animation: intro 5s;
  animation-delay: 0s;
}

.pulse1 span {
  position: absolute;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background-color: inherit;
  opacity: .8;
  animation: pulseAnimate 4s ease-out infinite;
  animation-delay: calc(1s * var(--i));
}

@keyframes pulseAnimate {
  100% {
    opacity: 0;
    transform: scale(2);
  }
}

.pulse2 {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: turquoise;
  border-radius: 50%;
  top: 50px;
  left: 1500px;
  animation: intro 5s;
  animation-delay: 0s;
}

.pulse2 span {
  position: absolute;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background-color: inherit;
  opacity: .8;
  animation: pulseAnimate 4s ease-out infinite;
  animation-delay: calc(1s * var(--i));
}

.pulse3 {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: red;
  border-radius: 50%;
  top: 500px;
  left: 1000px;
  animation: intro 5s;
  animation-delay: 0s;
}

.pulse3 span {
  position: absolute;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background-color: inherit;
  opacity: .8;
  animation: pulseAnimate 4s ease-out infinite;
  animation-delay: calc(1s * var(--i));
}

.pulse1.disable {
  animation: seconds .8s both;
  animation-delay: 0s;
}

.pulse2.disable {
  animation: seconds .8s both;
  animation-delay: 0s;
}

.pulse3.disable {
  animation: seconds .8s both;
  animation-delay: 0s;
}

@keyframes seconds {
  0% {
      opacity: 1;
  }
  100% {
      opacity: 0;
  }
} 

@keyframes intro {
  0% {
      opacity: 0;
  }
  100% {
      opacity: 1;
  }
} 
import "./styles.css";

import { useEffect, useLayoutEffect, useRef, useState } from "react";

function App() {
  const [box, setBox] = useState({
    active: false,
    color: "",
    left: "",
    top: ""
  });
  const [key, setKey] = useState(0);
  // const [globalCoords, setGlobalCoords] = useState({x: 0, y: 0})

  const nextBoxCoords = useRef({ left: "", top: "" });
  console.log(nextBoxCoords, box);
  useEffect(() => {
    const handleWindowMouseMove = (event) => {
      if (event.x < 350 && event.x > 250 && event.y > 250 && event.y < 350) {
        nextBoxCoords.current = {
          left: "300px",
          top: "300px"
        };
      } else if (
        event.x < 1600 &&
        event.x > 1500 &&
        event.y > 50 &&
        event.y < 150
      ) {
        nextBoxCoords.current = {
          left: "1550px",
          top: "100px"
        };
      } else if (
        event.x < 1100 &&
        event.x > 1000 &&
        event.y > 500 &&
        event.y < 600
      ) {
        nextBoxCoords.current = {
          left: "1050px",
          top: "550px"
        };
      }
    };
    document.addEventListener("mousemove", handleWindowMouseMove);

    return () => {
      document.removeEventListener("mousemove", handleWindowMouseMove);
    };
  }, []);
  // console.log(globalCoords);

  const commit = ({ color, active }) => {
    setKey((prev) => prev + 1);
    setBox((prev) => ({
      ...prev,
      left: nextBoxCoords.current.left,
      top: nextBoxCoords.current.top
    }));

    setTimeout(() => {
      setBox((prev) => ({
        left: nextBoxCoords.current.left,
        top: nextBoxCoords.current.top,
        color,
        active
      }));
    }, 0);
  };

  return (
    <section key={key}>
      <div
        style={{
          "--color": box.color,
          "--left": box.left,
          "--top": box.top
        }}
        className={box.active ? "box active" : "box"}
      ></div>
      <div
        style={{ backgroundColor: box.color === "orange" ? "black" : "" }}
        onClick={() => {
          commit({
            active: !box.active,
            color: "orange"
          });
        }}
        className={
          box.active
            ? box.color === "orange"
              ? "pulse1"
              : "pulse1 disable"
            : "pulse1"
        }
      >
        <span style={{ "--i": 0 }}></span>
        <span style={{ "--i": 1 }}></span>
        <span style={{ "--i": 2 }}></span>
        <span style={{ "--i": 3 }}></span>
      </div>
      <div
        style={{ backgroundColor: box.color === "turquoise" ? "black" : "" }}
        onClick={() => {
          commit({
            active: !box.active,
            color: "turquoise"
          });
        }}
        className={
          box.active
            ? box.color === "turquoise"
              ? "pulse2"
              : "pulse2 disable"
            : "pulse2"
        }
      >
        <span style={{ "--i": 0 }}></span>
        <span style={{ "--i": 1 }}></span>
        <span style={{ "--i": 2 }}></span>
        <span style={{ "--i": 3 }}></span>
      </div>
      <div
        style={{ backgroundColor: box.color === "red" ? "black" : "" }}
        onClick={() => {
          commit({
            active: !box.active,
            color: "red"
          });
        }}
        className={
          box.active
            ? box.color === "red"
              ? "pulse3"
              : "pulse3 disable"
            : "pulse3"
        }
      >
        <span style={{ "--i": 0 }}></span>
        <span style={{ "--i": 1 }}></span>
        <span style={{ "--i": 2 }}></span>
        <span style={{ "--i": 3 }}></span>
      </div>
    </section>
  );
}

export default App;

This hacks around with resetting the animation to get the position where it needs to be, and avoids slow state updates by using refs.

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