I see opportunities to minimize network bytes transfer of my website, but can't come up with a proper solution.
In Gatsby, I make use of .mdx files. In these files I can use React Components, such as:
<Cards id="id_1" />
There are dozens of cards defined in a .json file, which can be used across the website by just calling this component in de mdx file and passing their id.
The Cards component looks like somewhat like this:
import React from 'react'
import Img from 'gatsby-image';
import { StaticQuery, graphql } from 'gatsby';
const Cards = (props) => {
const card_id = props.id ? props.id.slice(0, 2) : [] // grab id
return <StaticQuery
query={graphql`
query Query {
images: allFile(filter: { sourceInstanceName: { eq: "card-images" } }) {
edges {
node {
childImageSharp {
fluid(maxHeight: 256, quality: 100) {
...GatsbyImageSharpFluid_withWebp
...GatsbyImageSharpFluidLimitPresentationSize
}
}
name
}
}
}
allCardsJson {
nodes {
name
id
}
}
}
`}
render={(data) => {
return(
// returns a component by filtering 'data' by 'card_id'
)
}}
/>
}
Everything works fine, but...
When this component is used, the full result of StaticQuery (meaning: all cards since filtering is done inside the return, not inside the staticquery) is send to visitors of the page. This is unnecessary and a waste of network bandwidth, because for example only one card (or a few) is used on the page.
I understand that a StaticQuery is .. static. Thus, I cannot dynamically filter within this query to limit the size of the result.
DynamicQuerys are used when building pages, not inside components.
Is it possible to somehow create components with dynamic content (defined elsewhere), but limited to just the required data? (like by providing an id like I've tried)
I am thinking about creating a seperate file for each Card. Then import the Cards needed into the mdx file and passing it to the component. Thoughts?
There is no documentation about my use case. This makes me wonder if I'm using it as intended.
I solved it by adding the used data to the page context by editing the gatsby-node.js:
Get the mdxAST of the post, filter the components, then filter the ids used.
Add this list of ids to the page context.
Move the query stated in the question above to the post GraphQL query. Use the list of ids provided by page context to filter the data down to only the used ids in this post.
Pass this data as a property to the MDXRenderer.
Pass this data in the .mdx file to the cards component, such as: <Cards data={props.cards_data} />.
Now the compontent received the data without using a StaticQuery.
This works, but it feels kinda weird. There must be a better, more clean, solution in my opinion.
GraphQL queries are run ONCE when you run gatsby develop or gatsby build. This is a counterintuitive way how Gatsby works but read it yourself:
Gatsby uses GraphQL at build-time and not for live sites.
More information about the gatsby build process and how GraphQL factors into it.
This means you already built your component the way you are supposed to. During build all your cards are queried and kept in memory. When creating the HTML for your pages with your cards, only the cards with your ID are used to build your page. Thus the user only sees gets transmitted the pages with filtered IDs.
You can double check if really only the cards with the ID are inside your page:
run gatsby clean: make sure old fragments of your page are removed
run gatsby build: Create your site from scratch
Check your public folder in your project root. This is the classic webpage that gatsby builds. Navigate to your pages with your cards for example /public/blog/example-page-with-card. Take a look inside the HTML of the page: Does it contain all the cards or just the one with the IDs you need?
Related
I'm using SSR in Next.js. I'm trying to generate a unique ID in a component to use in a DOM element's id attribute. This component may be used multiple times on a page so each component instance needs its id to be unique. However, doing something like the following on each render of the component results in a server/client mismatch (component creates new ID on each render, so server and client do not match):
const gradientId = `linear-gradient-${uuid().slice(0, 8)}`
Stuff I've tried:
generating the ID as a default useState value
generating the ID right outside the component (same file, just above component definition)
generating the ID inside a useMemo
All seem to suffer from the client/server mismatch issue. Is there a good way to do this? Is there something stable in the component instance I can base the ID on instead (maybe React generates one I can use?).
I should also note I’m stuck on React 17 so I don’t have access to React 18’s useId, which seems aimed at solving this exact problem!
Any ideas appreciated - thanks!
Well, there may be a better solution out there, but the key appeared to be in abandoning a random ID in favour of a more deterministic ID like an index.
Outside of my component (in the same file, just above the component definition), I can define a function to generate the ID and I can seed it with 0.
let sequentialId = 0
const getSequentialId = function() {
return sequentialId++
}
function MyComponent({ ...otherProps }) {
...etc...
}
then, in my component, I can simply call this instead of the uuid() function I was previously calling:
const gradientId = `linear-gradient-${getSequentialId()}`
This forces the server render to generate 0, 1, 2, etc. and then when the client runs (remember client instances are brand new component instances), it will also start at 0 and sequentially assign IDs in the same order as the server.
I’ve gone one step further and moved this function to a provider where it can be called from any component in case I need this again:
const PageSettingsContext = React.createContext({
_sequentialId: 0,
getSequentialId: function() {
return this._sequentialId++
},
})
function usePageSettings() {
return useContext(PageSettingsContext);
}
function PageSettingsProvider({ ...otherProps }) {
return (
<PageSettingsContext.Provider>
<App />
</PageSettingsContext.Provider>
);
}
export {
PageSettingsProvider,
usePageSettings,
}
This seems to clear up the error and hopefully this holds up over time. Any other ideas or insights welcome but think this works for me.
I'm facing an issue when I try to display images inside my React App component
I have all the images I need, divided by set, in this path src/cards_images/ inside root folder.
In this way I can reach for cards in different sets like:
src/cards_images/set1_MyFirstSetName_MyCard1.jpg
src/cards_images/set2_MySecondSetName_MyCard1234.jpg
I need to create images dynamically, because on this component I pass an array of objects, with the right source path. If I hard-code the same source path, it works, but if I dynamically try to create the path, React says it can't find the module.
I can't put every image inside Public folder because we are talking about several thousands of 200kb images and it won't load. Neither I can import/require every image in bulk, because it will not work aswell (still, how to filter/show them?). It has to be dynamically required and showed. How can I do it? Am I doing anything wrong on this?
import React , {Component}from 'react';
function CardDisplay({ sets }) {
return (
<div id="myDeckCardPickerContainer">
{sets?.map(settino => {
// Gives error "Module not found"
var temp = require(settino.url)
return <div key={settino.key} className='cardImageContainer'><img className='cardImagePreview' src={temp} alt={settino.Name}></img><div><span className='addCardToDeck'>➕</span>{settino.name}<span className='removeCardFromDeck'>➖</span></div></div>
// Gives error "Module not found"
return <div key={settino.key} className='cardImageContainer'><img className='cardImagePreview' src={require(settino.url)} alt={settino.Name}></img><div><span className='addCardToDeck'>➕</span>{settino.name}<span className='removeCardFromDeck'>➖</span></div></div>
// Works fine
return <div key={settino.key} className='cardImageContainer'><img className='cardImagePreview' src={require('../cards_images/set1_MyFirstSetName_MyCard1.jpg')} alt={settino.Name}></img><div><span className='addCardToDeck'>➕</span>{settino.name}<span className='removeCardFromDeck'>➖</span></div></div>
})}
</div>
)}
export default CardDisplay;
I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.
I have a website as a single page that is using Next.js. I have the home page on route / that show a list of products. The code of this page is located in pages/index.js. Each product has an id so I can jump to it using /#product-id.
To make it more url friendly I replicate this behaviour using the product-id as a second param in the route like that: /product-id.
What I do is simply looking on the product-id param using useRouter():
const selectedProductId = useRouter().query['product-id']
And then scroll to the element with this id using js:
document.getElementById(selectedProductId ).scrollIntoView()
So I to change my script name from /pages/index.js to /pages/[product-id].js.
So now the route /1234 work has expected but if I go to / I get error 404.
So has someone an idea how I can match / and /param using one js file?
Optional catch all routes
Catch all routes can be made optional by including the parameter in double brackets ([[...slug]]).
Nextjs has file system based routing, so if you remove /pages/index.js of course you will get an 404 error. Also /pages/index.js and /pages/[product-id].js will render two separate pages.
To answer your question, if it is possible to match two routes like / and /[productId] in one file using nextjs I don't think that is possible however similar results can be achieved by using shallow routing specific to your use case.
So for your use case, I suggest using shallow routing unless you want to render the same component in both pages just to get the product-id or want to make use of hash URLs.
You can make product-id a query string parameter and update it using shallow-routing. Here is an example,
Keep /pages/index.js
import { useRouter } from 'next/router';
const router = useRouter()
// when want to change the productId call
router.push('/?productId=1234', undefined, { shallow: true })
// call the scrollToView inside a useEffect hook
useEffect(() => {
const productId = router.query.productId
// get the element using the productId above then call scrollIntoView()
})
// if using useEffect with the dependency router.query.productId,
// when you change the productId once and scroll up and try to change to the same -
// productId again it will not scroll to view, hence not using the dependency array
// at all
To explain more on what shallow routing does
Shallow routing will allow the change of URL without running the data fetching methods i.e getStaticProps or getServerSideProps again. Which will make the updated query and pathname available without changing the state. Read more about it nextjs docs.
Option 1: Extract the shared code
You could extract a Page component to a separate file and then import it in both /pages/index.js and /pages/[product-id].js, so the code is not duplicated.
Option 2: Use experimental rewrites feature
Assuming you have /pages/[product-id].js you can show this page when a user requests /.
You would need to add next.config.js.
module.exports = {
experimental: {
async rewrites() {
return [
{ source: "/", destination: "/[product-id]" },
];
}
}
}
So, when a user requests / they would see the content of /[product-id], just with the empty product id.
Note, that at the moment rewrite doesn't support auto-rendered dynamic pages, so you have to disable auto-rendering for the dynamic page.
You can do that by adding getServerSideProps to /pages/[product-id].js.
export async function getServerSideProps() {
return {
props: {},
}
}
Imagine you made a web framework that helps you quickly make blogs for clients. For the sake of this post, its the same blog template everytime, what changes is the content. You're React app is a simple structure of the following [where the Content state is just changing each time]
<App>
<Navigation/>
<Content/>
</App>
What makes the framework is you have XML files which contain the HTML. Each XML file represents one blog post. The app pulls all the HTML from the XML files, and puts it into the state of the App in a "blog posts" array. Depending on the state of the app, a specific entry in the array will be displayed in Content...
Content's state has a field called "html" which is what holds the HTML to be injected in string form. [you have to use dangerouslySetInnerHTML]
This concept works fine, and I have a version of it now. However, imagine you have a React components that you want to add to each blog post. Say you want to add the component into a specific blog post in a specific section. You want to add props to it and such. Now this goes out the window with dangerouslySetInnerHTML
This is where I am stuck trying to find the best direction to go. The only thought I have now is the following:
Since you would now be writing JSX in the XML, just make each blog post its own component. You would have ...etc and then if this.state.currentPost === 1 then display BlogPost1 and likewise. Yet you would have to have a huge block of if-statements depending on how many blogposts you have, and its not ideal to have to add everytime you have a new blogpost
When I read the title of your question I got curious and found this library to parse XML into React components: xml-to-react. But that's not what you are asking for.
As you want to use components in the middle of you string of HTML, I'll suggest: react-remarkable. This component compiles its children (a string with markdown/html/react) into react nodes.
Example from its docs:
var React = require('react');
var Markdown = require('react-remarkable');
var MyComponent = React.createClass({
render() {
return (
<div>
{/* Pass Markdown source to the `source` prop */}
<Markdown source="**Markdown is awesome!**" />
{/* Or pass it as children */}
{/* You can nest React components, too */}
<Markdown>{`
## Reasons React is great
1. Server-side rendering
2. This totally works:
<SomeOtherAmazingComponent />
Pretty neat!
`}</Markdown>
</div>
);
}
});