Skip to content

Instantly share code, notes, and snippets.

@redbar0n
Last active January 2, 2021 03:04
Show Gist options
  • Save redbar0n/ba073c6ca61e6d3c4178d3a6dd756194 to your computer and use it in GitHub Desktop.
Save redbar0n/ba073c6ca61e6d3c4178d3a6dd756194 to your computer and use it in GitHub Desktop.
Alternative SolidJS syntax suggestion
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