Skip to content

Commit

Permalink
clean up useRainbow, write article about it, fix proptypes validation
Browse files Browse the repository at this point in the history
  • Loading branch information
ayan4m1 committed Dec 19, 2023
1 parent 25f0680 commit 92c0d33
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 21 deletions.
13 changes: 12 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
{
"presets": [["@babel/preset-react", { "runtime": "automatic" }]]
"presets": [["@babel/preset-react", { "runtime": "automatic" }]],
"plugins": [
[
"prismjs",
{
"languages": ["js", "jsx"],
"plugins": ["line-numbers"],
"theme": "tomorrow",
"css": true
}
]
]
}
67 changes: 67 additions & 0 deletions articles/react/use-rainbow-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
path: /react/use-rainbow-hook
date: 2023-12-05
description: A simple custom hook for lerping through the color wheel.
title: useRainbow Hook
---

Let's put together a simple hook in React that performs "linear interpolation" through the color wheel using hooks intelligently. This will give us a "useRainbow" hook that we can use to animate the color of anything on your page.

## The Hook

```jsx
// d3-interpolate handles the linear interpolation math
import { interpolateHslLong } from 'd3-interpolate';
import { useEffect, useMemo, useRef, useState } from 'react';

export default function useRainbow(
startColor = '#ff0000', // start at red
endColor = '#0000ff', // end at blue
timeDilation = 2000 // slow time down by a factor of 2000, this depends on your CPU/GPU
) {
// interpolateHslLong takes two colors and returns a function which can interpolate between them.
// here, useMemo helps us only recalculate the interpolation function if/when the input colors change
const interpolator = useMemo(
() => interpolateHslLong(startColor, endColor),
[startColor, endColor]
);
// requestId will be used to store the temporary ID assigned to an in-flight animation frame request.
// if the component unmounts, we will cancel this in-flight request to stop the render loop.
const requestId = useRef(null);
// this stores the current color which this hook returns directly
const [color, setColor] = useState(null);

// this function is called every "frame" and updates the output color
const animate = (time) => {
// (time / timeDilation) will increase monotonically. Pass it into abs(sin()) to convert it to a
// infinite sweep between 0 and 1. This is the input range for the interpolator function.
setColor(interpolator(Math.abs(Math.sin(time / timeDilation))));

// this is what causes the rendering to loop "infinitely"
requestId.current = requestAnimationFrame(animate);
};

useEffect(() => {
// kick off the first animation frame request
requestId.current = requestAnimationFrame(animate);

// if the component unmounts, cancel the render loop
return () => cancelAnimationFrame(requestId.current);
}, []);

// return the current color
return color;
}
```

## Example Usage

```jsx
import useRainbow from 'hooks/useRainbow';

export default function Component() {
const color = useRainbow();

return <p style={{ color }}>Hello World</p>;
}
```
5 changes: 5 additions & 0 deletions data/articleCategories.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
"name": "electronics",
"title": "Electronics",
"description": "Writing about electronics design and integration."
},
{
"name": "react",
"title": "React",
"description": "Writing about React composition, architecture, and patterns."
}
]
3 changes: 2 additions & 1 deletion gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ const remarkPlugins = [
}
},
'gatsby-remark-autolink-headers',
'gatsby-remark-numbered-footnotes'
'gatsby-remark-numbered-footnotes',
'gatsby-remark-prismjs'
];

module.exports = {
Expand Down
41 changes: 41 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"babel-plugin-prismjs": "^2.1.0",
"bootstrap": "^5.3.2",
"bootswatch": "^5.3.2",
"d3-color": "^3.1.0",
Expand All @@ -46,11 +47,13 @@
"gatsby-remark-external-links": "0.0.4",
"gatsby-remark-images": "^7.12.3",
"gatsby-remark-numbered-footnotes": "^1.0.1",
"gatsby-remark-prismjs": "^7.13.0",
"gatsby-source-filesystem": "^5.12.1",
"gatsby-transformer-json": "^5.12.0",
"gatsby-transformer-remark": "^6.12.3",
"gatsby-transformer-sharp": "^5.12.3",
"graphql": "^16.8.1",
"prismjs": "^1.29.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
Expand Down
15 changes: 14 additions & 1 deletion src/components/article.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Prism from 'prismjs';
import { graphql } from 'gatsby';
import PropTypes from 'prop-types';
import { useEffect } from 'react';
import { Container, Row, Col } from 'react-bootstrap';

import SEO from 'components/seo';
Expand All @@ -18,6 +20,11 @@ export default function Article({ data }) {
}
} = data;

useEffect(() => {
Prism.manual = true;
Prism.highlightAll();
}, []);

return (
<Layout>
<SEO title={title} description={description} />
Expand All @@ -37,7 +44,13 @@ export default function Article({ data }) {

Article.propTypes = {
data: PropTypes.shape({
markdownRemark: PropTypes.arrayOf(PropTypes.object)
markdownRemark: PropTypes.shape({
html: PropTypes.string.isRequired,
frontmatter: PropTypes.shape({
title: PropTypes.string.isRequired,
description: PropTypes.string
})
})
})
};

Expand Down
4 changes: 3 additions & 1 deletion src/components/articleCategory.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ export default function ArticleCategory({ data }) {
ArticleCategory.propTypes = {
data: PropTypes.shape({
articleCategoriesJson: PropTypes.object,
allMarkdownRemark: PropTypes.arrayOf(PropTypes.object)
allMarkdownRemark: PropTypes.shape({
nodes: PropTypes.arrayOf(PropTypes.object)
})
})
};

Expand Down
8 changes: 4 additions & 4 deletions src/components/featuredPost.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export default function FeaturedPost({ image, date, path, title, excerpt }) {
<Link to={path}>
<h5>{title}</h5>
</Link>
<p>
Published on {Boolean(date) && format(parseISO(date), 'yyyy-MM-dd')}
</p>
<p>{excerpt}</p>
{Boolean(date) && (
<p>Published on {format(parseISO(date), 'yyyy-MM-dd')}</p>
)}
{Boolean(excerpt) && <p>{excerpt}</p>}
</div>
</Col>
<Col md={1}></Col>
Expand Down
15 changes: 6 additions & 9 deletions src/components/seo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { useStaticQuery, graphql } from 'gatsby';

function SEO({ description, lang, meta, title }) {
export default function SEO({
title,
description = '',
lang = 'en',
meta = []
}) {
const { site } = useStaticQuery(graphql`
query {
site {
Expand Down Expand Up @@ -62,17 +67,9 @@ function SEO({ description, lang, meta, title }) {
);
}

SEO.defaultProps = {
lang: `en`,
meta: [],
description: ``
};

SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired
};

export default SEO;
14 changes: 10 additions & 4 deletions src/hooks/useRainbow.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { interpolateHslLong } from 'd3-interpolate';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';

const interpolator = interpolateHslLong('#ff0000', '#0000ff');

export default function useRainbow(timeDilation = 2000) {
export default function useRainbow(
startColor = '#ff0000',
endColor = '#0000ff',
timeDilation = 2000
) {
const interpolator = useMemo(
() => interpolateHslLong(startColor, endColor),
[startColor, endColor]
);
const requestId = useRef(null);
const [color, setColor] = useState(null);

Expand Down

0 comments on commit 92c0d33

Please sign in to comment.