diff --git a/README.md b/README.md index 02a9061e8..9c257fe8c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
-

Reactime is a performance and debugging tool for React developers (Beta version for Gatsby and Next.js). It records a snapshot whenever a target application's state is changed and allows the user to jump to any previously recorded state. It also detects the amount of renders of each component and average time of rendering.

+

Reactime is an open source Chrome developer tool for time travel debugging and performance monitoring in React applications. Reactime enables developers to record snapshots of application state, jump between and inspect state snapshots, and monitor performance metrics such as component render time and render frequency.


@@ -43,18 +43,18 @@ How To Use • Features • Website • Read More

-Currently, Reactime supports React apps using stateful components and Hooks, with beta support for Recoil and Context API and frameworks like Gatsby and Next.js. +Currently, Reactime supports React apps (now including React Router apps) using stateful components and Hooks, with beta support for Recoil and Context API and frameworks like Gatsby and Next.js. -Reactime 13.0 has added the exciting features below: +Reactime 14.0 has added the exciting features below: -I. Action Comparison Tool -Users now have the ability to name, save, and analyze specific action snapshots within a saved series. This feature allows engineers to compare component render times throughout the development process of their application, providing them with metrics to show any improvements or changes. +I. React Router Compatibility
+Reactime is now compatible with React Router applications! Prior to Reactime 14.0, recording state snapshots as the user navigated across various routes was possible, but time travel debugging was only possible for the current route (i.e. jumping back to a prior state at a different route was not possible). In order to streamline debugging of applications with multiple routes, Reactime 14.0 added functionality that allows the user to time-travel back to different routes, including live updating in the browser to reflect the state of their application at that previously visited route. -II. Reactime Visual Tutorial Walkthrough -While Reactime offers a user friendly and intuitive interface, users can now access a guided tutorial, walking the user through each feature while explaining practical use cases and added benefits that Reactime can provide. The walkthrough utilizes the Intro.js library, providing a visual experience that highlights and cycles through each COMPONENT displayed on the app. +II. Classifying State Snapshots by Route
+The list of state snapshots in the Reactime dashboard is now classified by route to give the developer visual cues of the snapshot-route relationship and make time travel debugging of various routes easier. -III. State Monitoring Toggle Feature -Added toggle feature allows users to temporarily pause Reactime's state monitoring of the linked application. This allows users to make state changes within their application without populating the actions container within Reactime. Especially useful when trying to limit and compare the number of actions within one series that a user is planning to save. Relinking Reactime to the application is as simple as toggling the record button back to it's original state! +III. Filtering Performance Metrics by Route
+The Reactime dashboard includes a stacked bar graph showing render times for each component, with a separate bar stack for each snapshot. With Reactime 14.0, this composite bar graph can now be filtered by route to allow the developer to review detailed performance data by route. After installing Reactime, you can test its functionalities with your React application in development mode. @@ -92,38 +92,52 @@ Reactime is an open source project, and we’d really appreciate your help with ## Features -### 🔹 Re-render Optimization - -One of the most common issues that affects performance in React is unnecessary render cycles. This problem can be fixed by checking your renders in the Performance tab in Chrome DevTools under the Reactime panel. - -### 🔹 Gatsby +### 🔹 Viewing -Reactime offers fully support for Gatsby applications. You would be able to identify unnecessary renders, duration of each rendering, travel-debugging features and visual representation of the tree components. +You can view your application's file structure and click on a snapshot to view your app's state. State can be visualized in a Component Graph, JSON Tree, or Performance Graph. Snapshots can be diffed with the previous snapshot, which can be viewed in Diff mode. +
+
+

+ +

+
-### 🔹 Next.js +### 🔹 Snapshot Series and Action Comparison -Reactime offers debugging and performance tools for Next.js apps: time-traveling debugging, preventing unnecessary components re-renders and making your application faster. +You can save a series of state snapshots and use it to analyze changes in component render performance between current and previous series of snapshots. You can also name specific snapshots and compare all snapshots with the same name. +
+
+

+ +

