autocomplete not updating options but new array is available in hits property - javascript

import { Highlight, connectAutoComplete } from 'react-instantsearch-dom';
import AutoSuggest from 'react-autosuggest';
import Autocomplete, { createFilterOptions } from '#material-ui/lab/Autocomplete';
class AutoCompleteuix extends Component {
static propTypes = {
hits: PropTypes.arrayOf(PropTypes.object).isRequired,
currentRefinement: PropTypes.string.isRequired, };
state = {
value: this.props.currentRefinement,
};
onChange = (_, { newValue }) => {
if (!newValue) {
this.props.onSuggestionCleared();
}
this.setState({
value: newValue,
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.props.refine(value);
};
onSuggestionsClearRequested = () => {
this.props.refine();
};
render() {
const { hits, onSuggestionSelected } = this.props;
const { value } = this.state;
const inputProps = {
onChange: this.onChange,
};
return (
<Autocomplete
options={hits}
getOptionLabel={(hits) => hits.ROMAN}
onInputChange = {(ev,obj,val) => this.props.refine(obj) }
renderInput={(params) => <TextField {...params} label="search for a Word" variant="outlined"/> }
value
/>
);
}
}
export default connectAutoComplete(AutoCompleteuix);
hits has the options with are being passed into options but my problem is the dropdown is not updated as per the latest value but the hits have all the updated values.
im tryign to use the algolia search which give the values of hit coming from another component

Related

Material UI Textfield 'error' and 'helper text' for elements in a loop

I am trying to create an application that has dynamic text field input using MUI textfield. There are two fields - From and To. When the "Add New Field" button is clicked, it generates two new fields. These two are part of the state object. Now, if the user enters a value in "To" field which is lesser than "from" field, it's supposed to display an error below the field as defined in the 'helper text'. However, in my case, the error appears in all the 'To' fields even though the error is supposed to appear only in the row where the input is wrong. It's repeating it in all the rows. How do I fix this?
The code is as follows. It can be reproduced in sandbox directly.
import "./styles.css";
import React from "react";
import { Button, Grid, Paper } from "#mui/material";
import { TextField } from "#mui/material";
interface Props {}
interface State {
serialInputObjects: any;
}
var fromErrorMessage = "";
var toErrorMessage = "";
class SerialQRScanClass extends React.PureComponent<Props, State> {
state = {
serialRegistrationTracker: [],
serialInputObjects: {
//0: { from: "", to: "", except: "" } }
}
};
calculation = (key) => {
let errors = this.getFromToSerialErrorMessages(
this.state.serialInputObjects[key]["from"],
this.state.serialInputObjects[key]["to"]
);
fromErrorMessage = errors.fromErrorMessage;
toErrorMessage = errors.toErrorMessage;
console.log(`Key ${key} From error message - ` + fromErrorMessage);
console.log("To error message - " + toErrorMessage);
};
getSerialCodeErrorMessage = (serialCode) => {
if (!serialCode) return "";
if (String(serialCode).match(/[^0-9,]+/)) {
return "Enter only numbers";
}
return "";
};
getFromToSerialErrorMessages = (fromSerial, toSerial) => {
const fromErrorMessage = this.getSerialCodeErrorMessage(fromSerial);
let toErrorMessage = this.getSerialCodeErrorMessage(toSerial);
if (!fromErrorMessage && !toErrorMessage) {
const diff = parseInt(toSerial) - parseInt(fromSerial);
if (diff < 0) toErrorMessage = "To lower than starting point";
}
return { fromErrorMessage, toErrorMessage };
};
handleAdd = () => {
const objectLength = Object.keys(this.state.serialInputObjects).length;
console.log(objectLength);
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[objectLength]: { from: "", to: "", except: "" }
}
}));
console.log(this.state.serialInputObjects);
};
handleChangeFromSerials = (key: any, data: string) => {
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[key]: { ...prevState.serialInputObjects[key], from: data }
}
}));
console.log(this.state.serialInputObjects);
this.calculation(key);
};
handleChangeToSerials = (key: any, data: string) => {
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[key]: { ...prevState.serialInputObjects[key], to: data }
}
}));
console.log(this.state.serialInputObjects);
this.calculation(key);
};
render() {
return (
<Paper elevation={3} className="abc">
<Button onClick={this.handleAdd}>ADD NEW FIELD</Button>
{Object.keys(this.state.serialInputObjects).map((key) => (
<div key={key}>
<Grid container alignItems="flex-end">
<Grid item className="bcd">
<TextField
fullWidth
label={"FROM"}
placeholder={"Ex.100"}
value={this.state.serialInputObjects[key]["from"]}
onChange={(e) =>
this.handleChangeFromSerials(key, e.target.value)
}
error={Boolean(fromErrorMessage) || false}
helperText={fromErrorMessage}
margin="none"
size="small"
/>
</Grid>
<Grid item className="bcd">
<TextField
fullWidth
label={"To"}
placeholder={"Ex.100"}
value={this.state.serialInputObjects[key]["to"]}
onChange={(e) =>
this.handleChangeToSerials(key, e.target.value)
}
error={Boolean(toErrorMessage) || false}
helperText={toErrorMessage}
margin="none"
size="small"
/>
</Grid>
</Grid>
</div>
))}
</Paper>
);
}
}
export default function App() {
return (
<div className="App">
<SerialQRScanClass />
</div>
);
}
I want to be able to print the error only in that corresponding field in the loop.
We can do this by tracking all the errors for every individual input. First update the state with a fromErrorMessage and a toErrorMessage object. These will hold the errors for the inputs.
state = {
serialRegistrationTracker: [],
serialInputObjects: {
//0: { from: "", to: "", except: "" } }
},
fromErrorMessage: {},
toErrorMessage: {},
};
Then we can update the calculation function to store the errors for the specific input. I added 2 new arguments fromValue and toValue these will help with checking the up-to-date value and prevent the error messages to be one state behind.
calculation = (key, fromValue, toValue) => {
let errors = this.getFromToSerialErrorMessages(fromValue, toValue);
this.setState((prevState) => ({
...prevState,
fromErrorMessage: {
...prevState.fromErrorMessage,
[key]: errors.fromErrorMessage,
},
toErrorMessage: {
...prevState.toErrorMessage,
[key]: errors.toErrorMessage,
},
}));
console.log(
`Key ${key} From error message - ` + this.state.fromErrorMessage[key]
);
console.log("To error message - " + this.state.toErrorMessage[key]);
};
Now we need to update the handlers to work with the new calculation function. We pass the current state from the to and the new data to check against and vice versa.
handleChangeFromSerials = (key: any, data: string) => {
...
this.calculation(key, data, this.state.serialInputObjects[key]["to"]);
};
handleChangeToSerials = (key: any, data: string) => {
...
this.calculation(key, this.state.serialInputObjects[key]["from"], data);
};
Finally update the TextField components. And the same for the to input.
<TextField
...
error={Boolean(this.state.fromErrorMessage[key]) || false}
helperText={this.state.fromErrorMessage[key]}
...
/>

How to filter from Observable Map in React?

I have an React Component Which Consists Dropdown , the dropdown has 4 values 'requsted,all,taken,available' and initially all the List of item are loaded. Each Item has isRequested,isTaken and isAvailable. Now, I need to filter those arrays accordingly to dropdown but i am getting something else as an output
My React Component is:
import React, { useContext, useEffect, SyntheticEvent, useState } from 'react'
import { observer } from 'mobx-react-lite';
import { Dropdown, Segment, Item, Icon, Label, Button, Select } from 'semantic-ui-react';
import { BooksStatus } from '../../app/common/options/BookStatus';
import { RootStoreContext } from '../../app/stores/rootStore';
import { format } from 'date-fns';
import { NavLink } from 'react-router-dom';
import { id } from 'date-fns/esm/locale';
const LibrarianManager: React.FC = () => {
const rootStore = useContext(RootStoreContext);
const { loadBooks, getAvailableBooks, deleteBook } = rootStore.bookStore;
const [status, setStatus] = useState(BooksStatus[0].value);
useEffect(() => {
loadBooks();
}, [loadBooks]);
const onChange = (value: any) => {
setStatus(value)
console.log(value)
if (value === 'requested') {
if (value === 'requested') {
getAvailableBooks.filter(data => data.isRequested == true)
console.log(getAvailableBooks)
}
}
}
return (
<div>
<Select
value={status}
onChange={(e, data) => onChange(data.value)}
options={BooksStatus}
/>
{getAvailableBooks.map(books => (
<Segment.Group key={books.bookName}>
<Segment>
<Item.Group>
<Item>
<Item.Image size='tiny' circular src='/assets/books.jpg' />
<Item.Content>
<Item.Header as={NavLink} to={`/booksDetail/${books.id}`} >{books.bookName}</Item.Header>
</Item.Content>
</Item>
</Item.Group>
</Segment>
<Segment clearing>
<span></span>
</Segment>
</Segment.Group>
))}
</div>
)
}
export default observer(LibrarianManager);
My Map Functions is :-
#computed get getAvailableBooks() {
return Array.from(this.bookRegistry.values());
}
#action loadBooks = async () => {
this.loadingInitial = true;
try {
const books = await agent.Books.list();
runInAction("loading books", () => {
books.forEach((books) => {
books.issuedOn = new Date(books.issuedOn);
this.bookRegistry.set(books.id, books);
});
this.loadingInitial = false;
});
} catch (error) {
runInAction("load books error", () => {
this.loadingInitial = false;
});
}
};
and my data Model or the item is
export interface IBooks {
id: number;
bookname: string;
issuedOn: Date;
returnDate: Date;
isReturned: boolean;
isRequested: boolean;
isAvailable: boolean;
isTaken: boolean;
name: string;
requestedBy: string;
}
while trying by this method
if (value === 'requested') {
if (value === 'requested') {
getAvailableBooks.filter(data => data.isRequested == true)
console.log(getAvailableBooks)
}
in console i am getting all the list without filtering
Array.filter doesn't mutate the original array but returns a new one which is nice (MDN).
What you want is:
const filteredBooks = getAvailableBooks.filter(data => data.isRequested == true)
console.log(filteredBooks)

Console.log() after setState() doesn't return the updated state

I've created a simple todo list to learn react and i'm trying to add some additional features. At the moment i'm trying to add buttons that toggle the list of items, so it either shows all the tasks or just those that are completed.
I've written a function to change the state of my visabilityFilter so I can later use this to toggle the items in the list, but it isn't behaving how it should be.
I console log the visabilityFilter variable but it always shows the wrong state before changing to the correct state. e.g. the 'show all' button will console log 'show completed' then if you press it again it will console log 'show all'
App.js
import React, { Component } from 'react';
import './App.css';
import TodoList from './components/TodoList.js'
import VisabilityFilter from './components/VisabilityFilter.js'
export const SHOW_ALL = 'show_all'
export const SHOW_COMPLETED = 'show_completed'
class App extends Component {
constructor (props) {
super(props)
this.state = {
inputValues: {
'newTodo': ''
},
todos: [
{
task: 'My First Todo',
completed: false
}
],
visabilityFilter: SHOW_ALL
}
this.addTodo = this.addTodo.bind(this)
this.handleInputChange = this.handleInputChange.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
this.toggleCompleted = this.toggleCompleted.bind(this)
this.removeTodo = this.removeTodo.bind(this)
this.checkCompleted = this.checkCompleted.bind(this)
this.setVisabilityFilter = this.setVisabilityFilter.bind(this)
}
handleInputChange (e) {
const { inputValues } = this.state
const { id, value } = e.target
this.setState({
inputValues: {
...inputValues,
[id]: value
}
})
}
handleKeyUp (e) {
var code = e.key
if(code === 'Enter') {
this.addTodo(e);
}
}
toggleCompleted (e, index) {
const { todos } = this.state
todos[index].completed = !todos[index].completed
todos.sort((a, b) => b.completed - a.completed)
this.setState({ todos })
}
removeTodo (e, index) {
const { todos } = this.state
this.setState ({ todos: todos.filter((todo, i) => i !== index) })
}
addTodo (e) {
const { todos, inputValues } = this.state
const { dataset } = e.target
if (inputValues[dataset.for] === '') return
const newTodo = { task: inputValues[dataset.for], completed: false }
todos.push(newTodo)
this.setState({
todos,
inputValues: { ...inputValues, [dataset.for]: '' }
})
}
checkCompleted (e, index) {
const { todos } = this.state
return { todos } && todos[index].completed
}
setVisabilityFilter (e) {
const { visabilityFilter } = this.state
const { dataset } = e.target
this.setState({
visabilityFilter: dataset.for
})
console.log ({ visabilityFilter })
}
render() {
const { todos, inputValues, visabilityFilter } = this.state
return (
<div className="App">
<TodoList
todos={todos}
inputValues={inputValues}
addTodo={this.addTodo}
handleInputChange={this.handleInputChange}
removeTodo={this.removeTodo}
toggleCompleted={this.toggleCompleted}
handleKeyUp={this.handleKeyUp}
checkCompleted={this.checkCompleted}
/>
<VisabilityFilter setVisabilityFilter={this.setVisabilityFilter} />
</div>
);
}
}
export default App;
VisabilityFilter.js
import React from 'react'
import { func } from 'prop-types'
import { SHOW_ALL, SHOW_COMPLETED } from '../App'
const VisabilityFilter = props => {
return (
<div>
<button data-for={SHOW_COMPLETED} onClick={ props.setVisabilityFilter } >
Show Completed Tasks
</button>
<button data-for={SHOW_ALL} onClick={ props.setVisabilityFilter }>
Show All Tasks
</button>
</div>
)
}
VisabilityFilter.propTypes = {
setVisabilityFilter: func.isRequired
}
export default VisabilityFilter
setState() is async (React docs), so the state changes won't be applied immediately. If you want to log out the new state,setState() takes in a function as the second argument and performs that function when the state is updated. So:
this.setState({
abc: xyz
},
() => console.log(this.state.abc),
)
Or you can also use componentDidUpdate(), which is recommended
In the functional components, you can use useEffect to track changes in state.
useEffect(() => {
console.log(someState);
},[someState);

react-select createable option - adding group label

I've created this cool sandbox that utilises react-select and the creatable feature. It allows you to select from a prepopulated dropdown and at the same time create a custom option by typing into the select field. Once you have typed into the field your option becomes available in the select list.
I have added in options to be grouped - pre-existing fields are fine, but new options I would like to be grouped by a default value e.g. "new group".
Any help would be appreciated.
https://codesandbox.io/s/p5x7m478rm
import React from "react";
import { Field, reduxForm, FieldArray } from "redux-form";
import TextField from "material-ui/TextField";
import { RadioButton, RadioButtonGroup } from "material-ui/RadioButton";
import Checkbox from "material-ui/Checkbox";
import SelectField from "material-ui/SelectField";
import MenuItem from "material-ui/MenuItem";
import asyncValidate from "./asyncValidate";
import validate from "./validate";
import CreatableSelect from "react-select/lib/Creatable";
const CustomStyle = {
option: (base, state) => ({
...base,
display: "inline",
marginRight: "10px",
backgroundColor: state.isSelected ? "#00285C" : "#eee",
cursor: "pointer"
}),
menuList: () => ({
padding: 10,
display: "inline-flex"
}),
menu: () => ({
position: "relative"
})
};
const createOption = (label: string) => ({
label,
value: label.toLowerCase().replace(/\W/g, "")
});
const formatGroupLabel = data => (
<div>
<span>{data.label}</span>
</div>
);
class LastNameSelectInput extends React.Component {
constructor(props) {
super(props);
}
state = {
value: this.props.options[0].options,
options: this.props.options
};
handleCreate = input => (inputValue: any) => {
this.setState({ isLoading: true });
setTimeout(() => {
const { options, value } = this.state;
const newOption = createOption(inputValue);
this.setState({
isLoading: false,
options: [...options, newOption],
value: newOption,
formatGroupLabel: "new label"
});
input.onChange(newOption);
}, 1000);
};
isValidNewOption = (inputValue, selectValue, selectOptions) => {
if (
inputValue.trim().length === 0 ||
selectOptions.find(option => option.name === inputValue)
) {
return false;
}
return true;
};
render() {
const { input, options } = this.props;
return (
<div>
<style>
{`.react-select__dropdown-indicator,.react-select__indicator-separator {
display: none;
}`}
</style>
<CreatableSelect
classNamePrefix="react-select"
options={this.state.options}
menuIsOpen={true}
onChange={value => {
let newValue = input.onChange(value);
this.setState({ value: newValue });
}}
onBlur={() => input.onBlur(input.value)}
onCreateOption={this.handleCreate(input)}
value={this.state.value}
styles={CustomStyle}
isClearable
isValidNewOption={this.isValidNewOption}
formatGroupLabel={formatGroupLabel}
/>
</div>
);
}
}
const MaterialUiForm = props => {
const { handleSubmit, options, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<Field name="option" component={LastNameSelectInput} {...props} />
</div>
</form>
);
};
export default reduxForm({
form: "MaterialUiForm", // a unique identifier for this form
validate,
asyncValidate
})(MaterialUiForm);
To achieve your goal I have changed the function handleCreate you have provided and the options props. You can see a live example here.
In MaterialUiForm.js
handleCreate = input => (inputValue: any) => {
this.setState({ isLoading: true });
setTimeout(() => {
const { options } = this.state;
const newOption = createOption(inputValue);
options.map(option => {
if (option.label === "New group") {
return {
label: option.label,
options: option.options.push(newOption)
};
}
return option;
});
this.setState({
isLoading: false,
options: [...options],
value: newOption,
formatGroupLabel: "new label"
});
input.onChange(newOption);
}, 1000);
In index.js
<MaterialUiForm
onSubmit={showResults}
initialValues={{
option: colourOptions,
option: flavourOptions
}}
options={[
{
label: "New group",
options: []
},
{
label: "Colours",
options: colourOptions
},
{
label: "Flavours",
options: flavourOptions
}
]}
/>
There is different and probably smarter way to do it but the logic is the good one.

set state twice in the same component

I am trying to setState to an event category for display inside of handleCategoryChange. The categories are rendered from the getCategories fetch point. I need to send a different value to the action fetch call in createEventHandler. The set state only happens once though and omits the second to send the first value of the state. Is there a work-around for this? or is this a limitation of react?
//... styles and imports
class NewEvent extends Component {
constructor(props) {
super(props);
this.state = {
event: {
category: ''
}
};
this.createEventHandler = this.createEventHandler.bind(this);
this.handleCategoryChange = this.handleCategoryChange.bind(this);
}
handleCategoryChange(evnt) {
this.setState({
event: {
...this.state.event,
category: evnt.target.value
}
});
}
componentWillMount() {
this.props.getCategories();
}
renderStepOne() {
const { event } = this.state;
const { categories } = this.props;
return (
<div style={styles.flexColumn}>
<Typography variant="title">Event</Typography>
<Select
value={event.category}
onChange={this.handleCategoryChange}
error={categoryError.length > 0}
>
{categories.map(category => (
<MenuItem key={category.id} value={category.name}>
{category.name}
</MenuItem>
))}
</Select>
</div>
);
}
createEventHandler() {
const { event } = this.state;
if (!error) {
let categoryId = this.props.categories.filter(e => {
if (e.name === event.category) {
return e;
}
});
categoryId = categoryId[0].id;
this.setState({
event: {
...event,
category: categoryId
}
});
this.props.createEvent(event, this.props.history);
}
}
render() {
const { step } = this.state;
const { isFetching, user, categories } = this.props;
return (
<ViewContainer title="New Event" isFetching={isFetching}>
<Paper style={styles.paper}>
<div style={styles.body}>{this.renderStepOne()}</div>
<MobileStepper
type="dots"
steps={0}
position="static"
nextButton={
<Button
variant="raised"
color="primary"
onClick={this.createEventHandler}
disabled={isFetching}
>
Submit
<KeyboardArrowRight />
</Button>
}
/>
</Paper>
</ViewContainer>
);
}
}
const mapStateToProps = state => ({
categories: state.events.categories
});
const mapDispatchToProps = dispatch => ({
createEvent: (event, history) => dispatch(createEvent(event, history)),
getCategories: () => dispatch(getCategories())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(NewEvent));
You could try using functional setState like so:
this.setState(() => ({
event: {
...this.state.event,
category: evnt.target.value
})
});
So that everything involving a setting of state happens together.

Categories

Resources