How to render components dynamically in Gatsby.js and use code-splitting? - reactjs

I am building websites with Gatsby and Contentful and it has been great so far. My problem is, that I don't know how to dynamically render components based on the data in Contentful.
Let's say there is a page content type which as a title, url and field for components. Those components could be a YouTube player, or markdown text or photos. Currently I'm using a custom component that imports all available components and then renders the components of the page using switch.
switch (contentType) {
case 'billboard':
return <Billboard key={key} id={key} {...section.fields}/>;
case 'photos':
return <Photos key={key} id={key} {...section.fields}/>;
case 'postGrid':
return <PostGrid key={key} id={key} {...section.fields}/>;
case 'splitView':
return <SplitView key={key} id={key} {...section.fields}/>;
case 'text':
return <Text key={key} id={key} {...section.fields}/>;
case 'tile':
return <Tile key={key} id={key} {...section.fields}/>;
default:
return null;
}
The problem with this is that Gatsby will include all available components in the webpack chunk which leads to a blown up site if there are many of them. Let's say there is a page with text only (e.g. imprint) the YouTube player and photos component would be loaded too - just not used.
So... is there a way to render components based on data which then results in proper code-splitting?
Thank you so much!

I'm thinking of another approach; in a mapping component that renders your type of component based on contentType, much more cleaner and especially, a huge performance improvement (no need to force the code to check for the switch statement each time).
Without seeing the rest of the code it's difficult to guess how you are printing that switch. However, let's say you have all your data inside data object, then:
import SwitchComponents from '../../SwitchComponents/SwitchComponents'
// other code
return data.map(item => {
const Switcher = SwitchComponents[item.contentType];
return (
<Switcher
key={item.key} // or something unique
{...section.fields}
/>
);
})
This component should have a similar structure, such as:
import Billboard from './Billboard/Billboard';
import Photos from './Photos/Photos';
import PostGrid from './PostGrid/PostGrid';
import SplitView from './SplitView /SplitView';
import Text from './Text/Text';
import Tile from './Tile/Tile';
const SwitchComponents= {
billboard : Billboard,
photos : Photos,
postGrid : PostGrid,
splitView : SplitView,
text : Text,
tile : Tile
};
export default SwitchComponents
Basically you are telling with SwitchComponents[item.contentType] the position of SwitchComponents that should take, since it's mapped as a component (imported in SwitchComponents) and rendered as <Switcher/> will get a component and will do the trick.
I would be glad to upload the question if it breaks but I hope you get my workaround.
Let me know how it works!

React Loadable has worked brilliantly for me. This is one of those cases when adding a library makes the bundle smaller.
You might also want to defer async loading to when the user has scrolled to the particular element, which this library doesn't do by itself. You can easily mix the original with the InteractionObserver version - react-loadable-visibility. However, make sure you have a good way of handling CLS (Cumulative Layout Shift) or Lighthouse will complain that your website elements are frustrating your users.

Related

Algolia: Export React Instant Search results

Is there any way to export Algolia's React Instant Search results to a CSV? I've tried using the react-csv package, but it doesn't work with Algolia's Hit Component. The package requires data as props, but the data is constantly changing since it's React Instant Search.
What I mean by constantly changing is that on page load, you're given the entire index of records found, then you can narrow down the results with the search bar or other filtering components.
I've gone down the Google rabbit hole looking for information about exporting Algolia's search results as a CSV, but I haven't found anything regarding React Instant Search—unless I completely missed it.
Has anyone tried this before? If so, could you point me in the right direction regarding documentation or examples?
Not sure if this solves your problem but one possible way is to use the StateResults widget. The StateResults widget provides a way to access the searchState and the searchResults of InstantSearch.
Here I will create a custom StateResults component in the form of a download button and then connect it using the connectStateResults connector.
I have attached a demo below as well.
For simplicity I didn't format the data to be fed into the CSV builder.
// 1. Create a React component
const StateResults = () => {
// return the DOM output
};
// 2. Connect the component using the connector
const CustomStateResults = connectStateResults(StateResults);
// 3. Use your connected widget
<CustomStateResults />
In your case something like
const StateResults = ({ searchResults }) => {
const hits = searchResults?.hits;
return (
<div>
<button>{hits && <CSVLink data={hits}>Download CSV</CSVLink>}</button>
</div>
);
};
const DownloadButton = connectStateResults(StateResults);
//in your JSX then <DownloadButton />

Display a loading text/spinner until a component is fully loaded(text,images...etc) in React

