React HeadlessUI ComboBox (autocomplete) - How to pass onClick action - javascript

I want to replace a text input with a Combobox from https://headlessui.dev/react/combobox
Now the Combobox internal mechanism is working, but I'm not able to filter my results with the selected item.
This was the initial search input:
const SearchBar = ({ journeys, setSearchResults }) => {
const handleSubmit = (e) => e.preventDefault();
const handleSearchChange = (e) => {
if (!e.target.value) return setSearchResults(journeys);
const resultsArray = journeys.filter(
(journey) =>
// journey.arr.toLowerCase().includes(e.target.value) ||
journey.dep.toLowerCase().includes(e.target.value)
);
setSearchResults(resultsArray);
};
return (
<header>
<form className="p-4" onSubmit={handleSubmit}>
<input
className="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
type="text"
id="search"
onChange={handleSearchChange}
/>
</form>
</header>
);
};
export default SearchBar;
and here is the new Combobox:
import { useState } from 'react';
import { Combobox } from '#headlessui/react';
import { MapPinIcon } from '#heroicons/react/24/outline';
import places from '../data/places';
const DepartureBox = ({ journeys, setSearchResults }) => {
const handleSubmit = (e) => e.preventDefault();
const handleSearchChange = (e) => {
if (!e.target.value) return setSearchResults(journeys);
const resultsArray = journeys.filter(
(journey) =>
// journey.arr.toLowerCase().includes(e.target.value) ||
journey.dep.toLowerCase().includes(e.target.value)
);
setSearchResults(resultsArray);
};
const [query, setQuery] = useState('')
const filteredPlaces = query
? places.filter((place) => place.name.toLowerCase().includes(query.toLowerCase()))
: []
return (
// <header>
// <form className="p-4" onSubmit={handleSubmit}>
// <input
// className="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
// type="text"
// id="search"
// onChange={handleSearchChange}
// />
// </form>
// </header>
<div className="m-4">
<Combobox
onChange={(place) => {
// TODO: onChange={handleSearchChange}
handleSearchChange
}}
as="div"
className="relative max-w-xl rounded-md ring-1 ring-black/5 divide-y divide-gray-100 overflow-hidden"
>
<div className='flex items-center px-2'>
<MapPinIcon className="h-6 w-6 text-gray-500" />
<Combobox.Input
onChange={(event) => {
setQuery(event.target.value)
}}
className="w-full h-10 bg-transparent border-0 focus:ring-0 text-sm text-gray-800 placeholder:text-gray-400"
placeholder='Select Departure Place...'
/>
</div>
{filteredPlaces.length > 0 && (
<Combobox.Options className="py-4 text-sm max-h-40 overflow-y-auto">
{filteredPlaces.map((place) => (
<Combobox.Option key={place.id} value={place}>
{({ active }) => (
<div className={`space-x-1 px-4 py-2 ${active ? 'bg-indigo-600' : 'bg-white'}`}>
<span className={`font-medium ${active ? 'text-white' : 'text-gray-900'}`}>{place.name}</span>
<span className={`font-medium ${active ? 'text-indigo-200' : 'text-gray-400'}`}>in {place.id}</span>
</div>
)}
</Combobox.Option>
))}
</Combobox.Options>
)}
{
query && filteredPlaces && filteredPlaces.length === 0 && (
<p className='p-4 text-sm text-gray-500'>No places found.</p>
)
}
</Combobox>
</div>
);
};
export default DepartureBox;
For sure I'm missing something at the onChange={(place) => level, but as this is my second react component, I'm lost at that point. Any help would be very appreciated.
Here is the codesandbox: https://codesandbox.io/p/github/sinyayadynya/roadbook/draft/codesandbox

Related

Make div with react components as children disappear if I click outside of it

I want to make a form that disappears if I click outside of it.
Form Component:
const CreateTaskPopup = (props) => {
const ref = query(collection(db, "tasks"));
const mutation = useFirestoreCollectionMutation(ref);
useEffect(() => {
const closeTaskPopup = (event) => {
if (event.target.id != "addForm") {
props.setTrigger(false)
}
}
document.addEventListener('click', closeTaskPopup);
return () => document.removeEventListener('click', closeTaskPopup)
}, [])
return (props.trigger) ? (
<>
<div className="bg-darkishGrey my-4 p-1 mx-3 ml-16 cursor-pointer block"
id="addForm">
<div className="flex justify-between">
<div className="flex break-all items-center ">
<Image src={fileIcon} className="w-6 mx-2"/>
<div>
<Formik
initialValues={{
taskName: "",
taskIsDone: false,
parentId: props.parentId ? props.parentId : "",
hasChildren: false,
}}
onSubmit={(values) => {
mutation.mutate(values);
props.setTrigger(false);
}}
>
<Form>
<div className="">
<TextInput
placeholder="Type a name"
name="taskName"
type="text"
/>
</div>
</Form>
</Formik>
</div>
</div>
<div className="flex items-center">
</div>
</div>
</div>
</>
) : null
}
export default CreateTaskPopup
Text Input Component:
import { useField } from "formik";
const TextInput = ({ label, ...props}) => {
const [field, meta] = useField(props);
return (
<div>
<label id="addForm" className="text-lightestGrey text-xl block"
htmlFor={props.id || props.name}>
{label}
</label>
<input id="addForm" className="bg-darkishGrey text-xl text-almostWhite my-2
outline-none w-10/12 rounded-sm p-1 mx-3" {...field} {...props} />
{meta.touched && meta.error ? <div>{meta.error}</div>: null}
</div>
);
};
export default TextInput;
I tried giving an id to the elements inside it but it's not the best solution as it has components from the Formik library to which I can't assign an id. I don't know what would be the best solution for this problem.

How can I do to clear my input when I click on the button?

I am trying on a code using React with an input and a button. When I click on a button I would like to clear the input or it is not the case. Here is my code :
import { useState } from "react";
import "./styles.css";
const App = () => {
const [finalValue, setFinalValue] = useState(true);
const changevalue = () => {
setFinalValue(!finalValue);
};
return (
<div className="App">
{finalValue ? (
<input
type="text"
placeholder=" E-mail"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
) : (
<input
type="text"
placeholder=" Pseudo"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
)}
<button onClick={() => changevalue()}>Change input</button>
</div>
);
};
export default App;
Here is my code : https://codesandbox.io/s/wandering-fire-hj8d84?file=/src/App.js:0-823
Could you help me please ?
Thank you very much !
NB : I tried to use value but I was not able to type on the input and I also tried to use defaultValue without any success.
You have to use useRef I have implemented this below,
First, you have to import useRef Hook and then make a constant one
const one = useRef("") then in the input tag you have to add ref={one}.
Then in the changevalue function you have to write one.current.value = "";
import { useState ,useRef } from "react";
import "./styles.css";
const App = () => {
const [finalValue, setFinalValue] = useState(true);
const one = useRef("");
const changevalue = () => {
setFinalValue(!finalValue);
one.current.value = "";
};
return (
<div className="App">
{finalValue ? (
<input ref={one}
type="text"
placeholder=" E-mail"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
) : (
<input type="text" ref={one}
placeholder=" Pseudo"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
)}
<button onClick={changevalue}>Change input</button>
</div>
);
};
export default App;
Try this one
import { useState, useRef } from "react";
import "./styles.css";
const App = () => {
const [finalValue, setFinalValue] = useState(true);
const emailRef = useRef();
const changevalue = () => {
setFinalValue(!finalValue);
emailRef.current.value = "";
};
return (
<div className="App">
{finalValue ? (
<input
type="text"
ref={emailRef}
placeholder=" E-mail"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
) : (
<input
type="text"
ref={emailRef}
placeholder=" Pseudo"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
)}
<button onClick={() => changevalue()}>Change input</button>
</div>
);
};
export default App;
First of all you could use const [inputValue, setInputValue] = useState(''); to use/change/clear input value;
Then just set value and onChange function into input tag (event.target.value will be your input string value). And with you button just set inputValue to default '';
Hope this help

How to return multiple function in react in JSX format

I'm trying to return a few functions in react but I'm unable to perform as I wanted.
return (
loadingMessage(),
errorMessage(),
loginForm(),
performRedirect()
)
}
I want to return my functions as above but when try this my app directly calls the last function performRedirect(). I don't know whether am I running this correctly or not.
please find the whole code below.
import React, { useState } from "react";
import { Link, Navigate } from "react-router-dom";
import {signin, authenticate, isAutheticated} from "../auth/helper/index"
const Login = () => {
const [values, setValues] = useState({
username: "",
password: "",
error: "",
loading: false,
didRedirect: false,
});
const {username, password, error, loading, didRedirect} = values;
const {user} = isAutheticated();
const handleChange = name => event => {
setValues({ ...values, error: false, [name]: event.target.value });
};
const onSubmit = event => {
event.preventDefault();
setValues({ ...values, error: false, loading: true });
signin({ username, password })
.then(data => {
if (data.error) {
setValues({ ...values, error: data.error, loading: false });
} else {
authenticate(data, () => {
setValues({
...values,
didRedirect: true
});
});
}
})
.catch(console.log("signin request failed", error, user));
};
const performRedirect = () => {
//TODO: do a redirect here
if (didRedirect) {
if (user && user.role === 1) {
return <p>redirect to admin</p>;
} else {
return <p>redirect to user dashboard</p>;
}
}
if (isAutheticated()) {
return <Navigate to="/" />;
}
};
const loadingMessage = () => {
return (
loading && (
<div className="alert alert-info">
<h2>Loading...</h2>
</div>
)
);
};
const errorMessage = () => {
return (
<div className="row">
<div className="col-md-6 offset-sm-3 text-left">
<div
className="alert alert-danger"
style={{ display: error ? "" : "none" }}
>
{error}
</div>
</div>
</div>
);
};
const loginForm = () => {
return (
<div className='bg-gray-200'>
<div className="flex items-center h-screen w-full">
<div className="w-80 bg-white rounded-2xl p-6 m-0 md:max-w-sm md:mx-auto border border-slate-300 shadow-sm">
<div align='center' className='mt-3 mb-3 items-center content-center'> <img src={require('./../data/logo.jpg')} width="120px"/></div>
<span className="block w-full text-xl uppercase font-bold mb-4 text-center">Sign in to EMS
</span>
<form className="mb-0" action="/" method="post">
<div className="mb-4 md:w-full">
<label for="email" className="block text-xs mb-1 text-left text-gray-500">Username</label>
<input onChange={handleChange("username")} className="bg-gray-100 w-full border rounded-2xl px-4 py-2 outline-none focus:shadow-outline text-left text-xs" type="text" name="username" id="username" placeholder="Username" value={username}/>
</div>
<div className="mb-6 md:w-full relative">
<div className='flex w-full'>
<label for="password" className="block text-xs mb-1 text-center text-gray-500">Password</label>
<a className="text-xs text-right text-[#58a6ff] absolute right-0" href="/login">Forgot password?</a></div>
<input onChange={handleChange("password")} className="bg-gray-100 w-full border rounded-2xl px-4 py-2 outline-none focus:shadow-outline text-left text-xs" type="password" name="password" id="password" placeholder="Password" value={password}/>
</div>
<div className="mb-6 md:w-full relative">
<div className='flex w-full'>
<p className="block text-xs mb-1 text-center text-gray-500">{JSON.stringify(values)}</p>
</div>
<button className="bg-green-500 hover:bg-green-700 shadow-lg text-white uppercase text-sm font-semibold px-4 py-2 rounded-2xl text-center items-center w-full" onClick={onSubmit}>Login</button>
</form>
</div>
</div>
</div>
);
};
return (
loadingMessage(),
errorMessage(),
loginForm(),
performRedirect()
)
}
export default Login
Please someone help me on this?
You can modify your return statement with an array value [] like below
return [
loadingMessage(),
errorMessage(),
loginForm(),
performRedirect()
]
Another way, you can render those JSX functions by {} and wrap them into <React.Fragment></React.Fragment> (Or simpler version <></>)
return (
<React.Fragment>
{loadingMessage()}
{errorMessage()}
{loginForm()}
{performRedirect()}
</React.Fragment>)
like this:
return (
<>
{loadingMessage()}
{errorMessage()}
{loginForm()}
{performRedirect()}
</>
)

How to make Dropdown in ReactJs from Query and get the value

