Flight 3
The major release 3.0.0 contains some breaking changes that you need to be aware of when upgrading. Just follow these steps and upgrading should be a breeze! So, it's finally here! The new major version of Flight, which brings a whole new development setup with Razzle, as well as a lot of performance improvements. From now on, we will version the packages together, so it's easier to figure out what the latest version of all packages are when you upgrade.
Upgrading the dependencies
Start off by upgrading your dependencies:
"@jetshop/core": "^3.0.0",
"@jetshop/intl": "^3.0.0",
"@jetshop/react-scripts": "^3.0.0",
"@jetshop/ui": "^3.0.0",
Then run yarn
to install the new versions.
Change server entry point
-
Open
src/server.js
-
Replace
import boot from '@jetshop/core/boot/server';
withimport { createApp } from '@jetshop/core/boot/server';
-
Replace
boot
withcreateApp
and make it a default export.
You should end up with something like this:
import React from 'react';
import { createApp } from '@jetshop/core/boot/server';
import Shop from './components/Shop';
import config from './shop.config';
import Theme from './components/Theme';
export default createApp(
<Theme>
<Shop />
</Theme>,
config
);
Update index.html
We have changed how the order of script tags etc are loaded into the HTML. Make sure that your index.html
looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<slot name="title" />
<slot name="polyfill" />
<slot name="preloadLinks" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<slot name="meta" />
<slot name="style" />
</head>
<body class="no-js">
<script>
document.body.className = document.body.className.replace('no-js', '');
</script>
<div id="root">
<slot name="html" />
</div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<slot name="apolloState" />
<slot name="preloadScripts" />
</body>
</html>
The really important part here is the <slot>
tags. These lets the server side renderer know where to inject different parts of the data, and if they are not placed in the correct place you might get some really weird behaviours.
Nosto and Addwish
Nosto and Addwish have been removed from flight. To figure out how to add them back in please visit the Nosto Docs and the Addwish Docs
gql
utility
The gql
utility has been removed from the @jetshop/core
package. The reason for this is that we don't want to ship an entire GraphQL parser/interpreter with every page load. Instead, put all your queries in .gql
files and import them into your .js
files.
Shortcodes
Shortcodes must also be updated to the latest version 2.0.5
. They way shortcodes are imported has also changed. Please change your imports to the following:
import { Shortcodes } from '@jetshop/flight-shortcodes';
Be cautious about warnings
A lot of things have changed under the hood. We try to give helpful warnings and errors, so make sure that you try out the shop thoroughly, read the warnings and fix them to have a smooth transition.
Common Issues when upgrading
TypeError: Cannot read property 'theme' of null
This is usually caused by forgetting to wrap your theme around the Shop in the client.js
and server.js
file. Please have them look like so:
import Theme from './components/Theme';
boot(
<Theme>
<Shop />
</Theme>,
config
);
Error: You should not use Route or withRouter() outside a Router
This is caused by having a conflicting version of react-router
in your package.json
. Please remove any local copies of react-router
as we import it in @jetshop/core
Let us know if you have any problems!
We have tried making this upgrade as smooth as possible, but there might be things we have missed. Let us know if you have any problems upgrading and we'll try to help you!
Upgrading to Flight 3.3.0
In Flight 3.3.0, the markup used to render pagination was changed for improved SEO. Instead of rendering buttons for the next and previous actions, anchors are now rendered instead. This may mean you need to update your CSS.
The disabled next/prev button will have a data-disabled="true"
attribute, which you can target in your css, for example:
.pagination [data-disabled='true'] {
cursor: not-allowed;
opacity: 0.3;
}
There is one small breaking change when upgrading to flight 3.3.0. react-apollo-hooks
has been replaced with @apollo/react-hooks
. If you are using the former in your shop simply replacing the import should fix all instances of the useQuery
mutation. For uses of the useMutation
hook, simply wrap your mutate function in brackets like so:
const [mutate] = useMutation(mutation);
Additionally the data object coming back from apollo can sometimes be null
. Please add the following null check to CategoryPage.js
before:
<ProductGrid categoryPath={category.isDynamic ? null : data.route} />
after:
<ProductGrid categoryPath={category.isDynamic ? null : data && data.route} />
Product configurations have now also been added. To use them in your shop add the ProductConfigurationsFragment.gql
to your ProductPageFragment like so:
#import "@jetshop/core/data/fragments/ProductConfigurationsFragment.gql"
fragment ProductPage on Product {
...ProductConfigurations
}
Then update your ProductPage.js
with the following new components and hooks:
const Product = ({ result: { data, loading }, product }) => {
const price = useDynamicPrice(product);
return (
<div>
<Price {...price} />
{product.hasConfigurations && (
<ConfigSwitch
components={{
default: DefaultPicker
}}
/>
)}
</div>
);
};
Upgrading to Flight 3.5.0
If you are using freeShippingLimit from countrySettings
it is now no longer on the selectedChannel
object. You can get it by using the useFreeShippingCheck
hook.
Upgrading to Flight 3.5.1
class LogInPage extends Component {
render() {
return (
<AuthContext.Consumer>
{({ loggedIn }) => (
<MaxWidth css={{ alignItems: 'center', marginTop: '2em' }}>
{loggedIn ? (
<Redirect to="/my-pages" />
) : (
<LogInFormProvider>
{({ globalError, isSubmitting, isValid }) => (
to:
class LogInPage extends Component {
render() {
return (
<MaxWidth css={{ alignItems: 'center', marginTop: '2em' }}>
<LogInFormProvider redirect="my-pages">
{({ globalError, isSubmitting, isValid }) => (
Upgrading to Flight 3.5.2
Handling login redirects has been added to the LoginFormProvider
component. To uptake this convert LoginPage.js
to no longer redirect to my-pages and instead pass it as a prop if you would like to keep this behaviour
Upgrading to Flight 3.6.0 - Image component changes
The Image component has been rewritten in Flight 3.6.0 to use an img
tag instead of a div
with a background-image. This has been done for performance reasons, and also to add support for Chrome's new native lazy loading.
The new Image component accepts the same props as before and should behave in the same way, however the changes necessitated an update to the DOM output for the component. This means you may need to update any CSS you used to override the display of images or their children.
The new DOM structure for the image component is this:
<div
data-flight-image-container=""
data-flight-image-loaded="true"
style="position: relative; overflow:hidden; width:100%; height:auto; padding-bottom: 100%"
>
<!-- The LQIP (Low Quality Image Placeholder) -->
<!-- This shows a tiny blurred version of the full image -->
<picture data-flight-image-placeholder="">
<img
alt=""
src="https://www.demostore.se/pub_images/original/842086630.jpg?extend=copy&width=160&method=fit&height=160&sigma=2.5&minampl=0.5&quality=30"
loading="auto"
style='position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: contain; object-position: center center; opacity: 0; transition-delay: 500ms; font-family: "object-fit: contain";'
/>
</picture>
<!-- The full image -->
<!-- This renders only when the image is in the viewport, OR if native lazy loading is supported -->
<picture data-flight-image="">
<source
type="image/webp"
srcset="
https://www.demostore.se/pub_images/original/842086630.jpg?extend=copy&width=160&method=fit&height=160&type=webp 160w …etc
"
sizes="(max-width: 20rem) 50vw, (max-width: 40rem) 50vw, (max-width: 50rem) 33vw, 25vw"
/>
<source
srcset="
https://www.demostore.se/pub_images/original/842086630.jpg?extend=copy&width=160&method=fit&height=160 160w …etc
"
sizes="(max-width: 20rem) 50vw, (max-width: 40rem) 50vw, (max-width: 50rem) 33vw, 25vw"
/>
<img
alt="Image Alt"
sizes="(max-width: 20rem) 50vw, (max-width: 40rem) 50vw, (max-width: 50rem) 33vw, 25vw"
srcset="
https://www.demostore.se/pub_images/original/842086630.jpg?extend=copy&width=160&method=fit&height=160 160w …etc
"
src="https://www.demostore.se/pub_images/original/842086630.jpg"
loading="lazy"
style='position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: contain; object-position: center center; opacity: 1; transition: opacity 500ms ease 0s; font-family: "object-fit: contain";'
/>
</picture>
<!-- The NOSCRIPT tag loads the full image when JS is disabled -->
<noscript>
<picture>
<img
alt="842086630"
sizes="(max-width: 20rem) 50vw, (max-width: 40rem) 50vw, (max-width: 50rem) 33vw, 25vw"
srcset="
https://www.demostore.se/pub_images/original/842086630.jpg?extend=copy&width=160&method=fit&height=160 160w …etc
"
src="https://www.demostore.se/pub_images/original/842086630.jpg"
loading="lazy"
style="position:absolute; top:0; left:0; width:100%; height:100%; object-fit:contain; object-position:center"
/>
</picture>
</noscript>
<!-- Any children of the image component are rendered inside this container -->
<div data-flight-image-children="" style="position:relative; z-index:2">
Image component children render here! The container is given a z-index of 2
to ensure it appears over the top of the img
</div>
</div>
Note that each of the key elements has a data attribute. You can use this in your CSS to target those parts of the DOM.
For example, in your CSS:
.image [data-flight-image-children] {
font-size: 200%;
p {
line-height: 1.75;
}
}
.image [data-flight-image-placeholder] {
opacity: 0.1;
}
In your JSX:
<Image {...restOfYourImageprops} className="image">
<p>This text will have a font-size of 200% and line-height of 1.75</p>
</Image>
Upgrading to Flight 3.7.0 - New filter implementation
In the Flight 3.7.0, the filter implementation has been rewritten to improve the developer experience, as well as to improve performance. Upgrading to the new version of filters is opt-in for now, but the old implementation will be removed in a future major release.
The previous implementation of filters was quite complex, and so the complexity was hidden away inside the Flight Core and UI packages. The new implementation has a much simpler API, and so the UI has been moved in to the template. This allows you to create any UI or styling that you like.
The new implementation uses Apollo's local state management. If you're interested, you can read the docs on Apollo's website. You don't need to know how this works in order to use the new filters, but you may find it interesting!
In order to switch to the new implementation, there are a few steps to take.
-
Remove references to
FilterContext
andFilterContainer
Filters no longer use React context to manage state. These components should be removed.
Note: if you were previously using FilterContext to enable global filters, you will have to use the previous implementation for now. Global filters will be enabled for the new API in a coming release.
- Remove all UI filter components
The filter components at @jetshop/ui
are using the now-deprecated API. In order to update, you should make your own components for list, boolean, range, and multi list filters. You'll also need to make your own active filters component.
The easiest way to do this is to copy the components from Template Trend. As all styling and UI is now in those template files, you are able to customise them much more easily than before.
- Update your graphql queries
You'll also need to update the CategoryPageFragment
and SearchQuery
to add the new @client
fields. You can see examples of the CategoryPageFragment and the SearchQuery on the main Flight repo.
These client fields are critical to the new implementation, and allow you to query for the filter state alongside the data returned from the API.
For example, this allows you to determine is a ListFilter is active by checking the hasActiveItems
field on the filter itself.
Filter hooks
The new API uses custom hooks to provide all of the functions you will need to apply and clear filters. These replace the previous React context-based implementation.
For example, the previous implementation of boolean filters looked like this:
<FilterContext.Consumer>
{({ activeFilters, applyBoolFilter }) =>
boolFilters.map((filter) => {
const isSelected = activeFilters.booleanFilters.some(
(item) => item.id === stripCategoryId(filter.id)
);
return (
<Checkbox
key={filter.id}
label={filter.name}
checked={isSelected}
onChange={(e) => applyBoolFilter(filter.id, e.currentTarget.checked)}
/>
);
})
}
</FilterContext.Consumer>
The new implementation looks like this:
function BooleanFilter({ filter }) {
const { apply } = useBooleanFilter({ filter });
return (
<Checkbox
key={filter.id}
label={filter.name}
checked={filter.value}
onChange={(e) => {
apply({ value: e.currentTarget.checked });
}}
/>
);
}
The 2 key things to notice here are:
-
The new
useBooleanFilter
hook takes in the filter and returns anapply
function, which can be used to apply the filter. -
The filter has a new (client-only)
value
field, which returns a boolean (true if the boolean filter is applied, false if it is not). The replaces the need for the loop throughactiveFilters
to determine whether the boolean filter is applied or not.
All of the filters have their own respective custom hooks — useBooleanFilter
, useListFilter
, useRangeFilter
, useMultiFilter
.
There is also a useFilters
hook, which can be used to clear all filters:
const { clearAllFilters } = useFilters();
return <button onClick={clearAllFilters}>Clear Filters</button>;
Full documentation
Please see the full documentation for these hooks here.
Upgrading to Flight 3.8.0
react-router has been upgraded to version 5.1, which now includes built in support for hooks. This means that the package we have been using for hook support use-react-router
has been removed and is no longer included in dependencies.
If you're using My pages from flight, you have to make a small change to MyPages.js
:
// replace
import useReactRouter from 'use-react-router';
// with
import { useLocation } from 'react-router';
function MyPages() {
const { loggedIn } = useAuth();
// replace
const { location } = useReactRouter();
// with
const location = useLocation();
// rest of the component
}
If you use use-react-router
somewhere else in the code, make sure to replace those occurances with useLocation
, useHistory
, useParams
or useRouteMatch
depending on what data you need. Read more at the React Router Hooks API documentation.