Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: update to latest react version #39

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import React from 'react'
import { GitHubButtonProps } from 'github-buttons'
import React from 'react';

interface ReactGitHubButtonProps extends GitHubButtonProps {
export interface GitHubButtonProps {
href: string;
'data-color-scheme'?: 'no-preference' | 'light' | 'dark';
'data-icon'?: 'octicon-star' | 'octicon-repo-forked' | 'octicon-eye' | 'octicon-issue-opened' | 'octicon-git-pull-request';
'data-size'?: 'large' | 'small';
'data-show-count'?: boolean | 'true' | 'false';
'data-text'?: string;
'aria-label'?: string;
children?: React.ReactNode;
}

export default class GitHubButton extends React.PureComponent<ReactGitHubButtonProps> {}
export type ReactGitHubButtonProps = GitHubButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>;

declare const GitHubButton: React.FC<ReactGitHubButtonProps>;

export default GitHubButton;
105 changes: 64 additions & 41 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,65 @@
import React, { PureComponent } from 'react'

class GitHubButton extends PureComponent {
constructor (props) {
super(props)
this.$ = React.createRef()
this._ = React.createRef()
}
render () {
return React.createElement('span', { ref: this.$ }, React.createElement('a', { ...this.props, ref: this._ }, this.props.children))
}
componentDidMount () {
this.paint()
}
getSnapshotBeforeUpdate () {
this.reset()
return null
}
componentDidUpdate () {
this.paint()
}
componentWillUnmount () {
this.reset()
}
paint () {
const _ = this.$.current.appendChild(document.createElement('span'))
import(/* webpackMode: "eager" */ 'github-buttons').then(({ render }) => {
if (this._.current != null) {
render(_.appendChild(this._.current), function (el) {
try {
_.parentNode.replaceChild(el, _)
} catch (_) {}
})
import React, { useCallback, useEffect, useRef } from "react";

const GitHubButton = React.memo(({ children, ...props }) => {
const containerRef = useRef(null);
const buttonRef = useRef(null);

const paint = useCallback(() => {
if (!containerRef.current) return;

const tempSpan = document.createElement("span");
containerRef.current.appendChild(tempSpan);

import(/* webpackMode: "eager" */ "github-buttons")
.then(({ render }) => {
if (
buttonRef.current &&
containerRef.current &&
containerRef.current.lastChild === tempSpan
) {
render(tempSpan.appendChild(buttonRef.current), (el) => {
Copy link
Contributor

@ntkme ntkme Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial state:

<span> <!-- containerRef-->
  <a> <!-- buttonRef -->
  </a>
</span>

After adding a tempSpan:

<span> <!-- containerRef-->
  <a></a> <!-- buttonRef -->
  <span></span> <!-- tempSpan -->
</span>

With tempSpan.appendChild(buttonRef.current):

<span> <!-- containerRef-->
  <span> <!-- tempSpan -->
    <a></a> <!-- buttonRef -->
  </span>
</span>

Therefore, when restoring the state on error or inside useEffect, it should be:

containerRef.current.replaceChild(buttonRef.current, containerRef.current.lastChild);

So that it goes back to initial state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason we have this tempSpan is because this library runs async rendering that dispatches outside of react lifecycles, therefore we want to make sure when we come back to react lifecycle, react should not see any DOM patch that we have done so that it can patch DOM incrementally. In order to do that we have to restore the DOM to the state that react generates. The primary goal of using an additional tempSpan is allow us to easily check if we have patched the DOM outside of react or not, and acting accordingly.

if (
containerRef.current &&
containerRef.current.lastChild === tempSpan
) {
containerRef.current.replaceChild(el, tempSpan);
}
});
}
})
.catch((error) => {
console.error("Error loading github-buttons:", error);
if (
containerRef.current &&
containerRef.current.lastChild === tempSpan
) {
containerRef.current.removeChild(tempSpan);
}
});
}, []);

useEffect(() => {
paint();

return () => {
if (containerRef.current) {
const lastChild = containerRef.current.lastChild;
if (lastChild && lastChild !== buttonRef.current) {
containerRef.current.removeChild(lastChild);
}
}
})
}
reset () {
this.$.current.replaceChild(this._.current, this.$.current.lastChild)
}
}

export default GitHubButton
};
}, [paint]);

return (
<span ref={containerRef}>
<a {...props} ref={buttonRef}>
{children}
</a>
</span>
);
});

GitHubButton.displayName = "GitHubButton";

export default GitHubButton;
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-github-btn",
"version": "1.4.0",
"version": "2.0.0",
"description": "GitHub button component for React.js",
"main": "index.js",
"type": "module",
Expand All @@ -27,7 +27,15 @@
"dependencies": {
"github-buttons": "^2.22.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"typescript": "^4.5.0"
},
"peerDependencies": {
"react": ">=16.3.0"
"react":"^18.3.1"
},
"engines": {
"node": ">=14"
}
}
}