I'm trying to create a tag input typeahead from this library:
import { Typeahead } from 'react-bootstrap-typeahead';
in my reactjs app:
<Typeahead
allowNew
id="custom-selections-example"
multiple
newSelectionPrefix="Add a new item: "
options={opt}
placeholder="Autocomplete"
name="tags"
onChange={onChange}
value={values.options}
/>
the statement console.log(values.options)-> does not return anything when I select one of the options...
Can someone show me a way to get the value?
UPDATE
I have previously tested with onChange function and gives the following error:
TypeError: Cannot read property 'name' of undefined
OnChange Method:
const { values, onChange, onSubmit } = useForm(createPostCallback, {
tags:''
})
const [createData, { error }] = useMutation(CREATE_QUERY, {
variables: values,...code continues
Here is the working code . https://codesandbox.io/s/wonderful-sanderson-eg0g8?file=/src/index.js:0-648. Here I modified the code based on your requirement, now you can call your parent onChange method from the handleChange method.
import React, { useEffect, useRef, useState } from 'react';
import { Typeahead } from 'react-bootstrap-typeahead';
import ReactDOM from 'react-dom';
import options from './data';
import 'react-bootstrap-typeahead/css/Typeahead.css';
import './styles.css';
const TypeaheadExample = () => {
const [selected, setSelected] = useState([]);
const refHidden = useRef(null);
const onChange = (name) => (values) => {
if (values.length > 0) {
const e = new Event('input', { bubbles: true });
setNativeValue(refHidden.current, values[0].capital);
refHidden.current.dispatchEvent(e);
}
};
function setNativeValue(element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(
prototype,
'value'
).set;
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value);
} else {
valueSetter.call(element, value);
}
}
function handleChange(e) {
console.log(e.target.name);
console.log(e.target.value);
}
//{(text, e) => { console.log(text, e); }}
return (
<>
<Typeahead
id="basic-example"
onChange={onChange('basic')}
options={options}
placeholder="Choose a state..."
selected={selected}
/>
<input
ref={refHidden}
style={{ display: 'none' }}
name="control_Name"
onChange={handleChange}
/>
</>
);
};
ReactDOM.render(<TypeaheadExample />, document.getElementById('root'));
Unless I misunderstand what you are doing or there is missing code from your example, you want to set the values selected from that component correct?
Looking through the library's examples, it would seem like you don't have the onChange prop, where ideally you will set that value. So try this:
<Typeahead
allowNew
id="custom-selections-example"
multiple
newSelectionPrefix="Add a new item: "
options={opt}
placeholder="Autocomplete"
name="tags"
onChange={(value) => console.log('SET VALUE HERE!', value)}
value={values.options}
/>
Edit: take note for form related components in general, chances are you want to do anything relating to setting and storing values within the events (so onChange and possible variations of that).
From the documentation:
<Typeahead
allowNew
id="id"
multiple
newSelectionPrefix="Add a new item: "
options={opts}
placeholder="Autocomplete tags"
onChange={(selected) => { values.tags = selected }} />
The typeahead behaves similarly to other form elements. It requires an array of data options to be filtered and displayed.
<Typeahead
onChange={(selected) => {
// Handle selections...
}}
options={[ /* Array of objects or strings */ ]}
/>
Related
I am building a form where the hotel owners will add a hotel and select a few amenities of the same hotel. The problem is If I use state in the onChange function the checkbox tick is not displayed. I don't know where I made a mistake?
import React from "react";
import { nanoid } from "nanoid";
const ListAmenities = ({
amenities,
className,
setHotelAmenities,
hotelAmenities,
}) => {
const handleChange = (e) => {
const inputValue = e.target.dataset.amenitieName;
if (hotelAmenities.includes(inputValue) === true) {
const updatedAmenities = hotelAmenities.filter(
(amenitie) => amenitie !== inputValue
);
setHotelAmenities(updatedAmenities);
} else {
//If I remove this second setState then everything works perfectly.
setHotelAmenities((prevAmenities) => {
return [...prevAmenities, inputValue];
});
}
};
return amenities.map((item) => {
return (
<div className={className} key={nanoid()}>
<input
onChange={handleChange}
className="mr-2"
type="checkbox"
name={item}
id={item}
data-amenitie-name={item}
/>
<label htmlFor={item}>{item}</label>
</div>
);
});
};
export default ListAmenities;
The problem is that you are using key={nanoid()}. Instead, using key={item] should solve your probem.
I believe your application that uses ListAmenities is something like this:
const App = () => {
const [hotelAmenities, setHotelAmenities] = useState([]);
return (
<ListAmenities
amenities={["A", "B", "C"]}
className="test"
setHotelAmenities={setHotelAmenities}
hotelAmenities={hotelAmenities}
/>
);
};
In your current implementation, when handleChange calls setHotelAmenities it changed hotelAmenities which is a prop of ListAmenities and causes the ListAmenities to rerender. Since you use key={nanoid()} react assumes that a new item has been added and the old one has been removed. So it re-renders the checkbox. Since there is no default value of checkbox, it is assumed that it is in unchecked state when it is re-rendered.
I am working on a component where the user searches a term and it is returned to them through a filter. I am using useContext hook to pass data from db via axios. I would like to use the button in the CompSearch component to render the results rather than having them show up on a keystroke. My question is how do I render the results via button click?
Here is the code
Follow these steps to achieve that.
Change the input element into an uncontrolled component.
Get the value using reference in React.
import React, { useContext, useRef, useState } from "react";
import CompanyInfoList from "./CompanyInfoList";
import { CompListContext } from "./CompListContext";
const CompSerach = () => {
const [company, setCompany] = useContext(CompListContext);
const [searchField, setSearchField] = useState("");
const [searchShow, setSearchShow] = useState(false);
const filtered = company.filter((res) => {
return res.company.toLowerCase().includes(searchField.toLowerCase());
});
const inputRef = useRef(null); // 1. Create the ref
const handleClick = () => {
const val = inputRef.current.value; // 3. Get the value
setSearchField(val);
if (val === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
};
const searchList = () => {
if (searchShow) {
return <CompanyInfoList filtered={filtered} />;
}
};
return (
<>
<div>
<em>
NOTE: if you search "ABC" or "EFGH" you will see results - my goal is
to see those results after clicking button
</em>
<br />
<input
type="search"
placeholder="search Company Title"
ref={inputRef} {/* 2. Assign the ref to the Input */}
/>
<button onClick={handleClick}>Enter</button>
</div>
{searchList()}
</>
);
};
export default CompSerach;
https://codesandbox.io/s/show-on-button-click-68157003-rot6o?file=/src/TestTwo/CompSearch.js
Let me know if you need further support.
const handleChange = (e) => {
setSearchField(e.target.value);
if (e.target.value === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
**setCompany(e.target.value);**
};
i think your question is similar with autocomplete.
Using Fluent UI React, I'm displaying some data from an AppSync API in a TextField. I want to be able to show text from the API for a contact form. I then want to edit that text and click a button to post it back to the AppSync API.
If I use the TextField component on its own, I can then use a hook to set a variable to result of an AppSync API call and then have the TextField component read the value coming from the variable I set with the hook. I can then edit that text as I feel like and its fine.
The problem I have is that if I want to take edits to the TextField and set them using my hook I lose focus on the TextField. To do this I am using the onChange property of TextField. I can set the variable fine but I have to keep clicking back in to the input window.
Any thoughts on how I can keep the focus?
import React, { useEffect, useState } from 'react';
import { API, graphqlOperation } from 'aws-amplify';
import * as queries from '../../graphql/queries';
import { Fabric, TextField, Stack } from '#fluentui/react';
const PhoneEntryFromRouter = ({
match: {
params: { phoneBookId },
},
}) => PhoneEntry(phoneBookId);
function PhoneEntry(phoneBookId) {
const [item, setItem] = useState({});
useEffect(() => {
async function fetchData() {
try {
const response = await API.graphql(
graphqlOperation(queries.getPhoneBookEntry, { id: phoneBookId })
);
setItem(response.data.getPhoneBookEntry);
} catch (err) {
console.log(
'Unfortuantely there was an error in getting the data: ' +
JSON.stringify(err)
);
console.log(err);
}
}
fetchData();
}, [phoneBookId]);
const handleChange = (e, value) => {
setItem({ ...item, surname: value });
};
const ContactCard = () => {
return (
<Fabric>
<Stack>
<Stack>
<TextField
label='name'
required
value={item.surname}
onChange={handleChange}
/>
</Stack>
</Stack>
</Fabric>
);
};
if (!item) {
return <div>Sorry, but that log was not found</div>;
}
return (
<div>
<ContactCard />
</div>
);
}
export default PhoneEntryFromRouter;
EDIT
I have changed the handleChange function to make use of prevItem. For this event it does accept the event and a value. If you log that value out it is the current value and seems valid.
Despite the change I am still seeing the loss of focus meaning I can only make a one key stroke edit each time.
setItem((prevItem) => {
return { ...prevItem, surname: e.target.value };
});
};```
I think you want the event.target's value:
const handleChange = e => {
setItem(prevItem => { ...prevItem, surname: e.target.value });
};
You should also notice that in your version of handleChange(), value is undefined (only the event e is being passed as a parameter).
Edit: Now I see that you're setting the value item with data from a fetch response on component mount. Still, the value of item.surname is initially undefined, so I would consider adding a conditional in the value of the <TextField /> component:
value={item.surname || ''}
Introduction
Yesterday I followed an advanced tutorial from Kent C. Dodds where he explained how to connect an input to localstorage which then handles the setting of value, change of values etc and automatically sync with LocalStorage in react.
At the first place this works pretty well for normal components. However, for example the custom checkboxes which I have in my app do not work with the logics. I tried to alter the logics a bit but it seems that I didn't got far with it.
The Problem
Currently my custom checkbox component does not connect / work with the hoc LocalStorageFormControl.
Project info
I have made a CodeSandbox for you to play around with: https://codesandbox.io/s/eager-curie-8sj1x
The project is using standard bootstrap with scss stylings. The CustomCheckbox consists of two elements: the main div and the actual input itself. Currently the matching value in state will trigger className change in one of the elements to allow custom styling.
For any further questions please comment below. Thanks in advance for all the help.
Resources
Kent C. Dodds - Tutorial resource
CodeSandBox Project
The problems were:
The LocalStorageFormControl component didn't update the state when
it gets the initial value from localStorage.
The input didn't update the state onChange as it didn't have onChange
handler.
The CustomCheckboxGroup component didn't have a name prop which is used
as a part of the key in the localStorage
The solution is as following:
App.js
import React, { useEffect, useState } from "react";
// Bootstrap
import { Row, Col, Form } from "react-bootstrap";
import CustomCheckboxGroup from "./CustomCheckboxGroup";
// Function that calls all functions in order to allow the user to provide their own onChange, value etc
const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args));
// Connect any <input /> to LocalStorage and let it manage value / onChange
function LocalStorageFormControl({
children,
formControl = React.Children.only(children),
lsKey = `lsfc:${formControl.props.name}`,
updateInitialState
}) {
const [hasChanged, setHasChanged] = useState(false);
const [value, setValue] = useState(() => {
return (
window.localStorage.getItem(lsKey) || formControl.props.defaultValue || ""
);
});
// Let the user control the value if needed
if (
formControl.props.value !== undefined &&
formControl.props.value !== value
) {
setValue(formControl.props.value);
}
useEffect(() => {
if (hasChanged) {
if (value) {
window.localStorage.setItem(lsKey, value);
} else {
window.localStorage.removeItem(lsKey);
}
} else {
if (value) {
// if hasChanged is false and there is value that means there was a value in localStorage
setHasChanged(true);
// update the state
updateInitialState(value);
}
}
}, [value, lsKey, hasChanged, updateInitialState]);
return React.cloneElement(React.Children.only(children), {
onChange: callAll(formControl.props.onChange, e => {
setHasChanged(true);
setValue(e.target.value);
}),
value,
defaultValue: undefined
});
}
const checkboxes = [
{
label: "Dhr",
name: "aanhef-dhr",
stateName: "salutation",
value: "De heer"
},
{
label: "Mevr",
name: "aanhef-mevr",
stateName: "salutation",
value: "Mevrouw"
}
];
export default function App() {
const [state, setState] = useState({});
function handleSubmit(e) {
e.preventDefault();
console.log("Handling submission of the form");
}
function onChange(e, stateName) {
e.persist();
setState(prevState => ({ ...prevState, [stateName]: e.target.value }));
}
// Log the state to the console
console.log(state);
return (
<Row>
<Col xs={12}>
<Form
id="appointment-form"
onSubmit={handleSubmit}
noValidate
style={{ marginBottom: 75 }}
>
<LocalStorageFormControl
updateInitialState={value => {
setState({ ...state, "test-textfield": value });
}}
>
{/* Add onChange handler to update the state with input value*/}
<input
type="text"
name="test-textfield"
onChange={e => {
setState({ ...state, "test-textfield": e.target.value });
}}
/>
</LocalStorageFormControl>
<LocalStorageFormControl
updateInitialState={value => {
setState({ ...state, salutation: value });
}}
>
<CustomCheckboxGroup
checkboxes={checkboxes}
key="salutation"
label="Salutation"
name="salutation"
onChange={(e, stateName) => onChange(e, stateName)}
required={true}
value={state.salutation}
/>
</LocalStorageFormControl>
</Form>
</Col>
</Row>
);
}
CustomCheckboxGroup.js
import React from "react";
// Bootstrap
import { Form, Row, Col } from "react-bootstrap";
export default ({ onChange, value, name, label, className, checkboxes }) => (
<Row>
<Col xs={12}>
<Form.Label>{label}</Form.Label>
</Col>
<Col>
<Form.Group className="d-flex flex-direction-column">
{checkboxes.map((checkbox, key) => {
return (
<div
key={key}
className={
checkbox.value === value
? "appointment_checkbox active mr-2 custom-control custom-checkbox"
: "appointment_checkbox mr-2 custom-control custom-checkbox"
}
>
<input
name={name}
type="checkbox"
value={checkbox.value}
onChange={e => onChange(e, checkbox.stateName)}
checked={value === checkbox.value}
id={"checkbox-" + checkbox.name}
className="custom-control-input"
/>
<label
className="custom-control-label"
htmlFor={"checkbox-" + checkbox.name}
>
{checkbox.label}
</label>
</div>
);
})}
</Form.Group>
</Col>
</Row>
);
I have some advice about your code:
Use radio buttons instead of checkboxes if you allow the user to choose one option only. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio
You can persist the whole state object if you would like to by replacing this:
const [state, setState] = useState({});
with this:
// Get the saved state in local storage if it exists or use an empty object
// You must use JSON.parse to convert the string back to a javascript object
const initialState = localStorage.getItem("form-state")
? JSON.parse(localStorage.getItem("form-state"))
: {};
// Initialize the state with initialState
const [state, setState] = useState(initialState);
// Whenever the state changes save it to local storage
// Notice that local storage accepts only strings so you have to use JSON.stringify
useEffect(() => {
localStorage.setItem("form-state", JSON.stringify(state));
}, [state]);
I want to change state value with change in the 'select' element. So I am calling the setFilter method in the onChange handler. But state is not getting updated. It's holding the previous value.
How to fix this issue?
I want to change state value with change in the 'select' element. So I am calling the setFilter method in the onChange handler. But state is not getting updated. It's holding the previous value.
How to fix this issue?
import React, { useState, useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import { useApolloClient} from 'react-apollo';
import { Formik, Form, ErrorMessage } from 'formik';
import * as Yup from "yup";
import AsyncPaginate from 'react-select-async-paginate'
import { GET_ITEM_CODES } from '../../library/Query';
export default function SampleForm({initialData}){
const history = useHistory();
const [productFilter, setProductFilter] = useState('');
const client = useApolloClient();
const defaultAdditional = {
cursor : null
}
const shouldLoadMore = (scrollHeight, clientHeight, scrollTop) => {
const bottomBorder = (scrollHeight - clientHeight) / 2
return bottomBorder < scrollTop
}
const loadItemcodeOptions = async (q = 0, prevOptions, {cursor}) => {
console.log('qu',q*1)
const options = [];
console.log('load')
const response = await client.query({
query:GET_ITEM_CODES,
variables : {filter: {
number_gte : q*1
},skip:0, first:4, after: cursor}
})
console.log('res',response)
response.data.itemCodes.itemCodes.map(item => {
return options.push({
value: item.number,
label: `${item.number} ${item.description}`
})
})
console.log('0',options)
return {
options,
hasMore: response.data.itemCodes.hasMore,
additional: {
cursor: response.data.itemCodes.cursor.toString()
}
}
}
const handleFilter = (e) => {
console.log('e',e)
setProductFilter(e.value)
console.log('pf',productFilter) // output is previous State(wrong)
}
useEffect(() => {
console.log('epf',productFilter) // output the current state(expected)
})
return(
<Formik
initialValues = {{
itemCode: !!initialData ? {value: initialData.itemCode, label: initialData.itemCode} : '',
}}
validationSchema = {Yup.object().shape({
itemCode: Yup.number().required('Required'),
})}
>
{({values, isSubmitting, setFieldValue, touched, errors }) => (
<Form>
<label htmlFor="itemCode">Item Code</label>
<AsyncPaginate
name="itemCode"
defaultOptions
debounceTimeout={300}
cacheOptions
additional={defaultAdditional}
value={values.itemCode}
loadOptions={loadItemcodeOptions}
onChange={option => {
handleFilter(option)
setFieldValue('itemCode', option)
}}
shouldLoadMore={shouldLoadMore}
/>
<ErrorMessage name="itemcode"/>
<pre>{JSON.stringify(values, null, 2)}</pre>
</Form>
)}
</Formik>
)
}
Actually setProductFilter is setting state asynchronously, so you'll get updated state in the effect, not right after calling setState. But your effect is going to run every time when your component gets re-rendered so you should add productFilter as a dependency of useEffect.
One other thing I want to mention is, I don't know about your use case but you should stick to the rule: Single source of truth. You have two states for productFilter, one is in Formik, i.e. itemCode, and other in your local state. I think you can remove your local state and use item code from formikProps.values.itemCode.