Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expo vector icons are not loading properly on solito-universal-app-template-nativebase-typescript #43

Closed
yinonburgansky opened this issue Apr 14, 2022 · 14 comments

Comments

@yinonburgansky
Copy link

@expo/vector-icons aren't loading properly in web.
They work well in native, but display as a question mark instead of the icon in web:

image

Steps to reproduce:

import { MaterialIcons } from '@expo/vector-icons'
import { Center, Icon } from 'native-base'
import React from 'react'

export function HomeScreen() {
  return (
    <Center flex={1}>
      <Icon size="sm" as={MaterialIcons} name="menu" />
    </Center>
  )
}
  • Install with yarn and run with yarn web

Thanks!

@jalexw
Copy link

jalexw commented Apr 14, 2022

I have been dealing with the exact same issue of serving icons on the Next.js app and I still have not found a fix. I believe modifications to next.config.js and _document.tsx are required to properly serve the fonts.

I've been playing around with next.config.js as well and I keep getting errors like:
error - ../../node_modules/@native-base/icons/Fonts/AntDesign.ttf Module parse failed: Unexpected character '' (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders (Source code omitted for this binary file)

However, I believe after following the setup guide on NativeBase's icon page something is still not working. Anyone know how to properly use NativeBase icons in a Solito app?

Here is my next.config.js file data:

const { withNativebase } = require("@native-base/next-adapter");
const path = require("path");

module.exports = withNativebase({
  dependencies: [
    "@expo/next-adapter",
    'react-native-vector-icons',
    'react-native-vector-icons-for-web',
    "@native-base/icons",
    "solito",
    "app",
  ],
  nextConfig: {
    projectRoot: __dirname,
    reactStrictMode: true,
    webpack5: true,
    webpack: (config, options) => {
      config.module.rules.push({
        test: /\.ttf$/,
        loader: "url-loader", // or directly file-loader
        include: path.resolve(__dirname, "node_modules/@native-base/icons"),
      });
      config.resolve.alias = {
        ...(config.resolve.alias || {}),
        "react-native$": "react-native-web",
        '@expo/vector-icons': 'react-native-vector-icons',
      };
      config.resolve.extensions = [
        ".web.js",
        ".web.ts",
        ".web.tsx",
        ...config.resolve.extensions,
      ];
      return config;
    },
  },
});

Here is my _document.tsx file:

import React from 'react';

import { default as NativebaseDocument } from '@native-base/next-adapter/document'
import AntDesignFontFaceCSS from '@native-base/icons/FontsCSS/AntDesignFontFaceCSS'
import MaterialIconsFontFaceCSS from '@native-base/icons/FontsCSS/MaterialIconsFontFaceCSS'

const fontsCSS = AntDesignFontFaceCSS + MaterialIconsFontFaceCSS

class Document extends NativebaseDocument {
  //
}

type getInitialPropsFunction = typeof Document.getInitialProps

const getInitialProps: getInitialPropsFunction = async ({ renderPage }) => {
  const res = await NativebaseDocument.getInitialProps({ renderPage })
  const styles = [
    // eslint-disable-next-line react/jsx-key
    <style dangerouslySetInnerHTML={{ __html: fontsCSS }} />,
    ...res.styles,
  ]
  return { ...res, styles: React.Children.toArray(styles) }
}

Document.getInitialProps = getInitialProps

export default Document

Here is my abbreviated usage of an icon in icon.tsx:

import React from 'react';
import { Button, Icon, useTheme } from "native-base";

import { MaterialCommunityIcons } from "@native-base/icons";

export function SettingsIcon() {

  const theme = useTheme();

  return (
    <Button
      onPress={() => {
        console.log("Settings button clicked");
      }}
      variant="ghost"
    >
      <Icon
        as={MaterialCommunityIcons}
        name="cog-outline"
        color={theme.colors.primary["500"]}
      />
    </Button>
  );
}

packages/app/package.json:

{
  "version": "0.0.0",
  "name": "app",
  "main": "index.ts",
  "dependencies": {
    "@native-base/icons": "^0.0.11",
    "@react-navigation/native": "^6.0.8",
    "@react-navigation/native-stack": "^6.5.0",
    "expo-linking": "^3.0.0",
    "firebase": "^9.6.10",
    "native-base": "^3.3.7",
    "react-native-svg": "^12.3.0",
    "react-native-web": "^0.17.7",
    "solito": "0.0.22"
  },
  "sideEffects": false
}

apps/next/package.json:

{
  "name": "next-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@expo/vector-icons": "^12.0.5",
    "@native-base/icons": "^0.0.11",
    "app": "*",
    "next": "12.1.0",
    "raf": "^3.4.1",
    "react-native-vector-icons": "^9.1.0",
    "url-loader": "^4.1.1"
  },
  "devDependencies": {
    "@expo/next-adapter": "^3.1.21",
    "@native-base/next-adapter": "^1.1.9",
    "@types/node": "17.0.21",
    "babel-preset-expo": "^9.0.2",
    "eslint-config-next": "^12.1.0",
    "next-compose-plugins": "^2.2.1",
    "next-fonts": "^1.5.1",
    "next-transpile-modules": "^9.0.0"
  }
}

