Font Awesome 6 (beta) and dynamic icon name in Next JS - next.js

I am developing a website with Next JS. This is the first time I use this language and I still have a lot to learn.
Now, I have a Pro subscription to Font Awesome and I am looking to use their component directly in my project, as described in their guides.
https://fontawesome.com/v6.0/docs/web/use-with/react/
https://fontawesome.com/v6.0/docs/web/use-with/react/add-icons
Basically, just using a component like this:
<FontAwesomeIcon icon={brands("google")} />
It works.
The problem, which I can't solve, is how to set the "google" value dynamically, in a previously initialized variable. I need that because this values are coming dynamically from a database.
If I try:
var example = "google";
<FontAwesomeIcon icon={brands(example)} />
Then I receive this error: "Only string literals are supported when referencing icons (use a string here instead)".
Anyone have any ideas?

The way you import your icons uses a babel macro. It means that the referenced icon must be known at build time. It just won't work, because Babel can't figure out what icon should be imported, it can only be done during runtime. That's why it tells you that only string literals are supported.
So you need to use the second method, explained in the docs. You have to have some kind of mapping between icon and the value from DB and then grab the relevant icon. Something like this:
// not sure about exact import
import { faGoogle } from '#fortawesome/free-brand-svg-icons';
// you'd put all valid values that can come from the backend here
const myIcons = {
google: faGoogle
}
// ...
const example = 'google';
<FontAwesomeIcon icon={myIcons[example]} />
The other option is to use library.add, explained here and then do the same as above but instead of imported icon use a string, e.g. google: 'fa-brands fa-google' (again, not sure about exact value here).

Would the following work?
var example = ['fab', 'google'];
<FontAwesomeIcon icon={example} />
P.S. It is recommended to use the let or const keywords with ES6.

// Utils
const UpperFirst = (sentence) => {
if (!sentence || (typeof sentence !== 'string')) return null
return `${sentence.charAt(0).toUpperCase()}${sentence.slice(1)}`
}
// React Generic Component
import React from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import * as solid from '#fortawesome/free-solid-svg-icons'
import * as regular from '#fortawesome/free-regular-svg-icons'
import { UpperFirst } from '#utils'
const GenFontAwe = ({ isReg, customClass, nameIco }) => {
if (!nameIco) return null
const finalName = nameIco.split('-').map((cv, ind) => ind === 0 ? cv : UpperFirst(cv)).join('')
const finalIcon = isReg ? regular[finalName] : solid[finalName]
if(!finalIcon) return null
return <FontAwesomeIcon icon={finalIcon} className={customClass || ''} />
}
export default GenFontAwe
// Consume that way:
<GenFontAwe nameIco='fa-folder-open' isReg customClass="h1 p-5" />

Related

Default value is not passed to prop

The code below should display my prop default value ("test") since it received no other value. Yet it displays nothing. Why not?
<template>
<div>
{{ propValue }}
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue"
const props = defineProps<{
myProp: {
type: string
default: "test"
}
}>()
const propValue = props.myProp
</script>
There are two reasons why.
When you use props with TypeScript you lose the ability to set default values, but it can be fixed using the withDefaults macro (you also need to separately declare a Props interface). Side note: I would not use the alternative experimental "Reactivity Transform" solution noted in the docs (it's being removed soon)
export interface Props {
myProp?: string;
}
const props = withDefaults(defineProps<Props>(), {
myProp: 'test'
});
This one isn't actually strictly necessary, but props is a reactive object, and when you assign a single property of props to a new variable, the reactivity breaks. In order to maintain reactivity and stay synced with any future changes to props, use the toRef function
const propValue = toRef(props, 'myProp');
and be sure to import it: import { defineProps, toRef } from 'vue';

Vue - using props on custom elements fails using createApp and mount?

We would like to pass props to custom elements that uses createApp
// index.html
<div id="my-root">
<my-element prop1="abc"></my-element>
</div>
// my-element.vue
<script lang="ts" setup>
const props = defineProps<{ prop1: number }>();
</script>
<template>
{{props.prop1}}
</template>
This works fine, but as our custome element get bigger we would like to register components and use e.g pinia and other tools. Do use those we need to add createApp and mount it. But then prop1 is always undefined
// main.ts
import ...<lots of imports>
import AppCe from "./AppWebComponent.ce.vue";
import { createPinia } from "pinia";
// Adding code below is causing prop1 to be undefined - if we skip this part, prop1 works fine
const pinia = createPinia();
const app = createApp(App);
app.use(pinia).use(ConfirmDialog);
app.component(...<lots of components>);
app.mount("#my-root");
const ceApp = defineCustomElement(AppCe);
customElements.define("my-element", ceApp);
update:
Here's a sample without: https://stackblitz.com/edit/vue3-script-setup-with-vite-56rizn?file=src/my-element/my-element-main.js
And here's a sample with the createApp: https://stackblitz.com/edit/vue3-script-setup-with-vite-gtkbaq?file=index.html
Any idea on how we could solve this?
We have a fallback, that is to do a getElementById and read the attribute value in the mounted callback - but that is not an optimal solution.
Thanks for any ideas!
update2:
Here's an attempt using #duannex suggestion. We're getting closer, the app is availible, components registered, but still no sigar. : https://stackblitz.com/edit/vue3-script-setup-with-vite-ofwcjt?file=src/my-element/defineCustomElementWrapped.js
Based on update2 with the wrapped defineCustomElement; Just pass the props to the render function:
render() {
return h(component, this.$props)
},
https://stackblitz.com/edit/vue3-script-setup-with-vite-vfdnvg?file=src/my-element/defineCustomElementWrapped.js

Checkboxes not working correctly with Array

I have a form that once certain checkboxes are checked, I need them to v-show additional form components. I can get it to work, sort of. If you only choose 1 option, it will populate the correct form, however, If multiple choices are made it doesn't show the additional forms components. Here is my code. It seems that am getting the correct values through the store, I'm just missing something. I apologize in advance if this is a duplicate question. I feel like I've read all of stackoverflow in the last few weeks :) Here is a screencast of it somewhat working http://somup.com/c3hD0TtnJh
I'm using Formkit, Vue3, Pinia. Thanks in advance.
App.vue
<template>
<ReasonForVisit />
<Sports v-show="data.reasonForVisit == 'si' " />
<WorkComp v-show="data.reasonForVisit == 'wc' " />
<Accident v-show="data.reasonForVisit == 'aa' " />
</template>
<script>
import ReasonForVisit from './components/ReasonForVisit.vue'
import Sports from './components/Sports.vue'
import WorkComp from './components/WorkComp.vue'
import Accident from './components/Accident.vue'
import { useFormStore} from './stores/formStore.js'
const data = useFormStore()
</script>
ReasonForVisit.vue
<template>
<FormKit
v-model="data.reasonForVisit"
type="checkbox"
label="Reason for Visit"
:options="{
we: 'Wellness Check',
aa: 'Auto Accident',
si: 'Sports Injury',
wc: 'Work Comp' }"
validation="required"
#change="data.checkedReason"
/>
<p>reason: {{ data.reasonForVisit }}</p>
</template>
FormStore.js
import { defineStore } from 'pinia'
import { differenceInYears, parseISO } from 'date-fns'
export const useFormStore = defineStore('formStore', {
state: () => ({
reasonForVisit: [],
}),
},
}
)
Your reasonForVisit is an array as you initialize it in FormStore.js as reasonForVisit: [].
Instead of data.reasonForVisit == 'si' and others, if you check for data.reasonForVisit.includes('si'), it should work as intended.
It shouldn't be working for 1 option too right now, I think the only reason it works for the 1 option case is the == operator considers si to be the same as ['si']. You should use strict equality to avoid such bugs in js.
You cannot compare Array with string. You need to iterate it or filter or use include method on it and check are it contains correct word. To do that, read about computed() properties. And if you are new to programming, use === not just == or you end up with bad errors in code.
Example:
<template>
<ReasonForVisit />
<Sports v-show="containsSi" />
<WorkComp v-show="containsWc" />
<Accident v-show="containsAa" />
</template>
<script>
import ReasonForVisit from './components/ReasonForVisit.vue'
import Sports from './components/Sports.vue'
import WorkComp from './components/WorkComp.vue'
import Accident from './components/Accident.vue'
import { useFormStore} from './stores/formStore.js'
const data = useFormStore()
const containsSi = computed(() => data.reasonForVisit.includes('si'))
const containsWc = computed(() => data.reasonForVisit.includes('wc'))
const containsAa = computed(() => data.reasonForVisit.includes('aa'))
</script>

Dynamically load .css based on condition in reactJS application