I searched for a solution like this but I just couldn't find it properly so I am asking this question if anyone can help.
This is a Sandbox that I made and the idea is something like this: I want to show a loading text until my component(let's say a whole website: including images, text.. etc..) is fully loaded, tried to make this with useEffect hook but the useEffect only shows the loading text until the component is mounted, and the hook does that, but there are things like images inside this component that aren't fully loaded yet, so this does not work the way I want. I would appreciate it if anyone knows a way to make this work, there is a lot of information on the web for making something like this with a setTimeout but I think this is a bit of a tricky/fake way to do it because the loading spinner should show depending on the speed of your ISP right? so for users with a better internet connection the loading text/spinner time will be shorter and for others, it will be longer.
Link to Sandbox
you can use the following function on your component :
document.onreadystatechange = function () {
if (document.readyState === 'complete') {
console.log("do something");
}
}
or controll all document's loading period using
switch (document.readyState) {
case "loading":
// The document is still loading.
break;
case "interactive":
// The document has finished loading. We can now access the DOM elements.
// But sub-resources such as scripts, images, stylesheets and frames are still loading.
console.log('do something')
break;
case "complete":
// The page is fully loaded.
console.log("do something");
break;
}
you can also use this function if you need it :
window.addEventListener("load", event => {
var image = document.querySelector('img');
var isLoaded = image.complete && image.naturalHeight !== 0;
console.log(isLoaded);
});
You could have a 'loading' state that is true at first and then change it's value with the onload function.
You could use it like this:
<div onload={setLoading(false)}>
<img src="..." />
<img src="..." />
<img src="..." />
</div>
Onload executes when all of it's children content has finished loading. You could read more about this at https://www.w3schools.com/tags/ev_onload.asp

React Context always returns EMPTY

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

How use N views in sample app in react native

I have some doubt in React Native. I read this answer (How to do a multi-page app in react-native?) but I stayed with a little bit doubt:
I use Navigator to many views in React Native App, but how I do to N componentes? For example, if I have 5 different views I have use the before code five times... and n times?
to ellude more on my comment.
instead of this
if (routeId === 'SplashPage') {
return (
<SplashPage
navigator={navigator}/>
);
}
if (routeId === 'LoginPage') {
return (
<LoginPage
navigator={navigator}/>
);
}
just have a hashtable that you use to get the component dynamically.
const Component = VIEW_COMPONENTS[routeid];
so your code would look something like this
const VIEW_COMPONENTS = {
'SplashPage': SplashPage,
'LoginPage': LoginPage
};
renderScene = ( route, navigator ) => {
const ViewComponent = VIEW_COMPONENTS[route.id];
return <ViewComponent navigator={navigator}/>
}
any additional screen would be a single line entry to your lookup table. I have a native app with 40 screens and its very easy to manage like this
to add to this. you can abstract more out of this too. make every view not care about where it is used or what its next view is. Instead make all of that a part of your lookup object. specify a next route that every view can show. you can pass any additional information all of which is configurable and your screens can be reused on multiple flows!

React how to pass data to parent navigation

I have a navigation toolbar, with H1 in it.
I also various sub content for each page, as child component.
How can I pass the title information, from the child page component, to the parent navigation?
I've tried to use Context, but it only propagate from Parent to Childs.
Her is a simplified exemple:
const App = React.createClass({
render() {
return (
<div>
<toolbar>
<h1>{myTitleAccordingToPage}</h1>
</toolbar>
<main>
{this.props.children}
</main>
</div>
)
}
})
const A = React.createClass({
render() {
return (
<div>
Content for A page
</div>
)
}
})
const B = React.createClass({
render() {
return (
<div>
Content for B page
</div>
)
}
})
Extract state for your navigation either into a store, or somewhere else, and propagate that state to your application via props.
var page = {
title: 'foo'
otherData: ''
}
ReactDOM.render( <App page={ page }, element )
Now your top-level component knows about the state and can filter accordingly down to its children, for example, you might change your App render function to pass different bits of the application state down to different children i.e.
(I'm passing props explicitly here to show what is going on, and using a pure function to render, use a class and render method if that is more comfortable for you, the advantage of only ever passing props is that it is easier to make your functions pure, which has many advantages)
const App = props => {
<div>
<Toolbar title={ props.page.title } />
<Main otherData={ props.page.otherData />
</div>
}
Now you have created top-down data flow, making your application more predictable and possibly paving the way to use the pure render function to maybe give you performance boosts for free. The key with top-down data-flow is really making your application more predictable, which should translate into testability, and thus reliability, and makes hacking on your application easier.
The react-router module can help with creating structures like this. There is a small learning phase when adopting the module but the benefits for an app like the one you are creating should outweigh this and the speed of development once learnt may very well outweigh any learning outlay you invest into it.
Possible it's time to use Flex https://facebook.github.io/flux/docs/dispatcher.html#content
Usable approuch to two way data binding http://voidcanvas.com/react-tutorial-two-way-data-binding/
Original documentation to two way data binding, but mixin now is now supported https://facebook.github.io/react/docs/two-way-binding-helpers.html
( For mixin possible to use react-mixin npm package )
EDIT:
There is an opinion at comments that: "two-way data-binding is often a bad idea, and if your application makes more sense with two-way data binding then React should probably not be the framework to employ". So you need to rearrange your application architecture to avoid using two-way data binding if possible. Use flux instead.

Resources