+
### 🔹 Recording Whenever state is changed (whenever setState, useState is called), this extension will create a snapshot of the current state tree and record it. Each snapshot will be displayed in Chrome DevTools under the Reactime panel. - -### 🔹 Snapshot Series and Action Comparison - -You can save a series of state snapshots and use it to analyze changes in component render performance between current and previous series of snapshots. You can also name specific snapshots and compare all snapshots with the same name. +
+

- +


-### 🔹 Viewing +### 🔹 Re-render Optimization -You can click on a snapshot to view your app's state. State can be visualized in a Component Graph, JSON Tree, or Performance Graph. Snapshots can be diffed with the previous snapshot, which can be viewed in Diff mode. +One of the most common issues that affects performance in React is unnecessary render cycles. This problem can be fixed by checking your renders in the Performance tab in Chrome DevTools under the Reactime panel. ### 🔹 Jumping Using the actions sidebar, a user can jump to any previous recorded snapshots. Hitting the jump button on any snapshot will allow a user to view state data at any point in the history of the target application. +### 🔹 Gatsby + +Reactime offers full support for Gatsby applications. You would be able to identify unnecessary renders, duration of each rendering, travel-debugging features and visual representation of the tree components. + +### 🔹 Next.js + +Reactime offers debugging and performance tools for Next.js apps: time-traveling debugging, preventing unnecessary components re-renders and making your application faster. + ### 🔹 TypeScript Support Reactime offers beta support for TypeScript applications using stateful class components and functional components. Further testing and development is required for custom hooks, Context API, and Concurrent Mode. @@ -133,11 +147,6 @@ Reactime offers beta support for TypeScript applications using stateful class co After cloning this repository, developers can simply run `npm run docs` at the root level and serve the dynamically generated `/docs/index.html` file on a browser. Doing so will provide a readable, extensible, and interactive GUI view of the structure and interfaces of the codebase.
-

- -