I have a reactJS application that I want to make available to multiple clients. Each clients has unique color schemes. I need to be able to import the .css file that corresponds to the specific client.
For example, if client 1 logs into the application, I want to import client1.css. if client 2 logs into the application, I want to import client2.css. I will know the client number once I have validated the login information.
The application contains multiple .js files. Every .js file contains the following at the top of the file
import React from 'react';
import { Redirect } from 'react-router-dom';
import {mqRequest} from '../functions/commonFunctions.js';
import '../styles/app.css';
Is there a way to import .css files dynamically for this scenario as opposed to specifying the .css file in the above import statement?
Thank you
Easy - i've delt with similar before.
componentWillMount() {
if(this.props.css1 === true) {
require('style1.css');
} else {
require('style2.css');
}
}
Consider using a cssInJs solution. Popular libraries are: emotion and styled-components but there are others as well.
I generally recommend a cssInJs solution, but for what you are trying to do it is especially useful.
In Emotion for example they have a tool specifically build for this purpose - the contextTheme.
What cssInJs basically means is that instead of using different static css files, use all the power of Javascript, to generate the needed css rules from your javascript code.
A bit late to the party, I want to expand on #Harmenx answer.
require works in development environments only, once it goes to production you're likely to get errors or not see the css file at all. Here are some options if you, or others, encounter this:
Option 1: Using css modules, assign a variable of styles with the response from the import based on the condition.
let styles;
if(this.props.css1 === true) {
//require('style1.css');
import("./style1.module.css").then((res) => { styles = res;});
} else {
//require('style2.css');
import("./style2.module.css").then((res) => { styles = res;});
}
...
<div className={styles.divClass}>...</div>
...
Option 2: using Suspend and lazy load from react
// STEP 1: create components for each stylesheet
// styles1.js
import React from "react";
import "./styles1.css";
export const Style1Variables = (React.FC = () => <></>);
export default Style1Variables ;
// styles2.js
import React from "react";
import "./styles2.css";
export const Style2Variables = (React.FC = () => <></>);
export default Style2Variables ;
// STEP 2: setup your conditional rendering component
import React, {lazy, Suspense} from "react";
const Styles1= lazy(() => import("./styles1"));
const Styles2= lazy(() => import("./styles2"));
export const ThemeSelector = ({ children }) => {
return (
<>
<Suspense fallback={null} />}>
{isClient1() ? <Styles1 /> : <Styles2/>}
</Suspense>
{children}
</>
);
};
// STEP 3: Wrap your app
ReactDOM.render(
<ThemeSelector>
<App />
</ThemeSelector>,
document.getElementById('root')
);
Option 3: Use React Helm which will include a link to the stylesheet in the header based on a conditional
class App extends Component {
render() {
<>
<Helmet>
<link
type="text/css"
rel="stylesheet"
href={isClient1() ? "./styles1.css" : "./styles2.css"}
/>
</Helmet>
...
</>
}
}
Personally, I like option 2 because you can set a variable whichClientIsThis() then modify the code to:
import React, {lazy, Suspense} from "react";
let clientID = whichClientIsThis();
const Styles= lazy(() => import("./`${clientID}`.css")); // change your variable filenames to match the client id.
export const ThemeSelector = ({ children }) => {
return (
<>
<Suspense fallback={null} />}>
<Styles />
</Suspense>
{children}
</>
);
};
ReactDOM.render(
<ThemeSelector>
<App />
</ThemeSelector>,
document.getElementById('root')
);
This way you don't need any conditionals. I'd still recommend lazy loading and suspending so the app has time to get the id and make the "decision" on which stylesheet to bring in.

Add a class to react-day-picker?

How can I add a class to react-day-picker's today button?
It seems to be possible from the documentation:
http://react-day-picker.js.org/api/DayPicker#classNames
const dayPickerClassNames = { todayButton: 'newClass' };
<DayPicker
classNames={dayPickerClassNames}
/>
However Im getting an error:
Warning: Failed prop type: The prop `classNames.day` is marked as required in `DayPicker`, but its value is `undefined`.
According to the API, it expects the following keys (ie, it needs the container, wrapper, .. months.. month.. day.. etc. keys), but you are only providing the todayButton key/value, apparently you need to provide each key/value pair.
You should be able to import the default classNames object, and then just update the todayButton value like so:
import classNames from '../classNames' // this path is probably not correct
const dayPickerClassNames = { ...classNames, todayButton: 'newClass' };
<DayPicker
classNames={dayPickerClassNames}
/>
const defaultClassNames = DayPicker.defaultProps.classNames,
newClassNames = { ...datePickerClassNames,
container: `${defaultClassNames.container} MY_CONTAINER`
};
...
<DayPicker classNames={dayPickerClassNames}/>
I'd recommend appending the default class with your own as in the example above the default container class appended with MY_CONTAINER class.
I believe it may just be parsing the word day and looking for a className for that parsed day******* I think I would just try defining the key:value relationship as they have in their documentation.
// const dayPickerClassNames = { todayButton: 'newClass' };
<DayPicker
classNames={ todayButton: 'newClass' }
/>

Resources