Last active
January 2, 2021 03:04
-
-
Save redbar0n/ba073c6ca61e6d3c4178d3a6dd756194 to your computer and use it in GitHub Desktop.
Alternative SolidJS syntax suggestion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useStore } from "../store"; | |
import ArticlePreview from "./ArticlePreview"; | |
export default props => { | |
const [{ token }, { unmakeFavorite, makeFavorite }] = useStore(), | |
handleClickFavorite = (article, e) => { | |
e.preventDefault(); | |
article.favorited ? unmakeFavorite(slug) : makeFavorite(slug); | |
}, | |
handlePage = (v, e) => { | |
e.preventDefault(); | |
props.onSetPage(v); | |
setTimeout(() => window.scrollTo(0, 0), 200); | |
}; | |
// Current implementation in SolidJS: | |
// From: https://github.com/ryansolid/solid-realworld/blob/master/src/components/ArticleList.js | |
// | |
// Pro: Powerful directives. | |
// Con: Idiosyncratic, unfamiliar to newcomers. | |
// Con: Complex, since all code is in one render function. No clear separation, hard to see what's what (for instance: where a page begins and ends). | |
// Con: Imperative code posing as declarative. | |
// Con: Loses familiarity with React JSX. | |
// Con: Loses familiarity with HTML. | |
// Con: Fallback in first line doesn't coincide with when you think about it, since you typically think of the happy path first, and then fallbacks. | |
// Con: Components not corresponding to what is rendered: Loses correlation with what's seen in the browser → Less intuitive, and less fast to visually "scan". | |
return ( | |
<Suspense fallback={<div class="article-preview">Loading articles...</div>}> | |
<For | |
each={props.articles} | |
fallback={<div class="article-preview">No articles are here... yet.</div>} | |
> | |
{article => ( | |
<ArticlePreview article={article} token={token} onClickFavorite={handleClickFavorite} /> | |
)} | |
</For> | |
<Show when={props.totalPagesCount > 1}> | |
<nav> | |
<ul class="pagination"> | |
<For each={[...Array(props.totalPagesCount).keys()]}> | |
{v => ( | |
<li | |
class="page-item" | |
classList={{ active: props.currentPage === v }} | |
onClick={[handlePage, v]} | |
> | |
<a class="page-link" href="" textContent={v + 1} /> | |
</li> | |
)} | |
</For> | |
</ul> | |
</nav> | |
</Show> | |
</Suspense> | |
); | |
// Alternative 1: | |
// Control flow as regular JS, instead of imperative-posing-as-declarative code. | |
// Presumes Solid.forEach, Solid.showWhen would exist. | |
// | |
// Pro: Clearer what is SolidJS and what is "normal JSX". | |
// Pro: Fallback comes (optionally) at last, which coincides with when you think about it. | |
// Con: Some escaping with {} needed. | |
return ( | |
<Suspense fallback={<div class="article-preview">Loading articles...</div>}> | |
{ Solid.forEach( props.articles, | |
article => ( | |
<ArticlePreview article={article} token={token} onClickFavorite={handleClickFavorite} /> | |
), | |
<div class="article-preview">No articles are here... yet.</div> | |
)} | |
{ Solid.showWhen( props.totalPagesCount > 1, | |
<nav> | |
<ul class="pagination"> | |
{ Solid.forEach( [...Array(props.totalPagesCount).keys()], | |
v => ( | |
<li | |
class="page-item" | |
classList={{ active: props.currentPage === v }} | |
onClick={[handlePage, v]} | |
> | |
<a class="page-link" href="" textContent={v + 1} /> | |
</li> | |
) | |
)} | |
</ul> | |
</nav> | |
)} | |
</Suspense> | |
); | |
// Alternative 2: | |
// Taking it one step further, to make the Suspense also a non-JSX node. | |
// | |
// Pro: More familiarity to HTML, since we distinguish clearer between normal JS control-flow and only use <> tags for whatever is actually rendered/displayed. | |
// Pro: Less {} escaping needed. | |
// Con: Slightly dissimilar to React since <Suspense> was converted. But perhaps better? Since <Suspense> is mostly rendering-logic-posing-as-a-UI-element. | |
// Con: When fallback is the last parameter to Suspense, it would typically be pushed far down in the code, making it a bit harder to associate with the correct function it is passed into (see: "Loading articles..."). Could be alleviated with a named parameter, to be clearer? Like: `fallback: <div class="article-preview">Loading articles...</div>` | |
return ( | |
Solid.Suspense( | |
Solid.forEach( props.articles, | |
article => ( | |
<ArticlePreview article={article} token={token} onClickFavorite={handleClickFavorite} /> | |
), | |
<div class="article-preview">No articles are here... yet.</div> | |
), | |
Solid.showWhen( props.totalPagesCount > 1, | |
<nav> | |
<ul class="pagination"> | |
{ Solid.forEach( [...Array(props.totalPagesCount).keys()], | |
v => ( | |
<li | |
class="page-item" | |
classList={{ active: props.currentPage === v }} | |
onClick={[handlePage, v]} | |
> | |
<a class="page-link" href="" textContent={v + 1} /> | |
</li> | |
) | |
)} | |
</ul> | |
</nav> | |
), | |
<div class="article-preview">Loading articles...</div> | |
) | |
); | |
// Alternative 3: | |
// Make fallbacks default, and only an optional parameter if a special case where something like <div class="article-preview">Loading....</div> would not suffice. | |
// How often do you need to specify a special loader apart from a default spinner or "Loading..."? Just see the examples here (For, Show, Suspense, SuspenseList, Index): https://github.com/ryansolid/solid/blob/master/documentation/rendering.md | |
// | |
// Pro: Remove boilerplate. Convention over configuration. → Terser code. Focus on happy path. | |
return ( | |
Solid.Suspense( | |
Solid.forEach( props.articles, article => <ArticlePreview article={article} token={token} onClickFavorite={handleClickFavorite} />), | |
Solid.showWhen( props.totalPagesCount > 1, | |
<nav> | |
<ul class="pagination"> | |
{ Solid.forEach( [...Array(props.totalPagesCount).keys()], | |
v => ( | |
<li | |
class="page-item" | |
classList={{ active: props.currentPage === v }} | |
onClick={[handlePage, v]} | |
> | |
<a class="page-link" href="" textContent={v + 1} /> | |
</li> | |
) | |
)} | |
</ul> | |
</nav> | |
) | |
) | |
); | |
}; | |
// Alternative 4: | |
// | |
// Pro: You could even get rid of the Suspense from the rendering, by wrapping the whole component in `export default Solid.Suspense(ArticleList);`. | |
// (Suspense would have to take in props and pass them along.) | |
const ArticleList = props => { | |
const [{ token }, { unmakeFavorite, makeFavorite }] = useStore(), | |
handleClickFavorite = (article, e) => { | |
e.preventDefault(); | |
article.favorited ? unmakeFavorite(slug) : makeFavorite(slug); | |
}, | |
handlePage = (v, e) => { | |
e.preventDefault(); | |
props.onSetPage(v); | |
setTimeout(() => window.scrollTo(0, 0), 200); | |
}; | |
return ( | |
Solid.forEach( props.articles, article => <ArticlePreview article={article} token={token} onClickFavorite={handleClickFavorite} />), | |
Solid.showWhen( props.totalPagesCount > 1, | |
<nav> | |
<ul class="pagination"> | |
{ Solid.forEach( [...Array(props.totalPagesCount).keys()], | |
v => ( | |
<li | |
class="page-item" | |
classList={{ active: props.currentPage === v }} | |
onClick={[handlePage, v]} | |
> | |
<a class="page-link" href="" textContent={v + 1} /> | |
</li> | |
) | |
)} | |
</ul> | |
</nav> | |
) | |
); | |
}; | |
export default Solid.Suspense(ArticleList); | |
// Alternative 5: | |
// Encapsulating the logic into their respective components. | |
// The Solid functions could perhaps even be extracted outside of the render function, so they are merely normal JS? | |
// Or extracted out to merely encapsulate the component itself, like in alternative 4? | |
// | |
// Pro: Encapsulation. Good practise. | |
// Pro: Clearer logical structure. | |
// Pro: Easier to debug. | |
// Pro: Individual extensibility. | |
// Pro: Reusability. | |
// Pro: More similarity to React. | |
// Con: Slightly more code? | |
// Con: Potential indirection, if prematurely placed in separate files. | |
// (Suspense would have to take in props and pass them along.) | |
export default ArticleList = Solid.Suspense(props => { | |
return ( | |
<> | |
<ArticlePreviews articles={props.articles} />, | |
<Pages {...props} /> | |
</> | |
); | |
}); | |
const ArticlePreviews = props => { | |
const [{ token }, { unmakeFavorite, makeFavorite }] = useStore(); | |
const handleClickFavorite = (article, e) => { | |
e.preventDefault(); | |
article.favorited ? unmakeFavorite(slug) : makeFavorite(slug); | |
}; | |
return ( | |
Solid.forEach(props.articles, | |
article => <ArticlePreview article={article} token={token} onClickFavorite={handleClickFavorite} /> | |
) | |
); | |
};¨ | |
const Pages = props => { | |
return ( | |
Solid.showWhen( props.totalPagesCount > 1, | |
<nav> | |
<ul class="pagination"> | |
<Page {...props} /> | |
</ul> | |
</nav> | |
) | |
); | |
); | |
const Page = props => { | |
const handlePage = (v, e) => { | |
e.preventDefault(); | |
props.onSetPage(v); | |
setTimeout(() => window.scrollTo(0, 0), 200); | |
}; | |
return( | |
Solid.forEach( [...Array(props.totalPagesCount).keys()], | |
v => ( | |
<li | |
class="page-item" | |
classList={{ active: props.currentPage === v }} | |
onClick={[handlePage, v]} | |
> | |
<a class="page-link" href="" textContent={v + 1} /> | |
</li> | |
) | |
) | |
) | |
} | |
// Alternative 6: | |
// Alternative 1 again, but with Suspense extracted outside of the render function, and fallbacks removed in favour of implicit conventional defaults. | |
// Pro: But here, all the `Solid.` calls in the render function clearly indicate component boundaries, so it would guide the developer in later extracting components (like in alternative 5) if wanted. | |
// (Suspense would have to take in props and pass them along.) | |
export default ArticleList = Solid.Suspense(props => { | |
const [{ token }, { unmakeFavorite, makeFavorite }] = useStore(), | |
handleClickFavorite = (article, e) => { | |
e.preventDefault(); | |
article.favorited ? unmakeFavorite(slug) : makeFavorite(slug); | |
}, | |
handlePage = (v, e) => { | |
e.preventDefault(); | |
props.onSetPage(v); | |
setTimeout(() => window.scrollTo(0, 0), 200); | |
}; | |
return ( | |
<> | |
{ Solid.forEach( props.articles, article => <ArticlePreview article={article} token={token} onClickFavorite={handleClickFavorite} />) } | |
{ Solid.showWhen( props.totalPagesCount > 1, | |
<nav> | |
<ul class="pagination"> | |
{ Solid.forEach( [...Array(props.totalPagesCount).keys()], | |
v => ( | |
<li | |
class="page-item" | |
classList={{ active: props.currentPage === v }} | |
onClick={[handlePage, v]} | |
> | |
<a class="page-link" href="" textContent={v + 1} /> | |
</li> | |
) | |
)} | |
</ul> | |
</nav> | |
)} | |
</> | |
); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment