I am trying to create a custom menu select for my Algolia search based on the documentation here with a barebones example. I am using Tailwind and Headless UI for my component styling.
More specifically, I am trying to make this work with the ListBox component by Headless UI, which is documented here. My code is below:
import { connectMenu } from "react-instantsearch-dom";
import React, { useState, Fragment, useEffect } from "react";
import { Listbox, Transition } from "#headlessui/react";
import { CheckIcon, SelectorIcon } from "#heroicons/react/solid";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const MobileDropdownS = ({ label, currentRefinement, refine, hide }) => {
const [selected, setSelected] = useState(true);
const items= [
{ value: 'category', label: 'Category' },
{ value: 'brand', label: 'Brand' },
{ value: 'color', label: 'Color' },
{ value: 'size', label: 'Size' },
]
if (hide) return null;
return (
<Listbox
onChange={(event) => {
refine(event.target.value);
setSelected(event.target.value);
}}
value={currentRefinement || ""}
>
{({ open }) => (
<div>
<Listbox.Label>{label}</Listbox.Label>
<div>
<Listbox.Button>
<span>{label}</span>
</Listbox.Button>
<Listbox.Options>
{items.map((item) => (
<Listbox.Option
key={item.label}
value={item.isRefined ? currentRefinement : item.value}
key={item.label}
className={({ active }) =>
classNames(
active ? "text-white bg-indigo-600" : "text-gray-900",
"cursor-default select-none relative py-2 pl-3 pr-9"
)
}
>
{({ selected, active }) => (
<>
<div className="flex items-center">
<span
className={classNames(
selected ? "font-semibold" : "font-normal",
"ml-3 block truncate"
)}
>
{item.label}
</span>
</div>
{selected ? (
<span
className={classNames(
active ? "text-white" : "text-indigo-600",
"absolute inset-y-0 right-0 flex items-center pr-4"
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</div>
</div>
)}
</Listbox>
);
};
const MobileDropdownSelect = connectMenu(MobileDropdownS);
export default MobileDropdownSelect;
The way I understand it, ListBox becomes the <select> tag from Algolia's documentation, where I should add this:
refine(event.target.value);
setSelected(event.target.value);
However, this gives me the following error:
×
TypeError: Cannot read properties of undefined (reading 'value')
onChange
D:/Gatsby/XTheme/src/components/search/MobileDropdown.jsx:22
19 | <Listbox
20 | onChange={
21 | event => {
> 22 | refine(event.target.value)
23 | setSelected(event.target.value)
24 | }
25 | }
Is this not correct? How do I fix this error and port over Algolia's example with the <select> input to the <ListBox> component such that when I select an item from the Listbox, it refines the search based on the item?
Tru using event.currentTarget.value in place of event.target.value . You can find difference between both in this link
Related
** const initialValues AssignClass: [],**
**NOW IN THE MULTISELECT DROP DOWN COMPONENT, i have set up formik there to my best effort, take a look **
import { ErrorMessage, useField } from "formik";
import Multiselect from "multiselect-react-dropdown";
const optionsArray = [
{ name: "Option1", id: 1 },
{ name: "Daniel", id: 2 },
];
const AppMultiSelect = ({
label = "Assign Class(es)",
obscure = false,
extraClasses,
error,
touched,
onFieldTouched,
options,
onSelect,
onRemove,
...props
}) => {
const [field, meta, helpers] = useField(props);
console.log(helpers, " the helpers");
return (
<div className="flex flex-col cursor-pointer w-full z-50 p-[5px]">
<label className="text-[14px] text-[#7D8592] font-semibold mb-2">
{label}
</label>
<div className=" w-full text-[#DADADA] py-[7px] border border-[#D8E0F0] rounded-2xl focus:border-danger focus:ring-danger py-[12px] px-[14px]">
<Multiselect
options={options} // Options to display in the dropdown
selectedValues={null} // Preselected value to persist in dropdown
onSelect={(selectedList) => {
helpers.setValue(...field.value, selectedList);
console.log(selectedList, " you selected this");
}} // Function will trigger on select event
onRemove={(selectedItem) =>
console.log(field.value, " to be removed")
} // Function will trigger on remove event
displayValue="name" // Property name to display in the dropdown options
{...props}
/>
</div>
{/* {error && touched && (
<span className="text-[14px] text-accent">{error}</span>
)} */}
</div>
);
};
export default AppMultiSelect;
** so, as you see above, i'm using the setValue, but then, the field updates but it happens once, even if i keep selecting more items... thanks in advance, by the way, im doing the formik validation and the rest in another component called addTeacher, and using yup for the schema part **
AppMultiSelect
name="AssignClass"
options={[
{ name: "fred" },
{ name: "cere" },
{ name: "veee" },
{ name: "mmeee" },
{ name: "typ" },
{ name: "wewe" },
]}
/>
I am trying to make a webapplication with Treeviz dependency. The goal is to place a popover button to each node of the tree and if user clicks to the button he/she can see the description of the node,and after it should be editable. I tried in many ways but for me popover does not work in React.
There is an example for what I would like to do. You can see I have to insert React component to HTML therefor I am using renderToString. All you have to look is the renderNode property of the tree. I am referencing to React component in renderNode like: ${tooltip} ${popover}.
import React from "react";
import { TreevizReact } from "treeviz-react";
import { renderToString } from "react-dom/server";
const data_1 = [
{
id: 1,
text_1: "Chaos",
description: "Void",
father: null,
color: "#FF5722"
},
{
id: 2,
text_1: "Tartarus",
description: "Abyss",
father: 1,
color: "#FFC107"
},
{ id: 3, text_1: "Gaia", description: "Earth", father: 1, color: "#8BC34A" },
{ id: 4, text_1: "Eros", description: "Desire", father: 1, color: "#00BCD4" }
];
export const App = () => {
return (
<div style={{ marginLeft: 10 }}>
<div style={{ display: "flex" }}>
<TreevizReact
data={data_1}
relationnalField={"father"}
nodeWidth={120}
nodeHeight={80}
areaHeight={500}
areaWidth={1000}
mainAxisNodeSpacing={2}
secondaryAxisNodeSpacing={2}
linkShape={"quadraticBeziers"}
renderNode={(data) => {
const nodeData = data.data;
const settings = data.settings;
let result = "";
const tooltip = renderToString(
<strong
data-toggle="tooltip"
data-placement="top"
title={nodeData.description}
>
{nodeData.text_1}
</strong>
);
const popover = renderToString(
<button
type="button"
className="btn btn-secondary"
data-container="body"
data-toggle="popover"
data-placement="top"
data-content={nodeData.description}
>
Popover on top
</button>
);
if (data.depth !== 2) {
result = `<div className="box"
style='cursor:pointer;height:${settings.nodeHeight}px; width:${settings.nodeWidth}px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:${nodeData.color};border-radius:5px;'>
<div>
${tooltip}
${popover}
</div></div>`;
} else {
result = `<div className="box" style='cursor:pointer;height:${
settings.nodeHeight
}px; width:${
settings.nodeHeight
}px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:${
nodeData.color
};border-radius:${settings.nodeHeight / 2}px;'><div><strong>
${nodeData.text_1}
</strong></div></div>`;
}
return result;
}}
duration={600}
isHorizontal
linkWidth={(node) => 10}
/>
</div>
</div>
);
};
export default App;
Tooltip is working but popover does not show up.
You can try it: https://codesandbox.io/s/zealous-orla-4bq5f?file=/src/App.js
Also tried
const popover = renderToString(
<Popup
trigger={<button> Trigger</button>}
position="right center"
>
<form onSubmit={saveHandler}>
<ContentEditable
html={text.current}
onChange={handleChange}
/>
<button type="submit">Save</button>
<button>Cancel</button>
</form>
</Popup>
const popoverContent = (
<Popover id="popover-basic">
<Popover.Header as="h3">Popover right</Popover.Header>
<Popover.Body>
And here's some <strong>amazing</strong> content. It's very
engaging. right?
</Popover.Body>
</Popover>
);
const popover = renderToString(
<OverlayTrigger
trigger="click"
placement="right"
overlay={popoverContent}
>
<Button variant="success">Click me to see</Button>
</OverlayTrigger>
);
None of them worked for me.
Probably your approach doesn't work because the dom elements in the tree are created dynamically, and bootstrap doesn't set them up.
A more react-ish way to do it would be using react-bootstrap lib and managing every UI aspect in states. To implement the tooltip, the Overlay component actually as a prop called target that allows you to change over what element the tooltip is shown.
<Overlay target={tooltipTarget} show={showTooltip} placement="right">
{(props) => (
<Tooltip id="overlay-example" {...props}>
{tooltipNode?.data.text_1}
</Tooltip>
)}
</Overlay>
Then you only need to manage all these states in the onNodeMouseEnter and onNodeMouseLeave handlers in the TreevizReact component.
onNodeMouseEnter={(_event, node) => {
const t = document.querySelector(`#node-${node.id}`);
setTooltipTarget(t);
setShowTooltip(true);
setTooltipNode(node);
}}
onNodeMouseLeave={(_event, node) => {
setTooltipTarget(null);
setShowTooltip(false);
setTooltipNode(null);
}}
The popup follows the same logic with another set of states.
<div ref={ref}>
<Overlay
show={!!selectedNode.current}
target={target}
placement="bottom"
container={ref.current}
containerPadding={20}
>
<Popover id="popover-contained">
{/* hack to avoid weird blinking (due mutable state) */}
{!!selectedNode.current && (
<>
some form based on node data
{JSON.stringify(selectedNode.current?.data)}
</>
)}
</Popover>
</Overlay>
</div>
and in onNodeClick handler
onNodeClick={(_event, node) => {
const t = document.querySelector(`#node-${node.id}`);
// unselect if already selected
if (selectedNode.current?.id === node.id) {
selectedNode.current = null;
setTarget(null);
} else {
selectedNode.current = node;
setTarget(t);
}
}}
You might notice this time I used a mutable variable via a ref (selectedNode.current), for some reason using a state caused some issues, maybe due to D3 and react having different rendering cycles, reason why you'll probably encounter some other glitches in this implementation.
https://codesandbox.io/s/quizzical-buck-slofh?file=/src/App.js
I've currently set a task for myself of creating a language selection box, using https://react-combobox.netlify.app/demo as a base and working off of that as I learn React. The code so far I have is returning [object Object] as a result when I select an entry in the combobox.
Code:
import React, { useState } from "react";
import ComboBox from "react-responsive-combo-box";
export default function ComboBoxLang(props) {
const [selectedOption, setSelectedOption] = useState("");
const [highlightedOption, setHighlightedOption] = useState("");
const options = props.dataSet.map ( (inputValue) => {
// const columnWidth = props.columnsWidth.language_name + "px";
const columnWidth = props.columnsWidth;
let keys = Object.keys(columnWidth);
console.log ('keys are' + keys)
const renderedColumns = keys.map( columnName =>{
return(
<div style={{display:"inline-block", width:columnWidth[columnName]}}>{inputValue[columnName]} </div>
);
})
return (
<div key={inputValue.id}>
{renderedColumns}
</div>
)
})
return (
<div>
<h1>React Combo Box</h1>
<p>
The selected option -{" "}
<span style={{ fontWeight: "bold" }}>
{" "}
{selectedOption.length > 0 ? selectedOption : "None"}
</span>
</p>
<p>
The highlighted option -{" "}
<span style={{ fontWeight: "bold" }}>
{" "}
{highlightedOption.length > 0 ? highlightedOption : "None"}
</span>
</p>
<ComboBox
options={options}
placeholder="choose country"
defaultIndex={4}
optionsListMaxHeight={300}
style={{
width: "500px",
margin: "0 auto",
marginTop: "50px"
}}
focusColor="#20C374"
renderOptions={(option) => (
<div className="comboBoxOption">{option}</div>
)}
onSelect={(option) => setSelectedOption(option)}
onChange={(event) => console.log(event.target.value)}
enableAutocomplete
onOptionsChange={(option) => setHighlightedOption(option)}
/>
</div>
);
}
index.js code:
import React from 'react';
import ReactDOM from 'react-dom';
import ComboBoxLang from './combobox_test.js';
const languages = [
{
"id": 1,
"language_name": "Afrikaans",
"language_native_name": "Afrikaanse taal",
"language_full_name": "Afrikaanse taal (Afrikaans)",
"language_code": "af",
"translation_engine_id": 2,
"translation_engine_name": "Google"
},
{
"id": 2,
"language_name": "Albanian",
"language_native_name": "Gjuha shqipe",
"language_full_name": "Gjuha shqipe (Albanian)",
"language_code": "sq",
"translation_engine_id": 2,
"translation_engine_name": "Google"
}
];
class App extends React.Component {
render () {
return (
<div>
<ComboBoxLang dataSet={languages} columnsWidth={{language_name:200,language_native_name:300}}/>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('drop-down-menu')
);
The goal is to effectively have the language, and the native language (or language code at a later date) to display within the combo-box once selected. I've left the 2 "Displayed" and "Selected" boxes so that, once it works, I can understand a bit how it works, and allow other things to interact or reference those items. For now, I am stuck trying to figure out why it spit's out [object Object] and where it is actually in need of correcting.
I am new to Javascript/ReactJs world. I am building a side nav. Below are some part of my code -
import React, {Component} from "react";
import Link from "./Link/Link";
class Sidebar extends Component {
state = {
dashboard: {
name: "Dashboard",
href: "#",
active: true,
svgPath: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6",
children: []
},
team: {
name: "Team",
href: "#",
active: false,
svgPath: "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z",
children: [
{
name: "Overflow",
href: "#",
},
{
name: "Members",
href: "#",
},
{
name: "Calender",
href: "#",
},
{
name: "Settings",
href: "#",
}
]
}
}
navClickHandler = (e, key) => {
console.log(e, key)
}
render() {
let navLinks = [];
for (const item in this.state) {
navLinks.push(<Link key={item} navClicked={(e, item) => this.navClickHandler(e, item)} config={this.state[item]} />)
}
return (
<div className="hidden bg-indigo-700 md:flex md:flex-shrink-0">
<div className="flex flex-col w-64 border-r border-gray-200 pt-5 pb-4 bg-white overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-indigo-600-mark-gray-800-text.svg"
alt="Workflow" />
</div>
<div className="mt-5 flex-grow flex flex-col">
<nav className="flex-1 px-2 space-y-1 bg-white" aria-label="Sidebar">
{navLinks}
</nav>
</div>
</div>
</div>
);
}
}
export default Sidebar;
How can I get the item value inside the navClickedHandler function -
let navLinks = [];
for (const item in this.state) {
navLinks.push(<Link key={item} navClicked={(e, item) => this.navClickHandler(e, item)} config={this.state[item]} />)
}
Actually, I want to use that key(item) to check which of the side-nav is active. It will change the state object based on the click.
console.log in nacClickHandler is giving undefined.
You are not passing item to Link, so you cannot get it as an argument back from the click event.
So change this:
navClicked={(e, item) => this.navClickHandler(e, item)}
to this:
navClicked={e => this.navClickHandler(e, item)}
Or, you can change your <Link/> component to something like this:
const Link = ({navClicked, item}) => (
<div onClick={e => navClicked(e, item)}></div>
);
And pass item as a prop:
<Link key={item} item={item} navClicked={this.navClickHandler} config={this.state[item]} />
The second option is preferable since it allows you to pass the same function reference always, thus avoiding redundant renders (provided that Link is a PureComponent, or a functional component wrapped in memo)
Hello other problems I see is that key={item} will be translated to key=[object Object] therefore your behaviour will fail due to the check key1 !== key2 because the actual value will be [object Object] !=== [object Object] which fails.
An alternative I would suggest to either use some unique data item.id for instance or generate some using a basic package like uuid
Back to your issue, in order to check which item is active I would suggest to you to do this:
<Link key={item.id} .... active={state.selected.id === item.id}/>
I am using mdbreact package to make table for my data. It's having an action button in the last column which opens a modal for editing the data.
Scenario is
I loaded the table with initial data
And then applied some sorting on it
And now I click on the edit button to open the modal for editing the data
Now the sorting gets automatically removed and it's looking really weird that modal is opening and data is changing in the background.
What do I need ?
I don't want data to be changed on the backend. Also, I don't know how to store that sorted data in the state even as I am using mdbreact for the first time.
Here you can check the exact issue I am facing.
File where I am formatting data and adding event and action to each row:
import React from 'react'
import PayRatesTable from './PayRatesTable'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faPencilAlt, faAngleRight, faAngleLeft } from '#fortawesome/free-solid-svg-icons'
import { Button, Row, Col } from 'reactstrap'
const columns =
[
{
label: 'Certificate',
field: 'certificate',
sort: 'asc'
},
{
label: 'Speciality',
field: 'speciality',
sort: 'asc'
},
{
label: 'Pay Rate ($)',
field: 'pay_rate',
sort: 'disabled'
},
{
label: 'Weekend Pay Rate ($)',
field: 'weekend_pay_rate',
sort: 'disabled'
},
{
label: 'Action',
field: 'action',
sort: 'disabled'
}
]
const formatCertSpec = (data, certificates, handleModelClose) => {
var cert = []
data && data.map((item) => (
certificates && certificates.map((certs) => {
if (certs.id == item.certificateId) {
certs.specialities && certs.specialities.map((certSpec) => {
if (item.speciality == certSpec.id) {
cert.push({
certificate: certs.abbreviation,
speciality: certSpec.name,
pay_rate: item.payRateCents ? `$${(item.payRateCents / 100).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}` : '',
weekend_pay_rate: item.weekendPayRateCents ? `$${(item.weekendPayRateCents / 100).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}` : '',
action: <Button color="link" onClick={(event) => {
event.preventDefault()
handleModelClose({
certificate: certs.abbreviation,
speciality: certSpec.name,
id: item.id,
pay_rate: item.payRateCents / 100,
weekend_pay_rate: item.weekendPayRateCents / 100,
})}}>
<FontAwesomeIcon key="edit" className="ml-2" icon={faPencilAlt} />
</Button>
})
}
})
}
})
))
return cert
}
function AddPayRatesComp({
data,
certificates,
handleModelClose,
handleNextPrevTabs
}) {
const certAndSpecPayData = formatCertSpec(data, certificates, handleModelClose)
console.log(certAndSpecPayData)
return (
<div className="container-fluid">
<PayRatesTable
columns={columns}
certificates={certificates}
certs={certAndSpecPayData}
/>
<Row className="mb-2 text-center">
<Col className="col-md-3">
</Col>
<Button
type="button"
onClick={() => handleNextPrevTabs('prev')}
outline
color="secondary"
className="btn-rounded font-weight-bold py-1 mr-2 mt-2 col-sm-12 col-md-3"
><FontAwesomeIcon icon={faAngleLeft} /> Previous</Button>
<Button
type="button"
onClick={() => handleNextPrevTabs('next')}
outline
color="secondary"
disabled
className="btn-rounded font-weight-bold py-1 mr-2 mt-2 col-sm-12 col-md-3"
>Next <FontAwesomeIcon icon={faAngleRight} /></Button>
</Row>
</div>
);
}
export default AddPayRatesComp;
PayRatesTable.js
import React from 'react'
import { MDBDataTable } from 'mdbreact'
const PayRatesTable = ({ columns, certs }) => {
const data = {
columns: columns,
rows: certs
}
return (
<>
<MDBDataTable
striped
bordered
medium="true"
responsive
data={data}
order={['certificate', 'asc' ]}
/>
<style jsx global>{`
#import "../styles/bootstrap-custom/jsx-import";
.table thead:last-child{
display:none;
}
`}</style>
</>
);
}
export default PayRatesTable;
This is all that I can provide due to security issues.