-
- ### Additional Features - Identifying unnecessary re-renders @@ -222,6 +231,11 @@ After cloning this repository, developers can simply run `npm run docs` at the r - **Kristina Wallen** - [@kristinawallen](https://github.com/kristinawallen) - **Quan Le** - [@blachfog](https://github.com/Blachfog) - **Robert Maeda** - [@robmaeda](https://github.com/robmaeda) +- **David Kim** - [@codejunkie7](https://github.com/codejunkie7) +- **Robby Tipton** - [@RobbyTipton](https://github.com/RobbyTipton) +- **Kevin HoEun. Lee** - [@khobread](https://github.com/khobread) +- **Christopher LeBrett** - [@fscgolden](https://github.com/fscgolden) +- **Joseph Park** - [@joeepark](https://github.com/joeepark) ## License diff --git a/assets/action-comparison.gif b/assets/action-comparison.gif new file mode 100644 index 000000000..4a50300ce Binary files /dev/null and b/assets/action-comparison.gif differ diff --git a/assets/history-tree.gif b/assets/history-tree.gif new file mode 100644 index 000000000..f53a30fce Binary files /dev/null and b/assets/history-tree.gif differ diff --git a/assets/map-viewing.gif b/assets/map-viewing.gif new file mode 100644 index 000000000..e1028a540 Binary files /dev/null and b/assets/map-viewing.gif differ diff --git a/assets/new-reactime.gif b/assets/new-reactime.gif index 1593005a7..7eae93979 100644 Binary files a/assets/new-reactime.gif and b/assets/new-reactime.gif differ diff --git a/demo-app/.babelrc b/demo-app/.babelrc new file mode 100644 index 000000000..a45d619c1 --- /dev/null +++ b/demo-app/.babelrc @@ -0,0 +1,7 @@ + +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ] +} \ No newline at end of file diff --git a/demo-app/.gitignore b/demo-app/.gitignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/demo-app/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/demo-app/package.json b/demo-app/package.json new file mode 100644 index 000000000..2f37d8da9 --- /dev/null +++ b/demo-app/package.json @@ -0,0 +1,36 @@ +{ + "name": "typescript-module", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "devDependencies": { + "@babel/core": "^7.16.7", + "@babel/preset-env": "^7.16.7", + "@babel/preset-react": "^7.16.7", + "@types/express": "^4.17.13", + "@types/node": "^17.0.8", + "@types/react": "^17.0.38", + "@types/react-dom": "^17.0.11", + "babel-loader": "^8.2.3", + "copy-webpack-plugin": "^10.2.0", + "css-loader": "^6.5.1", + "html-webpack-plugin": "^5.5.0", + "nodemon": "^2.0.15", + "ts-loader": "^9.2.6", + "typescript": "^4.5.4", + "webpack": "^5.65.0", + "webpack-cli": "^4.9.1", + "webpack-dev-server": "^4.7.2" + }, + "dependencies": { + "express": "^4.17.2", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "react-router-dom": "^6.3.0", + "ts-node": "^10.4.0" + } +} diff --git a/demo-app/src/client/Components/Board.tsx b/demo-app/src/client/Components/Board.tsx new file mode 100644 index 000000000..18e610cf8 --- /dev/null +++ b/demo-app/src/client/Components/Board.tsx @@ -0,0 +1,157 @@ +import React, { Component } from 'react'; +import Row from './Row'; +import { BoardText, BoardContent, Scoreboard, Player } from './../../types'; + +type BoardState = { + board: BoardContent; + currentPlayer: Player; + gameOver: boolean; + message: string; + scoreboard: Scoreboard; +}; + +class Board extends Component<{}, BoardState> { + constructor(props: any) { + super(props); + this.state = { + board: this.newBoard(), + currentPlayer: 'X', + gameOver: false, + message: '', + scoreboard: { X: 0, O: 0 }, + }; + + this.resetBoard = this.resetBoard.bind(this); + this.handleBoxClick = this.handleBoxClick.bind(this); + } + + componentDidUpdate() { + this.checkForWinner(); + } + + /** + * @method newBoard + * @description - returns a blank BoardContent array, + * for the start of a new game + */ + newBoard(): BoardContent { + return [ + ['-', '-', '-'], + ['-', '-', '-'], + ['-', '-', '-'], + ]; + } + + /** + * @method resetBoard + * @description - sets to board object to be all '-', + * and sets gameOver and message to default state + */ + resetBoard(): void { + this.setState({ + gameOver: false, + board: this.newBoard(), + message: '', + }); + } + + /** + * @method checkForWinner + * @description - checks to see if either player has filled a row + * if so, ends the game and updates the message to declare winner + */ + checkForWinner(): void { + const { board, gameOver, currentPlayer } = this.state; + + const spacesLeft = (): boolean => { + for (let i of board) { + if (i.includes('-')) return true; + } + return false; + }; + + if (!gameOver) { + // win conditions: matching rows, columns, or diagonals, that are not empty('-') + if ( + (board[0][0] === board[0][1] && + board[0][1] === board[0][2] && + board[0][2] !== '-') || + (board[1][0] === board[1][1] && + board[1][1] === board[1][2] && + board[1][2] !== '-') || + (board[2][0] === board[2][1] && + board[2][1] === board[2][2] && + board[2][2] !== '-') || + (board[0][0] === board[1][0] && + board[1][0] === board[2][0] && + board[2][0] !== '-') || + (board[0][1] === board[1][1] && + board[1][1] === board[2][1] && + board[2][1] !== '-') || + (board[0][2] === board[1][2] && + board[1][2] === board[2][2] && + board[2][2] !== '-') || + (board[0][0] === board[1][1] && + board[1][1] === board[2][2] && + board[2][2] !== '-') || + (board[2][0] === board[1][1] && + board[1][1] === board[0][2] && + board[0][2] !== '-') + ) { + // winner is the person who's turn was previous + const winner: Player = currentPlayer === 'X' ? 'O' : 'X'; + + this.setState({ + gameOver: true, + message: `Player ${winner} wins!`, + }); + + // draw condition: no '-' remaining in board without above win condition triggering + } else if (!spacesLeft()) { + this.setState({ + gameOver: true, + message: 'Draw!', + }); + } + } + } + + handleBoxClick(row: number, column: number): void { + const boardCopy: BoardContent = [ + [...this.state.board[0]], + [...this.state.board[1]], + [...this.state.board[2]], + ]; + boardCopy[row][column] = this.state.currentPlayer; + const newPlayer: Player = this.state.currentPlayer === 'X' ? 'O' : 'X'; + this.setState({ board: boardCopy, currentPlayer: newPlayer }); + } + + render() { + const rows: Array = []; + for (let i = 0; i < 3; i++) { + rows.push( + + ); + } + const { X, O }: Scoreboard = this.state.scoreboard; + + return ( +
+

