Advanced Select does not render conditionally in Next.js
If rendered conditionally, the select is not rendered at all. I have checked the other issues, but could not find any specific related to this topic. If it is a duplicate, please close it :)
'use client';
import React, { useState } from 'react';
const Custom = () => {
const [display, setDisplay] = useState(false);
return (
<div>
<div onClick={() => setDisplay(true)}>display to true</div>
{display && (
<div>
yes
<select
data-hs-select='{
"placeholder": "Select option...",
"toggleTag": "<button type=\"button\"></button>",
"toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-3 px-4 pe-9 flex text-nowrap w-full cursor-pointer bg-white border border-gray-200 rounded-lg text-start text-sm focus:border-blue-500 focus:ring-blue-500 before:absolute before:inset-0 before:z-[1] dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400",
"dropdownClasses": "mt-2 z-50 w-full max-h-72 p-1 space-y-0.5 bg-white border border-gray-200 rounded-lg overflow-hidden overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 dark:bg-neutral-900 dark:border-neutral-700",
"optionClasses": "py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-none focus:bg-gray-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 dark:text-neutral-200 dark:focus:bg-neutral-800",
"optionTemplate": "<div class=\"flex justify-between items-center w-full\"><span data-title></span><span class=\"hidden hs-selected:block\"><svg class=\"flex-shrink-0 size-3.5 text-blue-600 dark:text-blue-500\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>",
"extraMarkup": "<div class=\"absolute top-1/2 end-3 -translate-y-1/2\"><svg class=\"flex-shrink-0 size-3.5 text-gray-500 dark:text-neutral-500\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 15 5 5 5-5\"/><path d=\"m7 9 5-5 5 5\"/></svg></div>"
}'
className="hidden"
>
<option value="">Choose</option>
<option>Name</option>
<option>Email address</option>
<option>Description</option>
<option>User ID</option>
</select>
</div>
)}
</div>
);
};
export default Custom;
I have created https://stackblitz.com/edit/stackblitz-starters-uc54d1 for it.
It seems, that the select is not rendered if the visibility is handled with a state update. It works fine, if the select is put outside of the condition.
I am experiencing similar issues with Advanced Select and other pure JS components. Here's what might be happening:
- Preline is a pure JS framework and as such, it is outside of React's virtual DOM (uncontrolled)
- The Preline component gets rendered when the DOM is ready
- Any subsequent changes to the React state only re-render that node of the DOM, not the entire tree
- As such, the Preline pure JS component never gets re-rendered
- @ahofmeister - in your case it remains hidden
- It works [perfectly], except for any state changes. For example, if an error state must render the advanced select in a red border, that also doesn't work, since it requires a re-render based on a state change, which simply doesn't work.
If anyone can point out a good way to get around this, the Advanced Select and other JS components are amazingly-well designed.
If rendered conditionally, the select is not rendered at all. I have checked the other issues, but could not find any specific related to this topic. If it is a duplicate, please close it :)
'use client'; import React, { useState } from 'react'; const Custom = () => { const [display, setDisplay] = useState(false); return ( <div> <div onClick={() => setDisplay(true)}>display to true</div> {display && ( <div> yes <select data-hs-select='{ "placeholder": "Select option...", "toggleTag": "<button type=\"button\"></button>", "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-3 px-4 pe-9 flex text-nowrap w-full cursor-pointer bg-white border border-gray-200 rounded-lg text-start text-sm focus:border-blue-500 focus:ring-blue-500 before:absolute before:inset-0 before:z-[1] dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400", "dropdownClasses": "mt-2 z-50 w-full max-h-72 p-1 space-y-0.5 bg-white border border-gray-200 rounded-lg overflow-hidden overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 dark:bg-neutral-900 dark:border-neutral-700", "optionClasses": "py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-none focus:bg-gray-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 dark:text-neutral-200 dark:focus:bg-neutral-800", "optionTemplate": "<div class=\"flex justify-between items-center w-full\"><span data-title></span><span class=\"hidden hs-selected:block\"><svg class=\"flex-shrink-0 size-3.5 text-blue-600 dark:text-blue-500\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>", "extraMarkup": "<div class=\"absolute top-1/2 end-3 -translate-y-1/2\"><svg class=\"flex-shrink-0 size-3.5 text-gray-500 dark:text-neutral-500\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 15 5 5 5-5\"/><path d=\"m7 9 5-5 5 5\"/></svg></div>" }' className="hidden" > <option value="">Choose</option> <option>Name</option> <option>Email address</option> <option>Description</option> <option>User ID</option> </select> </div> )} </div> ); }; export default Custom;I have created https://stackblitz.com/edit/stackblitz-starters-uc54d1 for it.
It seems, that the select is not rendered if the visibility is handled with a state update. It works fine, if the select is put outside of the condition.
Hi,
in your case, the best way to use HSSelect is to initialize it using a constructor.
'use client';
import React, { useState, useEffect, useRef } from 'react';
import { HSSelect, ISelect, ISelectOptions } from 'preline/preline';
const Custom = () => {
const selectRef = useRef<null | HTMLSelectElement>(null);
const hsSelectRef = useRef<ISelect | null>(null);
const [display, setDisplay] = useState(false);
const [selectVal, setSelectVal] = useState('');
const options: ISelectOptions = {
placeholder: 'Select option...',
dropdownSpace: 10,
value: selectVal,
toggleTag: '<button type="button"></button>',
toggleClasses:
'hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-3 px-4 pe-9 flex text-nowrap w-full cursor-pointer bg-white border border-gray-200 rounded-lg text-start text-sm focus:border-blue-500 focus:ring-blue-500 before:absolute before:inset-0 before:z-[1] dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400',
dropdownClasses:
'mt-2 z-50 w-full max-h-72 p-1 space-y-0.5 bg-white border border-gray-200 rounded-lg overflow-hidden overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 dark:bg-neutral-900 dark:border-neutral-700',
optionClasses:
'py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-none focus:bg-gray-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 dark:text-neutral-200 dark:focus:bg-neutral-800',
optionTemplate:
'<div class="flex justify-between items-center w-full"><span data-title></span><span class="hidden hs-selected:block"><svg class="flex-shrink-0 size-3.5 text-blue-600 dark:text-blue-500" xmlns="http:.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></span></div>',
extraMarkup:
'<div class="absolute top-1/2 end-3 -translate-y-1/2"><svg class="flex-shrink-0 size-3.5 text-gray-500 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg></div>',
};
useEffect(() => {
if (display && selectRef.current) {
hsSelectRef.current = new HSSelect(selectRef.current, options);
(hsSelectRef.current as HSSelect).value = selectVal;
(hsSelectRef.current as HSSelect).on('change', (payload: string) =>
setSelectVal(payload)
);
return () => {
if (hsSelectRef.current && (hsSelectRef.current as ISelect).destroy) {
(hsSelectRef.current as ISelect).destroy();
hsSelectRef.current = null;
}
};
}
}, [display]);
return (
<div>
<button type="button" onClick={() => setDisplay(!display)}>
Toggle select
</button>
{display && (
<div>
yes
<select
ref={selectRef}
data-hs-select
className="--prevent-on-load-init"
style={{
display: 'none',
}}
>
<option value="">Choose</option>
<option value="name">Name</option>
<option value="emailAddress">Email address</option>
<option value="description">Description</option>
<option value="userId">User ID</option>
</select>
</div>
)}
</div>
);
};
export default Custom;
I've added an example here https://stackblitz.com/edit/stackblitz-starters-nxlqpw
Summary:
- replace data parameters with js parameters
- initialize the element using the constructor
- use refs
- destroy the select when unmounting
Hi @olegpix
I really appreciate your answer and time you put into it! We decided (not only because of this issue) to move forward with another library instead of Preline.
Not sure if you want to have this issue to remain open or not.
@olegpix There's an error in the stackblitz. https://stackblitz.com/edit/stackblitz-starters-uc54d1
ReferenceError: self is not defined
Any notes on how to resolve?
@olegpix There's an error in the stackblitz. https://stackblitz.com/edit/stackblitz-starters-uc54d1
ReferenceError: self is not definedAny notes on how to resolve?
I solved it this way. But this is not a subject of the opened issue.
import dynamic from 'next/dynamic';
const DynamicCustom = dynamic(() => import('@/components/custom'), {
ssr: false,
});
export default function Home() {
return (
<main className="flex min-h-screen flex-col">
<DynamicCustom />
</main>
);
}
The same happens when used in Angular and I suspect a similar reason as in Next.js. You have to call window.HSStaticMethods.autoInit(['select']); to make it work again.
The same happens when used in Angular and I suspect a similar reason as in Next.js. You have to call
window.HSStaticMethods.autoInit(['select']);to make it work again.
@Gordi90 provide the vivid hint. Just use autoInit();
useEffect(() => {
const loadPreline = async () => {
await import("preline/preline");
window.HSStaticMethods.autoInit();
};
loadPreline();
}, [isLoaded]);