Any advice for getting NativeBase icons working with Next.js on Solito?

@yinonburgansky
Copy link
Author

I got it working but I'm still getting an error sometimes:
Could not resolve "@expo/vector-icons/Fonts/MaterialIcons.ttf" in file \solito-universal-app-template-nativebase-typescript\apps\next\pages\_document.tsx.

After finding this page: https://docs.nativebase.io/next-adapter-icons
I have made the following changes:

  • apps/next/pages/_document.tsx:
import { default as NativebaseDocument } from '@native-base/next-adapter/document'
import type { DocumentContext, DocumentInitialProps } from 'next/document'
import MaterialIconsFont from 'react-native-vector-icons/Fonts/MaterialIcons.ttf'

const fontsCSS = `
  @font-face {
    src: url(${MaterialIconsFont});
    font-family: MaterialIcons;
  }
`

class Document extends NativebaseDocument {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const props = await super.getInitialProps(ctx)
    const styles = [
      <style key={'fontsCSS'} dangerouslySetInnerHTML={{ __html: fontsCSS }} />,
      ...props.styles,
    ]
    return { ...props, styles }
  }
}

export default Document
  • apps/next/next.config.js:
/** @type {import('next').NextConfig} */

const { withNativebase } = require('@native-base/next-adapter')
const withFonts = require('next-fonts')

module.exports = withNativebase({
  dependencies: [
    '@expo/next-adapter',
    'react-native-vector-icons',
    'react-native-vector-icons-for-web',
    'solito',
    'app',
  ],
  plugins: [withFonts],
  nextConfig: {
    projectRoot: __dirname,
    reactStrictMode: true,
    webpack5: true,
    webpack: (config, options) => {
      config.resolve.alias = {
        ...(config.resolve.alias || {}),
        'react-native$': 'react-native-web',
        '@expo/vector-icons': 'react-native-vector-icons',
      }
      config.resolve.extensions = [
        '.web.js',
        '.web.ts',
        '.web.tsx',
        ...config.resolve.extensions,
      ]
      return config
    },
  },
})
  • packages/app/features/home/screen.tsx:
import MaterialIcons from '@expo/vector-icons/MaterialIcons' 
import { Center, Icon } from 'native-base'
import React from 'react'

export function HomeScreen() {
  return (
    <Center flex={1}>
      <Icon size="sm" as={MaterialIcons} name="menu" />
    </Center>
  )
}

** only works with full path imports
(import { MaterialIcons } from '@expo/vector-icons', is not working because the path is being renamed by babel)

The example repo is using nativebase version 3.3.7 and it looks like there are some changes in the @next branch using premade css styles, in any case we must load the fonts from react-native-vector-icons/Fonts folder, either with next-fonts plugin or with url-loader / file-loader.

I would like to know the official/recommended approach.

@jalexw
Copy link

jalexw commented Apr 15, 2022

Finally got it working in both Next and Expo with no errors after several hours. Thanks for the help @yinonburgansky. Your code of:

const fontsCSS = `
  @font-face {
    src: url(${MaterialIconsFont});
    font-family: MaterialIcons;
  }
`

Caused something like the following to be served by Next:
Screen Shot 2022-04-15 at 3 52 41 PM
I believe the correct @font-face string is generated from the ttf file. I simply used:

const fontsCSS = MaterialIconsFont;
  • Here is my _document.tsx:
import React from 'react';
import { DocumentContext, DocumentInitialProps } from 'next/document';
import { default as NativebaseDocument } from '@native-base/next-adapter/document'

// Icon Font Library Imports
import MaterialIconsFont from '@native-base/icons/FontsCSS/MaterialIconsFontFaceCSS';
  // Turns into a string like this:
  // @font-face {
  //   src: url(/_next/static/chunks/fonts/MaterialIcons-120b4c7bbd155bd0a04dc37d334baced.ttf);
  //   font-family: MaterialIcons;
  // }
  // Next.js serves the required fonts in the header of the page.