Tic Tac Toe

+ {this.state.gameOver &&

{this.state.message}

} + {rows} + +
+ ); + } +} + +export default Board; diff --git a/demo-app/src/client/Components/Box.tsx b/demo-app/src/client/Components/Box.tsx new file mode 100644 index 000000000..53b79548d --- /dev/null +++ b/demo-app/src/client/Components/Box.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { BoardText } from '../../types'; + +type BoxProps = { + value: BoardText; + row: number; + column: number; + handleBoxClick: (row: number, column: number) => void; +}; + +const Box = (props: BoxProps) => { + return ( + + ); +}; + +export default Box; diff --git a/demo-app/src/client/Components/Buttons.tsx b/demo-app/src/client/Components/Buttons.tsx new file mode 100644 index 000000000..af0ffa7a8 --- /dev/null +++ b/demo-app/src/client/Components/Buttons.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import Increment from "./Increment"; + +function Buttons() { + const buttons = []; + for (let i = 0; i < 4; i++){ + buttons.push() + } + + return( +
+

Stateful Buttons

+

These buttons are functional components that each manage their own state with the useState hook.

+ {buttons} +
+ ) +} + +export default Buttons; \ No newline at end of file diff --git a/demo-app/src/client/Components/Home.tsx b/demo-app/src/client/Components/Home.tsx new file mode 100644 index 000000000..2f38d48c6 --- /dev/null +++ b/demo-app/src/client/Components/Home.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +function Home() { + return ( +
+

Lorem Ipsum

+

+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum." +

+

+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum." +

+
+ ); +} + +export default Home; diff --git a/demo-app/src/client/Components/Increment.tsx b/demo-app/src/client/Components/Increment.tsx new file mode 100644 index 000000000..73fc98650 --- /dev/null +++ b/demo-app/src/client/Components/Increment.tsx @@ -0,0 +1,13 @@ +import React, { useState } from 'react'; + +function Increment() { + const [count, setCount] = useState(0); + + return ( + + ); +} + +export default Increment; diff --git a/demo-app/src/client/Components/Nav.tsx b/demo-app/src/client/Components/Nav.tsx new file mode 100644 index 000000000..eb7c28842 --- /dev/null +++ b/demo-app/src/client/Components/Nav.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +function Nav() { + return( +
+ About + Tic-Tac-Toe + Counter +
+ ) +} + +export default Nav; \ No newline at end of file diff --git a/demo-app/src/client/Components/Row.tsx b/demo-app/src/client/Components/Row.tsx new file mode 100644 index 000000000..d9bf89ca6 --- /dev/null +++ b/demo-app/src/client/Components/Row.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import Box from './Box'; +import { BoardText } from '../../types'; + +type RowProps = { + handleBoxClick: (row: number, column: number) => void; + values: Array; + row: number; +}; + +const Row = (props: RowProps) => { + const boxes: Array = []; + for (let i = 0; i < 3; i++) { + boxes.push( + + ); + } + + return
{boxes}
; +}; + +export default Row; diff --git a/demo-app/src/client/Router.tsx b/demo-app/src/client/Router.tsx new file mode 100644 index 000000000..08a1e08ad --- /dev/null +++ b/demo-app/src/client/Router.tsx @@ -0,0 +1,19 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import Nav from "./Components/Nav"; +import Board from "./Components/Board"; +import Home from "./Components/Home"; +import Buttons from "./Components/Buttons"; + +const root = document.getElementById("root"); + +ReactDOM.render( + +