Programatically creating pages in Gatsby using Json - javascript

I am using gatsby to programatically create pages but I want to do so at 2 different paths covers/{json.name}.js and styles/{json.name}.js.
I have it set up currently working using the gatsby-node.js file but would prefer to have them implemented using the {} convention. Currently getting all my nodes at both paths when trying the {}.js method I am under the impression filtering is impossible using this method but is there currently a work around to get it working with the above method.

Exactly, you hit the nail. Actually, using filters/custom variables is not possible using File System Route API. As you can see in the article feature announcement:
Can I use custom variables for the file name?
Defining custom variables/fields inside the file isn’t possible at the
moment, e.g. if you want to use {something}.js for your file name
and then define something as MarkdownRemark.fields__slug. Therefore
you’ll have to follow the syntax of {Model.field__nestedField}.
There's a lack of specifications in your question so I don't know if the aliasing method will fit you (I guess it doesn't). Aliasing allows you to use multiple gatsbyPath like:
import React from "react"
import { Link, graphql } from "gatsby"
export default function HomePage(props) {
return (
<ul>
{props.data.allProduct.map(product => (
<li key={product.name}>
<Link to={product.productPath}>{product.name}</Link> (
<Link to={product.discountPath}>Discount</Link>)
</li>
))}
</ul>
)
}
export const query = graphql`
query {
allProduct {
name
productPath: gatsbyPath(filePath: "/products/{Product.name}")
discountPath: gatsbyPath(filePath: "/discounts/{Product.name}")
}
}
`
If it doesn't fit you, you won't be able to get rid of the gatsby-node.js so the filesystem API won't be a valid option.

Related

Is it a good idea to use React.Context to inject UI-Components?

I plan to build a react component library. The react components are UI-Components but should only implement a specific logic. I want the user to be able to define a set of atoms (basic react components) that are used to compose the actual components. My main goal is to make the library independent of a specific UI-Component-Library like MaterialUI, ChakraUI, etc.
My idea was to use a React.Context to inject the components like this:
// Button Atom
const Button: FC = ({ children }) => (<button>{children}</button>)
const atoms = { button: Button }
const AtomContext = createContext(atoms);
// "higher" component
const HigherComponent: FC = () => {
const atoms = useContext(AtomContext)
// Logic ...
return (
<atoms.button>click me</atoms.button>
)
}
export default function App() {
return (
<AtomContext.Provider value={atoms}>
<HigherComponent />
</AtomContext.Provider>
);
}
This solves my problem. But I'm not sure if it is a good idea. Are there better ways to inject UI-dependencies? What may be problems with my approach?
This is a question that can result in multiple answers based on personal experience.
But overall the idea to pass component trough context is a bit of misuse of that concept.
Also it does not bring much benefit from making a standalone library that can be imported via package.json or making a folder with components and importing them.
If you need to pass some specific things to your components you can have a custom provider as you did there, but as far as the component themselves, there is no common sense to use context.
If you end goal is to shorten the list of imports of component with custom context hook and just getting them like that, I think that is a bad tradeoff overall.
I just read this article about using react context for dependency injection. The authors opinion is, that react context for injecting non-react dependencies into components is good practice. However, I'm not sure if that applies to injecting a specific set of react components. Since, react components are nothing more than functions I think it should be alright to use reacts context for that.

Gatsby: Why Do I (Or Do I Even?) Need to Use exports.onCreateNode to Create Pages?

All of the examples in the Gatsby documentation seem to assume you want to define an exports.onCreateNode first to parse your data, and then define a separate exports.createPages to do your routing.
However, that seems needlessly complex. A much simpler option would seem to be to just use the graphql option provided to createPages:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const { data } = await graphql(query);
// use data to build page
data.someArray.forEach(datum =>
createPage({ path: `/some/path/${datum.foo}`, component: SomeComponent }));
However, when I do that, I get an error:
TypeError: filepath.includes is not a function
I assume this is because my path prop for createPage is a string and it should be "slug". However, all the approaches for generating slugs seem to involve doing that whole exports.onCreateNode thing.
Am I missing a simple solution for generating valid slugs from a path string? Or am I misunderstanding Gatsby, and for some reason I need to use onCreateNode every time I use createPage?
It turns out the error I mentioned:
TypeError: filepath.includes is not a function
Wasn't coming from the path prop at all: it was coming from the (terribly named) component prop ... which does not take a component function/class! Instead it takes a path to a component (why they don't call the prop componentPath is just beyond me!)
But all that aside, once I fixed "component" to (sigh) no longer be a component, I was able to get past that error and create pages ... and it turns out the whole onCreateNode thing is unnecessary.
Why Do I Need to Use exports.onCreateNode to Create Pages?
You do not.
Gatsby heavily uses GraphQL behind the scenes. The Gatsby documentation is about teaching users that many of the features in Gatsby are often only available via GraphQL.
You can create pages without GraphQL as you do in answer with data.someArray.forEach ... but that is not the intended way. By skipping createNodeField you will not be able to query for these fields within your page queries. If you don't need these fields via GraphQL then your solution is perfect.

In React, how can I add a copy-to-clipboard functionality, by writing a simple function without any files imported?

I don't want import any files like react-copy-to-clipboard.
I just want to use a simple JavaScript function, and it should work for strings, values, states, props, etc.
In general, we can do this in multiple ways, by importing files, which I would not recommend, because we import files only for bigger things.
The solution in general is given for a textarea or input type.
Here is my solution which works for props, values, strings, states, or any other data type
clipboardCopy() {
var copyCode = document.createElement('textarea')
copyCode.innerText = this.props.voucher_id //you can use props,states,values,strings. I just used props
document.body.appendChild(copyCode)
copyCode.select()
document.execCommand('copy')
copyCode.remove()
this.toggleRegisterModal()
}
You can use this solution in React, JavaScript, or in any other framework.

Inject per-component style tags dynamically with Rollup and scss

I am building a React component library whose source code takes this general structure:
- src
- common.scss (contains things like re-usable css variables)
- components
- button
- index.js
- button.scss
- dialog
- index.js
- dialog.scss
My components are responsible for importing their own per-component styles (using scss), so for example, button/index.js has this line:
import "./button.scss";
So far, in my application I have been consuming my library directly from source like this:
// app.js
import "mylib/src/common.scss" // load global styles
import Button from 'mylib/src/components/button/index.js'
import Dialog from 'mylib/src/components/dialog/index.js'
// ...application code...
When my application uses webpack, along with style-loader, the per-component css is appended as style tags in head dynamically when the component is first used. This is a nice performance win since the per-component styling doesn't need to be parsed by the browser until it's actually needed.
Now though, I want to distribute my library using Rollup, so application consumers would do something like this:
import { Button, Dialog } from 'mylib'
import "mylib/common.css" // load global styles
// ...application code...
When I use rollup-plugin-scss it just bundles the per-component styles all together, not dynamically adding them as before.
Is there a technique I can incorporate into my Rollup build so that my per-component styles are dynamically added as style tags in the head tag as they are used?
One approach would be to load your SCSS as a CSS stylesheet string the output:false option in the plugin (see the Options section of the docs), then in your component use react-helmet to inject the stylesheet at runtime:
import componentCss from './myComponent.scss'; // plain CSS from rollup plugin
import Helmet from 'react-helmet';
function MyComponent(props) {
return (
<>
<ActualComponentStuff {...props} />
<Helmet>
<style>{ componentCss }</style>
</Helmet>
</>
);
}
This basic idea should work, but I wouldn't use this implementation for 2 reasons:
Rendering two instances of MyComponent will cause the stylesheet to be injected twice, causing lots of unnecessary DOM injection
It's a lot of boilerplate to wrap around every component (even if we factor out our Helmet instance into a nice wrapper)
Therefore you're better off using a custom hook, and passing in a uniqueId that allows your hook to de-duplicate stylesheets. Something like this:
// -------------- myComponent.js -------------------
import componentCss from "./myComponent.scss"; // plain CSS from rollup plugin
import useCss from "./useCss";
function MyComponent(props) {
useCss(componentCss, "my-component");
return (
<ActualComponentStuff {...props} />
);
}
// ------------------ useCss.js ------------------
import { useEffect } from "react";
const cssInstances = {};
function addCssToDocument(css) {
const cssElement = document.createElement("style");
cssElement.setAttribute("type", "text/css");
//normally this would be dangerous, but it's OK for
// a style element because there's no execution!
cssElement.innerHTML = css;
document.head.appendChild(cssElement);
return cssElement;
}
function registerInstance(uniqueId, instanceSymbol, css) {
if (cssInstances[uniqueId]) {
cssInstances[uniqueId].symbols.push(instanceSymbol);
} else {
const cssElement = addCssToDocument(css);
cssInstances[uniqueId] = {
symbols: [instanceSymbol],
cssElement
};
}
}
function deregisterInstance(uniqueId, instanceSymbol) {
const instances = cssInstances[uniqueId];
if (instances) {
//removes this instance by symbol
instances.symbols = instances.symbols.filter(symbol => symbol !== instanceSymbol);
if (instances.symbols.length === 0) {
document.head.removeChild(instances.cssElement);
instances.cssElement = undefined;
}
} else {
console.error(`useCss() failure - tried to deregister and instance of ${uniqueId} but none existed!`);
}
}
export default function useCss(css, uniqueId) {
return useEffect(() => {
// each instance of our component gets a unique symbol
// to track its creation and removal
const instanceSymbol = Symbol();
registerInstance(uniqueId, instanceSymbol, css);
return () => deregisterInstance(uniqueId, instanceSymbol);
}, [css, uniqueId]);
}
This should work much better - the hook will use effectively a app-wide global to track instances of your component, add the CSS dynamically when it gets first rendered, and remove it when the last component dies. All you need to do is add that single hook as an extra line in each of your components (assuming you're using only function React components - if you're using classes you'll need to wrap them, maybe using a HOC or similar).
It should work fine, but it also has some drawbacks:
We're effectively using global state (cssInstances, which is kind of unavoidable if we're trying to prevent clashes from different parts of the React tree. I was hoping there would be a way to do this by storing state in the DOM itself (this makes sense given that our de-duplication stage is the DOM), but I couldn't find one. Another way would be to use the React Context API instead of a module-level global. This would work fine too and be easier to test; shouldn't be hard to rewrite the hook with useContext() if that's what you want, but then the integrating app would need to set up a Context provider at the root level and that creates more work for integrators, more documentation, etc.
The entire approach of dynamically adding/removing style tags means that stylesheet order is not only non-deterministic (which it already is when doing style loading with bundlers like Rollup), but also can change during runtime, so if you have stylesheets that conflict, the behaviour might change during runtime. Your stylesheets should ideally be too tightly scoped to conflict anyway, but I have seen this go wrong with a Material UI app where multiple instances of MUI were loaded - it's real hard to debug!
The dominant approach at the moment seems to be JSS - using something like nano-renderer to turn JS objects into CSS and then injecting them. There doesn't seem to be anything I can find that does this for textual CSS.
Hope this is a useful answer. I've tested the hook itself and it works fine, but I'm not totally confident with Rollup so I'm relying on the plugin documentation here. Either way, good luck with the project!

dynamic image for autocomplete / components

I am trying to make a crypto react native app, I am trying to have icons to match with an autocomplete. So it would work very similar to the coinmarketcap search, where you can type a coin symbol and you will see the corresponding coin with its icon.
I think there could be a few ways to do this and I think this would be a good place for discussion on what is the best way.
For starters, I created a script that would take all the icons in a directory (SVG or PNG), and create a json that would have the symbol as the value and the key will be a reference to the icon.
With this method there are a few problems I have encountered. The solutions have been covered in this post.
React Native - Dynamic Image Source.
However, it has been hard for me to find a good solution to either
A. Encode all the image in a directory into a base64encoded string and put it into a json
B. Create an array on the React Native end which will have the require('path-to-image')
Relevant code examples are in the post so i dont want to repeat it, but I guess I would just like to know which one is the best practice. I think that doing it as a array of modules would be best. But I am not sure have to dynamically create something like that.
example of the dictionary I created is something like this:
Given a JSON object like this how would you extend it so it would become
const image = {
key1: 'path/to/key/one.png'
key2: 'path/to/key/two.png'
}
To
{
key1: require('path/to/key/one.png'),
key2: require('path/to/key/two.png')
}
you would it to fit into a react native component like so
<Image
source={ (images[symbol])}
/>
You have a couple of options, and I think you nailed the best one in your case (tested in React Native 0.50.3):
import {Image} from 'react-native'
export default (props) => <Image source={icons[props.currency]} />
const icons = {
bitcoin: require('../path/to/bitcoin.png'),
ethereum: require('../path/to/ethereum.png'),
...
}
Lol that's basically exactly what you already wrote in your question. I've also gotten away with storing a bunch of require(...) statements in an array and pulled by index, e.g:
import {Image} from 'react-native'
export default (props) => <Image source={icons[props.index]} />
const icons = [
bitcoin: require('../path/to/bitcoin.png'),
ethereum: require('../path/to/ethereum.png'),
...
]
This approach is really only useful if you don't know the key to identify your target reference by (e.g if you wanted to cycle through a bunch of images randomly). For the described use case I'd go with key lookup.

Categories

Resources