4
\$\begingroup\$

I'm looking for some feedback on a pomodoro clock that I recently wrote with React.

I'm pretty new to React and coding in general. I followed a couple of online courses, but I'm mostly self-taught. My academic background and my actual job have nothing to do with programming, so I really don't know how to rate my work.

A couple of things I have doubts on:

  • Is the code clean and easily readable?
  • Is there some common practice that I didn't follow?

Of course any other advice or observation would be much appreciated.

Here's the code of the main component, but since I used more modules it would make my day if you could take a look at the github repo.

class App extends React.Component {
 constructor() {
 super();
 this.state = {
 // prop used by SetInterval and ClearInterval
 timerId: 0,
 start: 0,
 // this property is used by tick method
 startTime: "25:00",
 // value displayed by timer
 currentTime: "25:00",
 workTime: "25",
 shortBreakTime: "5",
 longBreakTime: "30",
 // how many pomodoros before long break
 lBDelay: 4,
 pomodorosCompleted: 0,
 // session is Ready when startTime is updated
 sessionReady: "work",
 sessionRunning: "",
 theme: "violet",
 sound: "on",
 // to set the correct CSS variables for svg animation
 animationWasPaused: false
 };
 this.notificationDOMRef = React.createRef();
 }
 componentDidMount() {
 let circle = document.querySelector("circle");
 circle.style.setProperty("--time", "initial");
 }
 // handles notification component
 addNotification(mess, typ) {
 this.notificationDOMRef.current.addNotification({
 message: mess,
 type: typ,
 insert: "top",
 container: "top-right",
 animationIn: ["animated", "fadeIn"],
 animationOut: ["animated", "fadeOut"],
 dismiss: { duration: 5000 },
 dismissable: { click: true }
 });
 };
 prepareNewSession = () => {
 const val = this.state[`${this.state.sessionReady}Time`];
 const time = val < 10 ? `0${val}:00` : `${val}:00`;
 this.setState({
 sessionRunning: "",
 startTime: time,
 currentTime: time
 });
 };
 handleStart = () => {
 const {
 sessionRunning,
 sessionReady,
 startTime,
 animationWasPaused
 } = this.state;
 // disable button if session is already running
 if (sessionRunning) {
 this.addNotification("session has already started", "warning");
 return;
 }
 // toggle session that is starting now
 this.setState({
 sessionRunning: sessionReady,
 start: Date.now(),
 timerId: setInterval(this.setTimer, 1000)
 });
 // start svg timer
 let circle = document.querySelector("circle");
 if (animationWasPaused) {
 circle.style.setProperty("--pauseHandler", "running");
 this.setState({
 animationWasPaused: false
 });
 return;
 }
 const seconds = `${toSeconds(startTime)}s`;
 circle.style.setProperty("--time", seconds);
 circle.style.setProperty("--pauseHandler", "running");
 };
 setTimer = () => {
 const {
 startTime,
 start,
 currentTime,
 timerId,
 sessionReady,
 pomodorosCompleted,
 lBDelay
 } = this.state;
 // convert string to number and then to seconds
 let duration = toSeconds(startTime);
 let display = tick(duration, start);
 this.setState({
 currentTime: display
 });
 if (currentTime === "00:00") {
 clearInterval(timerId);
 this.alarm.play();
 // according to session that just finished update props in state and start new session
 if (sessionReady === "work") {
 // check if we already have n pomodoros completed and need to switch to long break
 this.setState({
 pomodorosCompleted: pomodorosCompleted + 1
 });
 const remainder = (pomodorosCompleted + 1) % lBDelay;
 remainder === 0 ?
 this.handleSelect("longBreak") :
 this.handleSelect("shortBreak");
 } else {
 this.handleSelect("work");
 }
 // give time to update svg circle CSS var
 setTimeout(this.handleStart, 1);
 }
 };
 handleStop = () => {
 let {
 sessionRunning,
 timerId,
 currentTime
 } = this.state;
 if (!sessionRunning) {
 this.addNotification("session isn't running", "warning");
 return;
 }
 clearInterval(timerId);
 // pause svg timer
 const circle = document.querySelector("circle");
 circle.style.setProperty("--pauseHandler", "paused");
 this.setState({
 sessionRunning: "",
 startTime: currentTime,
 animationWasPaused: true
 });
 };
 handleReset = () => {
 const {
 sessionRunning,
 timerId
 } = this.state;
 if (!sessionRunning) {
 this.addNotification("session isn't running", "warning");
 return;
 }
 clearInterval(timerId);
 this.prepareNewSession();
 // reset svg timer
 let circle = document.querySelector("circle");
 circle.style.setProperty("--time", "initial");
 circle.style.setProperty("--pauseHandler", "paused");
 };
 handleChange = ({ target }) => {
 const { name } = target;
 let { value } = target;
 let bgMax;
 let bgMin;
 this.setState({
 [name]: value
 });
 if (value === "0") {
 if (name === "lBDelay") return;
 this.addNotification("Session should last at least one minute", "danger");
 setTimeout(() => this.setState({
 [name]: 1
 }), 800);
 return;
 }
 if (name === "theme") {
 // set values for background
 bgMax = target.options[target.selectedIndex].dataset.max;
 bgMin = target.options[target.selectedIndex].dataset.min;
 updateTheme(value, bgMax, bgMin);
 return;
 }
 };
 // this method sets props in state so that the session is ready to start
 // the session doesn't start automatically, but only when Start button is clicked!
 handleSelect = selected => {
 const {
 sessionRunning,
 timerId
 } = this.state;
 // check if session is already running. If yes display notification, if not update state so session is ready to start
 if (sessionRunning === selected) {
 this.addNotification("Session is already running", "warning");
 return;
 }
 this.setState({
 sessionReady: selected
 }, () => this.prepareNewSession());
 clearInterval(timerId);
 // reset svg timer
 let circle = document.querySelector("circle");
 circle.style.setProperty("--time", "initial");
 circle.style.setProperty("--pauseHandler", "paused");
 };
 validateForm = schema => {
 const errors = this.validate(schema);
 if (errors) {
 const messages = Object.values(errors);
 messages.map(m => this.addNotification(m, "danger"));
 }
 return errors;
 };
 validate = schema => {
 const {
 workTime,
 shortBreakTime,
 longBreakTime,
 lBDelay
 } = this.state;
 const { error } = Joi.validate({
 workTime: workTime,
 shortBreakTime: shortBreakTime,
 longBreakTime: longBreakTime,
 lBDelay: lBDelay
 },
 schema, {
 abortEarly: false
 }
 );
 if (!error) return null;
 let errors = {};
 error.details.map(i => {
 errors[i.path[0]] = i.message;
 });
 return errors;
 };
 saveChangesInSettings = () => {
 this.prepareNewSession();
 // trigger notification
 this.addNotification("Changes have been saved!", "success");
 };
 progressTracker = () => {
 const { pomodorosCompleted } = this.state;
 return ( <
 span className = "d-flex justify-content-center pb-5" > {
 `You have completed ${pomodorosCompleted} ${
 pomodorosCompleted === 1 ? "pomodoro" : "pomodoros"
 }`
 } <
 /span>
 );
 };
 alarm = new UIfx({
 asset: alarm
 });
 render() {
 const {
 workTime,
 shortBreakTime,
 longBreakTime,
 currentTime,
 sessionReady,
 lBDelay,
 pomodorosCompleted,
 theme,
 sound
 } = this.state;
 return ( 
 <React.Fragment>
 <div id="background">
 <Container>
 <div>
 <Navigationbar
 activeKeyInNav={sessionReady}
 workTime={workTime}
 shortBreakTime={shortBreakTime}
 longBreakTime={longBreakTime}
 lBDelay={lBDelay}
 theme={theme}
 sound={sound}
 saveChanges={this.saveChangesInSettings}
 validateForm={this.validateForm}
 onChange={this.handleChange}
 onSelect={this.handleSelect}
 />
 <ReactNotification ref={this.notificationDOMRef} />
 <Timer
 currentTime={currentTime}
 pomodorosCompleted={pomodorosCompleted}
 />
 <TimerControls
 onStart={this.handleStart}
 onStop={this.handleStop}
 onReset={this.handleReset}
 />
 </div>
 <div id="backgroundLarge" />
 </Container>
 </div>
 <Container className="pt-4">
 <ToDoListForm />
 <hr />
 {this.progressTracker()}
 </Container>
 </React.Fragment>
 );
 }
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Aug 17, 2019 at 14:24
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.