Sorry if I ask again, I try to make dropdown from array in my database then use the dropdown to take the value I want looping the array to the dropdown in here but still not showed
//tipe akademik
//define state
const [postsTipe, setPostsTipe] = useState([]);
//useEffect hook
useEffect(() => {
//panggil method "fetchData"
fectDataTipe();
}, []);
//function "fetchData"
const fectDataTipe = async () => {
//fetching
const responseTipe = await axios.get('http://localhost:3000/api/tipe_akademik');
//get response data
const dataTipe = await responseTipe.data.data;
//assign response data to state "posts"
setPostsTipe(dataTipe);
}
const Dropdown = () => {
// dropdown props
const [dropdownPopoverShow, setDropdownPopoverShow] = React.useState(false);
const btnDropdownRef = React.createRef();
const popoverDropdownRef = React.createRef();
const openDropdownPopover = () => {
createPopper(btnDropdownRef.current, popoverDropdownRef.current, {
placement: "bottom-start",
});
setDropdownPopoverShow(true);
};
const closeDropdownPopover = () => {
setDropdownPopoverShow(false);
};
return (
<>
<a
className="text-blueGray-500 block"
href="#pablo"
ref={btnDropdownRef}
onClick={(e) => {
e.preventDefault();
dropdownPopoverShow ? closeDropdownPopover() : openDropdownPopover();
}} >
<div class="relative inline-block text-left">
<div>
<button type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500" id="menu-button" aria-expanded="true" aria-haspopup="true">
Tipe Akademik
</button>
</div>
</div>
</a>
<div
ref={popoverDropdownRef}
className={
(dropdownPopoverShow ? "block " : "hidden ") +
"bg-white text-base z-50 float-left py-2 list-none text-left rounded shadow-lg min-w-48"
}
>
{postsTipe.map((kategori)=> {
return(<>
<a
href="#pablo"
className={
"text-sm py-2 px-4 font-normal block w-full whitespace-nowrap bg-transparent text-blueGray-700"
}
onClick={(e) => e.preventDefault()}
>
{kategori.tipe_akademik}
</a> /</> )})}
</div>
</>
);
};
export default Dropdown;
Then I call it to my input form with import from dropdown and I want take the value in form and its still wrong. I dont know how to take the the value from import the dropdown named TipeDropdown
import TipeDropdown from "components/Dropdowns/Dropdown";
method "storePost"
const storePost = async (e) => {
e.preventDefault();
//send data to server
await axios.post('http://localhost:3000/api/akademik_a/store', {
tipeA: TipeA,
})
.then(() => {
//redirect
history.push('/admin/dafgur');
})
.catch((error) => {
//assign validation on state
setValidation(error.response.data);
})
};
export default function InputAkademik({ color }) {
return (
<>
<form onSubmit={ storePost }
<input className="border-0 px-3 py-3 placeholder-blueGray-300 text-blueGray-600 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full ease-linear transition-all duration-150"
placeholder="" />
<div className="field mt-5">
<label className="label">Tipe Akademik</label>
<div className="controls">
<TipeDropdown value={TipeA} onChange={(e) => setTipe(e.target.value)}/>
<input type="text" className="border-0 px-3 py-3 placeholder-blueGray-300 text-blueGray-600 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full ease-linear transition-all duration-150" placeholder="" value={TipeInput} onChange={(e) => TipeInput(e.target.value)} />
</div>
</div> </div>
</>
);
};

The placeholder is not showing up at first instance because of i put value={newTodo} and i know that in first instance "newTodo" is empty. How to fix?

The "newTodo" state variable is empty in the first instance when i load the page, that's why the placeholder is not displaying, it act as if there is zero value in newTodo variable so placeholder is not showing up in the first instance, but after i enter a text then placeholder shows up.
import React, {useState} from 'react'
const ToDo = () => {
const [todo, setTodo] = useState([]);
const [newTodo, setNewTodo] = useState(" ");
let globalID = 0;
const handleSubmit = (e) => {
e.preventDefault();
setNewTodo("");
setTodo((oldTodo) => {
return [...oldTodo, newTodo]
});
}
const handleInput = (e) => {
setNewTodo(e.target.value);
}
return (
<>
<h1 className="header">Building To Do App</h1>
<section className='flex justify-center mt-8'>
<div className='border border-indigo-800 w-1/2 flex flex-col text-center h-96'>
<h2 className=' h-16 flex justify-center items-center bg-pink-600 text-white' >To-Do List</h2>
<form onSubmit={handleSubmit} className="mt-6">
<input placeholder='Add a Item' type="text" onChange={handleInput} name="todo" id="todo" value={newTodo} className='w-1/2 h-12 rounded-sm border-b-4 border-indigo-500 text-xl bg-gray-300 focus:outline-none text-black' />
<button className='bg-pink-600 rounded-full w-12 h-12 mx-4 hover:bg-green-700'>+</button>
</form>
<ol className='todoList'>
{todo.map((items) => {
return <li key={globalID++} >{items}</li>
} )}
</ol>
</div>
</section>
</>
)
}
export default ToDo
You set the default value newTodo value " " that's issue !
const [newTodo, setNewTodo] = useState();
Remove " " from useState(); solve the issue !

Categories