// Combine all of the imported @font-face's above into one string
const fontsCSS = MaterialIconsFont;

/* Remake document class with a hook bringing additional fonts into the HTML's <head> to be served by Next.js */
export default class Document extends NativebaseDocument {

  static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
    const props = await super.getInitialProps(ctx);
    const styles = [
      <style dangerouslySetInnerHTML={{ __html: fontsCSS }} />,
      ...props.styles,
    ]
    return { ...props, styles: React.Children.toArray(styles) }
  }
}
  • Here is my next.config.js:
const { withNativebase } = require("@native-base/next-adapter");
const withFonts = require("next-fonts");

module.exports = withNativebase({
  dependencies: [
    "@expo/next-adapter",
    'react-native-vector-icons',
    'react-native-vector-icons-for-web',
    "solito",
    "app",
  ],
  plugins: [[withFonts]],
  nextConfig: {
    projectRoot: __dirname,
    reactStrictMode: true,
    webpack5: true,
    webpack: (config, options) => {
      config.resolve.alias = {
        ...(config.resolve.alias || {}),
        "react-native$": "react-native-web",
        '@expo/vector-icons': 'react-native-vector-icons',
      };
      config.resolve.extensions = [
        ".web.js",
        ".web.ts",
        ".web.tsx",
        ...config.resolve.extensions,
      ];
      return config;
    },
  },
});
  • Finally, use the icons as such:
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import {Icon} from 'native-base'
<Icon
  name="settings"
  as={MaterialIcons}
  size="md"
/>

@yinonburgansky
Copy link
Author

@jalexw thanks, it seems to be working.
** it's worth noting that this solution is using both @native-base/icons (for css and ttf files) and @expo/vector-icons (for jsx)
thus not following either package's usage guide.
anyway since we have a working solution for now, I'm closing this issue.

@jalexw
Copy link

jalexw commented Apr 17, 2022

Hmm, this solution seems to only work for next dev but not for next build-- unless I messed something up elsewhere...

@yinonburgansky Does next build work for you?

@yinonburgansky
Copy link
Author

@jalexw try to add
apps/next/pages/_fonts.d.ts :

declare module '*.ttf'

@yinonburgansky
Copy link
Author

working solution:
after cloning the repo edit:
apps/next/next.config.js:

/** @type {import('next').NextConfig} */

const { withNativebase } = require('@native-base/next-adapter')
const withFonts = require('next-fonts')
const { withExpo } = require('@expo/next-adapter')

module.exports = withNativebase({
  dependencies: ['@expo/next-adapter', 'solito', 'app'],
  plugins: [withFonts, [withExpo, { projectRoot: __dirname }]],
  nextConfig: {
    projectRoot: __dirname,
    reactStrictMode: true,
    webpack5: true,
  },
})

use the icon:
packages/app/features/home/screen.tsx:

import { MaterialIcons } from '@expo/vector-icons'
import { Center, Icon } from 'native-base'
import React from 'react'

export function HomeScreen() {
  return (
    <Center flex={1}>
      <Icon as={MaterialIcons} name="menu" />
    </Center>
  )
}

@miles-118
Copy link

miles-118 commented Jun 30, 2022

working solution: after cloning the repo edit: apps/next/next.config.js:

/** @type {import('next').NextConfig} */

const { withNativebase } = require('@native-base/next-adapter')
const withFonts = require('next-fonts')
const { withExpo } = require('@expo/next-adapter')

module.exports = withNativebase({
  dependencies: ['@expo/next-adapter', 'solito', 'app'],
  plugins: [withFonts, [withExpo, { projectRoot: __dirname }]],
  nextConfig: {
    projectRoot: __dirname,
    reactStrictMode: true,
    webpack5: true,
  },
})

use the icon: packages/app/features/home/screen.tsx:

import { MaterialIcons } from '@expo/vector-icons'
import { Center, Icon } from 'native-base'
import React from 'react'

export function HomeScreen() {
  return (
    <Center flex={1}>
      <Icon as={MaterialIcons} name="menu" />
    </Center>
  )
}

Using your next.config.js I can use expo vector icons. But, it is missing imports and extension support.

Changing apps/next/next.config.js to:

/** @type {import('next').NextConfig} */

const { withNativebase } = require('@native-base/next-adapter')
const withFonts = require('next-fonts')
const { withExpo } = require('@expo/next-adapter')

