Gatsby: My build scripts won't run successfully

Created on 18 Jun 2019  路  10Comments  路  Source: gatsbyjs/gatsby

Summary

Hi guys,

I am a bit stuck building a prototype app I was experimenting with. I started all good and wrote a bit of code but then realised that when I stop the dev server, it won't run nor will the build build successfully.

The issue started when I introduced a library that uses localStorage and window.CustomEvent.

Relevant information

I managed to fix the gatsby develop script by nulling the library as below in my gatsby-node.js file

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === 'develop-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /@rehooks\/local-storage/,
            use: loaders.null()
          }
        ]
      }
    });
  }
};

After doing the above the, I was able to run dev but now builds still fail. The error I got was as below;

  2 | Object.defineProperty(exports, "__esModule", { value: true });
  3 | const react_1 = require("react");
> 4 | class LocalStorageChanged extends CustomEvent {
    | ^
  5 |     constructor(payload) {
  6 |         super(LocalStorageChanged.eventName, { detail: payload });
  7 |     }

So I also nulled the lib on the build-html stage as well so my gatsby-node.js now looks as below;

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === 'develop-html' || 'build-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /@rehooks\/local-storage/,
            use: loaders.null()
          }
        ]
      }
    });
  }
};

Now I start getting a new error in a custom hook file that uses the localStorage lib. The error is as below;

const useCart = () => {
> 6 |   const [raw_cart_items] = useLocalStorage(CART_ITEMS);
    |                                            ^
  7 |   const cartItems = JSON.parse(raw_cart_items);
  8 | 
  9 |   const addItemToCart = data => {


  WebpackError: TypeError: Object(...) is not a function

  - useCart.js:6 useCart

The content of the useCart hook is as below;

import { writeStorage, useLocalStorage } from '@rehooks/local-storage';

import { CART_ITEMS } from '../constants/configs';

const useCart = () => {
  const [raw_cart_items] = useLocalStorage(CART_ITEMS);
  const cartItems = JSON.parse(raw_cart_items);

  const addItemToCart = data => {
    writeStorage(CART_ITEMS, cartItems ? JSON.stringify([...cartItems, ...data]) : JSON.stringify([...data]));
  };

  const deleteItemFromCart = product_item_id => {
    const newItems = cartItems.filter(item => item.product_item_id !== product_item_id);
    writeStorage(CART_ITEMS, JSON.stringify(newItems));
  };

  const updateCartItem = (products_item_id, data) => {
    const newItems = cartItems.map(item => {
      if (item.product_item_id === products_item_id) {
        item = { ...item, ...data };
        return item;
      }
      return item;
    });
    writeStorage(CART_ITEMS, JSON.stringify(newItems));
  };

  const clearCartItems = () => {
    writeStorage(CART_ITEMS, JSON.stringify([]));
  };

  const cartTotalAmount =
    cartItems && cartItems.length
      ? cartItems.reduce((prevAmount, currentItem) => prevAmount + currentItem.amount, 0)
      : 0;
  return {
    addItemToCart,
    cartTotalAmount,
    cartItems,
    deleteItemFromCart,
    updateCartItem,
    clearCartItems
  };
};

export default useCart;

I am using the above hook along with the ContextAPI and I have a container that looks like below;

import React from 'react';
import { SearchContext, CartContext } from '../context/';
import { useSearch, useCart } from '../hooks/';

const AppContainer = ({ children }) => {
  const { setSearchValue, toggleSearch, searchText, isSearch } = useSearch(); // search hook
  const { addItemToCart, cartItems, deleteItemFromCart, updateCartItem, clearCartItems, cartTotalAmount } = useCart(); // shopping cart hook

  return (
    <SearchContext.Provider value={{ setSearchValue, toggleSearch, searchText, isSearch }}>
      <CartContext.Provider
        value={{ addItemToCart, cartItems, deleteItemFromCart, updateCartItem, clearCartItems, cartTotalAmount }}
      >
        {children}
      </CartContext.Provider>
    </SearchContext.Provider>
  );
};

export default AppContainer;

I use the above container in my gatsby-browser and `gatsby-ssr as depicted below;

Environment (if relevant)

System:
    OS: macOS 10.14.5
    CPU: (4) x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 10.3.0 - ~/.nvm/versions/node/v10.3.0/bin/node
    Yarn: 1.7.0 - ~/.yarn/bin/yarn
    npm: 6.9.0 - ~/.nvm/versions/node/v10.3.0/bin/npm
  Languages:
    Python: 2.7.10 - /usr/bin/python
  Browsers:
    Chrome: 75.0.3770.90
    Safari: 12.1.1
  npmPackages:
    gatsby: ^2.4.3 => 2.4.3 
    gatsby-cli: ^2.6.7 => 2.6.7 
    gatsby-image: ^2.0.41 => 2.0.41 
    gatsby-plugin-manifest: ^2.1.1 => 2.1.1 
    gatsby-plugin-offline: ^2.1.0 => 2.1.0 
    gatsby-plugin-prefetch-google-fonts: ^1.4.2 => 1.4.2 
    gatsby-plugin-react-helmet: ^3.0.12 => 3.0.12 
    gatsby-plugin-sass: ^2.0.11 => 2.0.11 
    gatsby-plugin-sharp: ^2.0.37 => 2.0.37 
    gatsby-source-filesystem: ^2.0.33 => 2.0.33 
    gatsby-source-graphql: ^2.0.18 => 2.0.19 
    gatsby-transformer-sharp: ^2.1.19 => 2.1.19 

File contents (if changed)

gatsby-config.js: N/A
package.json: N/A
gatsby-node.js:

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === 'develop-html' || 'build-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /@rehooks\/local-storage/,
            use: loaders.null()
          }
        ]
      }
    });
  }
};

gatsby-browser.js:

import React from 'react';
import { ClientContext } from 'graphql-hooks';
import client from './src/api/graphql-client';
import Layout from './src/components/layout';
import AppContainer from './src/components/app-container';
export const onServiceWorkerUpdateReady = () => {
  const answer = window.confirm(`This application has been updated. Reload to display the latest version?`);

  if (answer === true) {
    window.location.reload();
  }
};

export const wrapRootElement = ({ element }) => {
  return (
    <AppContainer>
      <ClientContext.Provider value={client}>{element}</ClientContext.Provider>
    </AppContainer>
  );
};

export const wrapPageElement = ({ element, props }) => {
  return <Layout {...props}>{element}</Layout>;
};

gatsby-ssr.js:

import React from 'react';
import { ClientContext } from 'graphql-hooks';
import AppContainer from './src/components/app-container';
import client from './src/api/graphql-client';

import Layout from './src/components/layout';

export const wrapRootElement = ({ element }) => {
  return (
    <AppContainer>
      <ClientContext.Provider value={client}>{element}</ClientContext.Provider>
    </AppContainer>
  );
};

export const wrapPageElement = ({ element, props }) => {
  return <Layout {...props}>{element}</Layout>;
};

I have a reproduction on Codesandbox on here

question or discussion

All 10 comments

The issue here is that you鈥檙e trying to use local storae (useLocalStorage) in render and this isn鈥檛 SSR friendly since local storage is only available in the browser env.

You can fix this by using useLocalStorage in a useEffect hook (these are only run on the client side after render, similar to componentDidMount)

if (stage === 'develop-html' || 'build-html')

That's not how JavaScript comparison works. Replace it with this:

if (stage === 'develop-html' || stage === 'build-html')

Or

if (['develop-html', 'build-html'].includes(stage))

You can fix this by using useLocalStorage in a useEffect hook

:thinking: I don't think you can use any hook inside built-in hooks. That would violate the Rules of Hooks,

馃 I don't think you can use any hook inside built-in hooks. That would violate the Rules of Hooks

You can't conditionally call hooks but you can use hooks inside a hook. That's what most custom hooks do 馃檪

The issue here is that you鈥檙e trying to use local storae (useLocalStorage) in render and this isn鈥檛 SSR friendly since local storage is only available in the browser env.

You can fix this by using useLocalStorage in a useEffect hook (these are only run on the client side after render, similar to componentDidMount)

@sidharthachatterjee do you mind showing how to approach this with an example? Maybe from the sandbox link?

@joeynimu Sure! Found these that illustrate what I meant (I didn't make them, just found them)

You can't conditionally call hooks but you can use hooks inside a hook. That's what most custom hooks do :slightly_smiling_face:

I'm probably commenting about unrelated matter, sorry about that. But, I'm pretty sure you can't call useLocalStorage hook inside useEffect.

function Test() {
  useEffect(() => {
    useLocalStorage()
  })
  return <div>test</div>
}

React won't allow it, because useLocalStorage hook is not being called from the Top Level. It's being called inside a callback function.

React won't allow it, because useLocalStorage hook is not being called from the Top Level. It's being called inside a callback function.

Yeah, you're right. What I meant to say is that you could create a custom hook called useLocalStorage that uses useEffect internally and does side effects in it.

React won't allow it, because useLocalStorage hook is not being called from the Top Level. It's being called inside a callback function.

Yeah, you're right. What I meant to say is that you could create a custom hook called useLocalStorage that uses useEffect internally and does side effects in it.

@sidharthachatterjee From the above conversation, I am getting that then your initial approach won't work for I will be calling one custom hook (in this case the @rehooks/local-storage lib) inside useEffect of another custom hook (in this case my useCart CH). Correct me if I am wrong.

Alright, thanks guys for your input.

I ended up fixing this by implementing my own logic in the hook and using ContextAPI to keep my components in sync. I did it like below;

import { useState, useEffect } from 'react';

const PREFIX = `soko_yetu_cart_items`;

const useCart = () => {
  const [cartItems, setCartItems] = useState([]);

  useEffect(() => {
    setCartItems(JSON.parse(window.localStorage.getItem(PREFIX)) || []);
  }, [cartItems]);

  const addItemToCart = item => {
    const items = [...cartItems, ...item];
    window.localStorage.setItem(PREFIX, JSON.stringify(items));
    setCartItems(items);
  };

  const deleteItemFromCart = product_item_id => {
    const newItems = cartItems.filter(item => item.product_item_id !== product_item_id);
    window.localStorage.setItem(PREFIX, newItems);
    setCartItems(newItems);
  };

  const updateCartItem = (products_item_id, data) => {
    const newItems = cartItems.map(item => {
      if (item.product_item_id === products_item_id) {
        item = { ...item, ...data };
        return item;
      }
      return item;
    });

    window.localStorage.setItem(PREFIX, newItems);
    setCartItems(newItems);
  };

  const clearCartItems = () => {
    setCartItems([]);
  };

  const cartTotalAmount =
    cartItems && cartItems.length
      ? cartItems.reduce((prevAmount, currentItem) => prevAmount + currentItem.amount, 0)
      : 0;

  return {
    addItemToCart,
    cartTotalAmount,
    cartItems,
    deleteItemFromCart,
    updateCartItem,
    clearCartItems
  };
};

export default useCart;

With the above code I also deleted the nulling code in my gatsby-node.js for I am no longer using the library. I hope anyone else with such a problem will get the gist of how to solve this.

I am closing this for now, cheers!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

3CordGuy picture 3CordGuy  路  3Comments

benstr picture benstr  路  3Comments

timbrandin picture timbrandin  路  3Comments

magicly picture magicly  路  3Comments

dustinhorton picture dustinhorton  路  3Comments