module.exports = withNativebase({
  dependencies: ['@expo/next-adapter', 'react-native-vector-icons', 'react-native-vector-icons-for-web', 'solito', 'app'],
  plugins: [withFonts, [withExpo, { projectRoot: __dirname }]],
  nextConfig: {
    projectRoot: __dirname,
    reactStrictMode: true,
    webpack5: true,
    webpack: (config, options) => {
      config.resolve.alias = {
        ...(config.resolve.alias || {}),
        'react-native$': 'react-native-web',
      }
      config.resolve.extensions = [
        '.web.js',
        '.web.ts',
        '.web.tsx',
        ...config.resolve.extensions,
      ]
      return config
    },
  },
})

seems to still work.

The problem is '@expo/vector-icons': 'react-native-vector-icons',

@haveamission
Copy link

@miles-118 When trying your method, I am getting the following error:

Error: Expected font asset of type string | FontResource | Asset (number is not supported on web) instead got: null

Any idea?

@miles-118
Copy link

miles-118 commented Aug 2, 2022

@haveamission

Not really.

Make sure you have installed next-fonts with yarn add next-fonts and next-images with yarn add next-images

I've edited my next.config.js a bit though.

You can try what I use now:

/** @type {import('next').NextConfig} */

const { withNativebase } = require('@native-base/next-adapter')
const withFonts = require('next-fonts')
const withImages = require('next-images');
const { withExpo } = require('@expo/next-adapter')

module.exports = withNativebase({
  dependencies: ['@expo/next-adapter', 'react-native-vector-icons', 'react-native-vector-icons-for-web', 'solito', 'app'],
  plugins: [[withFonts, { projectRoot: __dirname }], [withImages, { projectRoot: __dirname }], [withExpo, { projectRoot: __dirname }]],
  nextConfig: {
    projectRoot: __dirname,
    reactStrictMode: true,
    webpack5: true,
    webpack: (config, options) => {
      config.resolve.alias = {
        ...(config.resolve.alias || {}),
        'react-native$': 'react-native-web',
      }
      config.resolve.extensions = [
        '.web.js',
        '.web.ts',
        '.web.tsx',
        ...config.resolve.extensions,
      ]
      return config
    },
    images: {
      disableStaticImages: true
    }
  },
})

@haveamission
Copy link

haveamission commented Aug 2, 2022

@miles-118 Did you do anything special to your _document or any other files?

And hrm, when using your next.config.js, I am still getting the error:

Error: Expected font asset of type string | FontResource | Asset (number is not supported on web) instead got: null

Even with both next-fonts and next-images installed.

I'll try to figure out what might be causing it.

@lefos987
Copy link

lefos987 commented Feb 19, 2023

Hi @haveamission and @miles-118

I am still struggling to make the icons work with this starter template.

I have used the next.config.js that @miles-118 shared but no luck. Are there other changes needed apart from this file?

What does _document.ts look like?

I get the following error:

error - ../../node_modules/@expo/vector-icons/build/createIconSet.js
Module parse failed: Unexpected token (39:27)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|                 }
|                 if (!this.state.fontIsLoaded) {
>                     return <Text />;
|                 }
|                 return (<RNVIconComponent ref={(view) => {

This error seems to be fixed when I add '@expo/vector-icons': 'react-native-vector-icons', back in the aliases but either way the icons do not work.

Thanks in advance for any help

@sejori
Copy link

sejori commented Aug 22, 2023

For anyone still wanting to get icons working (this is not a solution for the .ttf unexpected character error that has fixes listed above):

You need to add the icons package to your next.config.js transpileModules property, like so:

const { withExpo } = require('@expo/next-adapter')

/** @type {import('next').NextConfig} */
const nextConfig = {
  ...
  reactStrictMode: false,
  transpilePackages: [
    'react-native',
    'react-native-web',
    'solito',
    'dripsy',
    '@dripsy/core',
    'moti',
    'app',
    'react-native-reanimated',
    '@expo/html-elements',
    '@expo/vector-icons',
    'react-native-gesture-handler',
  ],
}

module.exports = withExpo(nextConfig)

@albertcito
Copy link

albertcito commented Jan 30, 2024

@sejori I did it, but in any case it doesn't work. I'm getting this issue:

../../node_modules/@expo/vector-icons/build/vendor/react-native-vector-icons/Fonts/Ionicons.ttf
Module parse failed: Unexpected character '' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

Do you know how to handle that now?

I added it to nextjs config file. But it return the same error :|

 webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    config.module.rules.push({
      test: /\.ttf$/,
      loader: "url-loader", // or directly file-loader
      include: path.resolve(__dirname, "node_modules/react-native-vector-icons"),
    });
    return config;
  },

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants