I am using React-autosuggest in my code but the issue i am facing is this that whenever i am clikcing any suggestion i am getting error that
Uncaught TypeError: Cannot read property 'trim' of undefined
Here is my code
var subjectsToBeSearched= []
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0
? []
:subjectsToBeSearched.filter(
lang => lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
};
const getSuggestionValue = suggestion => suggestion.name;
`
const renderSuggestion = suggestion => <div>{suggestion.name}</div>;
export default class Searchbar extends Component {
state = {
language_id: "",
subjects: [],
value: "",
suggestions: []
};
onChange = event => {
this.setState({
value: event.target.value
},()=>console.log(this.state.value));
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
componentWillMount() {
let languageid = localStorage.getItem("language_id");
var userdata = window.localStorage.getItem("userdata");
if (languageid == null) {
localStorage.setItem("language_id", 0);
}
this.setState({ language_id: languageid, userdata: JSON.parse(userdata) });
this.getAllSubjects();
}
getAllSubjects = async () => {
let details = {
language_id: this.state.language_id
};
this.setState({
response: fetch("http://18.221.47.207:3000/get_subjects", {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cache-Control": "max-age=31536000"
}
})
.then(response => response.json())
.then(responseJson => {
this.setState(
{
subjects: responseJson
},
() => {
let subjectsToBeFind = this.state.subjects.map(item => {
return { id: item.subject_id, name: item.subject_name };
});
subjectsToBeSearched=subjectsToBeFind
}
);
})
.catch(error => {
this.setState({
loading: false
});
swal("Warning!", "Check your network!", "warning");
console.log(error);
})
});
};
render() {
const { value, suggestions } = this.state;
const inputProps = {
value,
onChange: this.onChange
};
return (
<div className={`${styles.InputHeaderSearchDiv} `}>
<Autosuggest
inputProps={inputProps}
className={`${styles.InputHeaderSearch}`}
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
// alwaysRenderSuggestions={true}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
/>
<div className={`${styles.SearchIcon}`}>
<img src={SearchIcon} alt="search" />
</div>
</div>
);
}
}
So when i click on any suggestion i just need to console and do some stuff but right away it throws error
When you click a suggestion, the value of the "value" key in the inputProps is undefined thats the reason that you are getting a cannot trim error.
A workaround I did is add a props called "onSuggestionSelected" ( Documentation for onSuggestionSelected found here ) and added a function that set the value of the "value" key in the inputProps to whatever value you want your input tag should have after the click event.
THIS IS HOW MY AUTOSUGGEST LOOKSLIKE
HERE IS MY FUNCTION
in my case simply setting value of inputProps to string did it
<Autosuggest
inputProps: {
value: whateverValue.toString()
Related
I'm new React developer(mainly with hooks but did not find good example with hooks), here i have antd table with search functionality, my question is when user writes something in search then user gets different result, how to cancel that search by clicking 'Reset' button ?
my code:
https://codesandbox.io/s/antd-table-filter-search-forked-mqhcn?file=/src/EventsSection/EventsSection.js
You can add an id to your input into TitleSearch.js:
<Search
id='IDYOUWANT'
placeholder="Enter Title"
onSearch={onSearch}
onChange={onChange}
style={{ width: 200 }}
/>
And add event into EventsSection.js
ResetInput = () => {
const input = document.getElementById('IDYOUWANT');
input.value = '';
this.handleSearch('');
}
....
<button
onClick={this.ResetInput}
>Reset</button>
Change IDYOUWANT with your id
run this code
Created a new function for reset value and trigger it from reset button.
function:
resetValue = () =>{
this.setState({
eventsData: eventsData
});
}
And trigger from button
<button onClick={this.resetValue}>Reset</button>
all code::
import React, { Component } from "react";
import styles from "./style.module.css";
import { EventsTable } from "../EventsTable";
import { StatusFilter } from "../StatusFilter";
import { TitleSearch } from "../TitleSearch";
const eventsData = [
{
key: 1,
title: "Bulletproof EP1",
fileType: "Atmos",
process: "match media",
performedBy: "Denise Etridge",
operationNote: "-",
updatedAt: "26/09/2018 17:21",
status: "complete"
},
{
key: 2,
title: "Dexter EP2",
fileType: "Video",
process: "Compliance",
performedBy: "Dane Gill",
operationNote: "passed",
updatedAt: "21/09/2018 12:21",
status: "inProgress"
}
];
class EventsSection extends Component {
constructor(props) {
super(props);
this.state = {
eventsData
};
}
handleFilter = (key) => {
const selected = parseInt(key);
if (selected === 3) {
return this.setState({
eventsData
});
}
const statusMap = {
1: "complete",
2: "inProgress"
};
const selectedStatus = statusMap[selected];
const filteredEvents = eventsData.filter(
({ status }) => status === selectedStatus
);
this.setState({
eventsData: filteredEvents
});
};
handleSearch = (searchText) => {
const filteredEvents = eventsData.filter(({ title }) => {
title = title.toLowerCase();
return title.includes(searchText);
});
this.setState({
eventsData: filteredEvents
});
};
handleChange = (e) => {
const searchText = e.target.value;
const filteredEvents = eventsData.filter(({ title }) => {
title = title.toLowerCase();
return title.includes(searchText);
});
this.setState({
eventsData: filteredEvents
});
};
resetValue = () =>{
this.setState({
eventsData: eventsData
});
}
render() {
return (
<section className={styles.container}>
<header className={styles.header}>
<h1 className={styles.title}>Events</h1>
<button onClick={this.resetValue}>Reset</button>
<TitleSearch
onSearch={this.handleSearch}
onChange={this.handleChange}
className={styles.action}
/>
</header>
<EventsTable eventsData={this.state.eventsData} />
</section>
);
}
}
export { EventsSection };
Here is what i did in order to solve it:
i added onClick on the button
<button onClick={this.resetSearch}>Reset</button>
Then in the function i put handleSearch to '', by doing this it reset the table:
resetSearch = () =>{
this.handleSearch('')
}
When i call this function
getQuestions = () => {
this.setState({ loading: true })
const { data } = this.props
axios
.get(data.questions)
.then((res) => {
this.setState({
loading: false,
questions: res.data,
})
this.initialQuestions = res.data
})
.catch((err) =>
this.setState({
loading: false,
questions: [],
})
)
}
it updates the array questions in state and array initialQuestions variable in constructor. The state questions represents the values form inputs. The inputs are handled in child component with this code
onChange = (e) => {
const { hasChanged, setQuestions } = this.props
// Update questions
let questions = this.props.questions
questions[e.target.getAttribute('data-id')][e.target.name] =
e.target.value
setQuestions(questions)
}
setQuestions is passed in props as setQuestions={(state) => this.setState({ questions: state })}
So when i change the value of inputs the onChange function is called and it changes the parent component questions in state. But the parent variable this.initialQuestions also is being changed to the questions value from state, but I don't know why
Edit:
That's the code you should be able to run
const { Component } = React;
const Textarea = "textarea";
const objectsEquals = (obj1, obj2) =>
Object.keys(obj1).length === Object.keys(obj2).length &&
Object.keys(obj1).every((p) => obj1[p] === obj2[p])
class QuestionList extends React.Component {
static propTypes = {
questions: PropTypes.array,
removeQuestion: PropTypes.func.isRequired,
hasChanged: PropTypes.func.isRequired,
setQuestions: PropTypes.func.isRequired,
}
constructor(props) {
super(props)
this.questions = props.questions
this.onChange = this.onChange.bind(this)
}
onChange = (e) => {
const { hasChanged, setQuestions } = this.props
// Update questions
let questions = this.props.questions
questions[e.target.getAttribute('data-id')][e.target.name] =
e.target.value
setQuestions(questions)
if (hasChanged && this.questions.length > 0) {
// array of booleans, true if object has change otherwise false
const hasChangedArray = this.props.questions.map(
(_, index) =>
!objectsEquals(
this.questions[index],
this.props.questions[index]
)
)
console.log("hasChangedArray = ", hasChangedArray)
console.log("this.questions[0] = ", this.questions[0])
console.log("this.props.questions[0] = ", this.props.questions[0])
// If true in array than the form has changed
hasChanged(
hasChangedArray.some((hasChanged) => hasChanged === true)
)
}
}
render() {
const { removeQuestion, questions } = this.props
const questionList = questions.map((question, index) => (
<div className="card" key={index}>
<div className="card__body">
<div className="row">
<div className="col-sm-7">
<div className="form-control">
<label className="form-control__label">
Question:
</label>
<input
type="text"
id={`question-${index}`}
data-id={index}
onChange={this.onChange}
name="question"
value={
this.props.questions[index].question
}
className="form-control__input form control__textarea"
placeholder="Pass the question..."
rows="3"
/>
</div>
<div className="form-control">
<label className="form-control__label">
Summery:
</label>
<Textarea
id={`summery-${index}`}
data-id={index}
onChange={this.onChange}
name="summery"
value={this.props.questions[index].summery}
className="form-control__input form-control__textarea"
placeholder="Pass the summery..."
rows="3"
/>
</div>
</div>
</div>
</div>
</div>
))
return questionList
}
}
class Questions extends React.Component {
constructor(props) {
super(props)
this.initialQuestions = []
this.state = {
loading: true,
questions: [],
hasChanged: false,
}
this.getQuestions = this.getQuestions.bind(this)
this.resetForm = this.resetForm.bind(this)
}
resetForm = () => {
console.log("this.initialQuestions =", this.initialQuestions)
this.setState({
questions: this.initialQuestions,
hasChanged: false,
})
}
getQuestions = () => {
this.setState({ loading: true })
const { data } = this.props
// axios
// .get(data.questions)
// .then((res) => {
// this.setState({
// loading: false,
// questions: res.data,
// })
// this.initialQuestions = res.data
// })
// .catch((err) =>
// this.setState({
// loading: false,
// questions: [],
// })
// )
// You can't do a database request so here is some example code
this.setState({
loading: false,
questions: [
{
question: 'example-question',
summery: 'example-summery',
},
{
question: 'example-question-2',
summery: 'example-summery-2',
},
],
})
this.initialQuestions = [
{
question: 'example-question',
summery: 'example-summery',
},
{
question: 'example-question-2',
summery: 'example-summery-2',
},
]
}
componentDidMount = () => this.getQuestions()
render() {
const { loading, questions, hasChanged } = this.state
if (loading) return <h1>Loading...</h1>
return (
<form>
<QuestionList
questions={questions}
hasChanged={(state) =>
this.setState({ hasChanged: state })
}
setQuestions={(state) =>
this.setState({ questions: state })
}
/>
<button
type="reset"
onClick={this.resetForm}
className={`btn ${
!hasChanged
? 'btn__disabled'
: ''
}`}
>
Cancel
</button>
<button
type="submit"
className={`btn btn__contrast ${
!hasChanged
? 'btn__disabled'
: ''
}`}
>
Save
</button>
</form>
)
}
}
ReactDOM.render(<Questions />, document.querySelector("#root"));
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/prop-types#15/prop-types.min.js"></script>
<div id="root"></div>
Both state questions and class variable initialQuestions hold reference of res.data. Now when you update questions in onChange method, you are updating it by reference i.e directly mutating it and hence the class variable is also updated
You must not update it by reference but clone and update like below
onChange = (e) => {
const { hasChanged, setQuestions } = this.props
// Update questions
let questions = this.props.questions
questions = questions.map((question, idx) => {
if(idx === e.target.getAttribute('data-id')) {
return {
...question,
[e.target.name]: e.target.value
}
}
return question;
});
setQuestions(questions)
}
Trying to delete the contents of the input field by pressing backspace. However, nothing can be deleted. When I delete onChange = {this.handleSelectContractor} the field can be cleared, but after adding onChange = {this.handleSelectContractor} nothing can be done.
Pass selected todo to the handleSelect and assign it to the variable selected. The variable selected should appear in the input field
Demo here: https://stackblitz.com/edit/react-agfvwn?file=index.js
class App extends Component {
constructor() {
super();
this.state = {
todos: [],
selected: [],
todo: {},
todo2: 'ddd'
};
}
componentDidMount() {
this.getTodos().then(() => {
const todo = this.state.todos.find(todo => todo.id === 4);
this.setState({
selected: todo ? [todo] : []
})
})
}
getTodos = () => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: "GET"
})
.then(res => {
this.setState({
todos: res.data
});
})
.catch(error => {
console.log(error);
})
}
handleSelect = (todo) => {
let newArray = [...this.state.selected];
newArray.push(todo)
if(todo) {
this.setState({
todo: newArray
})
} else {
this.setState({
todo2: ''
})
}
}
render() {
console.log(this.state.todo)
return (
<div>
<Typeahead
id={'sasas'}
selected={this.state.selected}
labelKey="title"
onChange={this.handleSelect}
options={this.state.todos}
ref={(ref) => this._typeahead = ref}
/>
</div>
);
}
I found a lot of solutions about this problem but none of them work.
I have a view which renders dynamically components depending on the backend response
/**
* Module dependencies
*/
const React = require('react');
const Head = require('react-declarative-head');
const MY_COMPONENTS = {
text: require('../components/fields/Description'),
initiatives: require('../components/fields/Dropdown'),
vuln: require('../components/fields/Dropdown'),
severities: require('../components/fields/Dropdown'),
};
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class CreateView extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
states: props.states,
error: props.error,
spinner: true,
state: props.state,
prevState: '',
components: [],
};
this.handleChange = this.handleChange.bind(this);
this.getRequiredFields = this.getRequiredFields.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.changeState = this.changeState.bind(this);
this.loadComponents = this.loadComponents.bind(this);
}
componentDidMount() {
this.loadComponents();
}
onChangeHandler(event, value) {
this.setState((prevState) => {
prevState.prevState = prevState.state;
prevState.state = value;
prevState.spinner = true;
return prevState;
}, () => {
this.getRequiredFields();
});
}
getRequiredFields() {
request.get('/transitions/fields', {
params: {
to: this.state.state,
from: this.state.prevState,
},
})
.then((response) => {
const pComponents = this.state.components.map(c => Object.assign({}, c));
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
this.setState({
components: pComponents,
fields: response.data,
spinner: false,
});
})
.catch(err => err);
}
loadComponents() {
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
}
handleChange(field, value) {
this.setState((prevState) => {
prevState[field] = value;
return prevState;
});
}
changeState(field, value) {
this.setState((prevState) => {
prevState[`${field}`] = value;
return prevState;
});
}
render() {
const Components = this.state.components;
return (
<Page name="CI" state={this.props} Components={Components}>
<Script src="vendor.js" />
<Card className="">
<div className="">
<div className="">
<Spinner
show={this.state.spinner}
/>
{Components.map((component, i) => {
const Comp = component.component;
return (<Comp
key={i}
value={this.state[component.field.name]}
field={component.field}
handleChange={this.handleChange}
modal={this.state.modal}
changeState={this.changeState}
/>);
})
}
</div>
</div>
</div>
</Card>
</Page>
);
}
}
module.exports = CreateView;
and the dropdown component
const React = require('react');
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class DrpDwn extends React.Component {
constructor(props) {
super(props);
this.state = {
field: props.field,
values: [],
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('state', this.state.field);
console.log('prevState', prevState.field);
console.log('prevProps', prevProps.field);
console.log('props', this.props.field);
}
render() {
const { show } = this.props.field;
return (show && (
<div className="">
<Dropdown
className=""
onChange={(e, v) => this.props.handleChange(this.props.field.name, v)}
label={this.state.field.name.replace(/^./,
str => str.toUpperCase())}
name={this.state.field.name}
type="form"
value={this.props.value}
width={100}
position
>
{this.state.values.map(value => (<DropdownItem
key={value.id}
value={value.name}
primary={value.name.replace(/^./, str => str.toUpperCase())}
/>))
}
</Dropdown>
</div>
));
}
module.exports = DrpDwn;
The code actually works, it hide or show the components correctly but the thing is that i can't do anything inside componentdidupdate because the prevProps prevState and props are always the same.
I think the problem is that I'm mutating always the same object, but I could not find the way to do it.
What I have to do there is to fill the dropdown item.
Ps: The "real" code works, i adapt it in order to post it here.
React state is supposed to be immutable. Since you're mutating state, you break the ability to tell whether the state has changed. In particular, i think this is the main spot causing your problem:
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
}; return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
You mutate the previous states to changes its components property. Instead, create a new state:
this.setState(prevState => {
const components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return { components }
}
You have an additional place where you're mutating state. I don't know if it's causing your particular problem, but it's worth mentioning anyway:
const pComponents = [].concat(this.state.components);
// const pComponents = [...this.state.components];
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
You do at make a copy of state.components, but this will only be a shallow copy. The array is a new array, but the objects inside the array are the old objects. So when you set ob.field.required, you are mutating the old state as well as the new.
If you want to change properties in the objects, you need to copy those objects at every level you're making a change. The spread syntax is usually the most succinct way to do this:
let pComponents = this.state.components.map(c => {
return {
...c,
field: {
...c.field,
required: 0,
show: false
}
}
});
response.data.forEach(r => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
// Here it's ok to mutate, but only because i already did the copying in the code above
ob.field.required = r.required;
ob.field.show = true;
}
})
I am using React and Redux in order to manage the state of a list of checkboxes. What I have so far is working but eslint is sending me back an error:
assignment to property of function parameter
So I think I am doing something wrong.
This is the component:
import { toggleCheckboxAction, setCheckboxesToChecked } from '../actions/cancellations';
const CheckboxList = ({
columnsFilterHandler,
setCheckboxesToCheckedHandler,
checkboxes,
t,
}) => {
const onChange = (value, id, event) => {
const item = checkboxes.slice().find(idx => idx.id === id);
if (item) {
item.checked = !item.checked;
columnsFilterHandler(value, id, event.target.value);
return { checkboxes };
}
return item;
};
const setCheckboxesToTrue = () => {
const items = checkboxes.filter(checkbox => checkbox.checked === false);
items.map(item => {
if (item) {
item.checked = !item.checked; // LINE THROWING THE WARNING
setCheckboxesToCheckedHandler(item.checked);
}
return item;
});
};
return (
<>
<ToolbarTitle title="Columns" />
<ToolbarOption>
<Button kind="ghost" small onClick={setCheckboxesToTrue}>
{t('cancellations.resetDefault')}
</Button>
</ToolbarOption>
{checkboxes.map(checkbox => (
<ToolbarOption>
<Checkbox
key={checkbox.id}
id={checkbox.id}
labelText={checkbox.labelText}
value={checkbox.value}
checked={checkbox.checked}
onChange={onChange}
/>
</ToolbarOption>
))}
</>
);
};
CheckboxList.propTypes = {
columnsFilterHandler: PropTypes.func.isRequired,
setCheckboxesToCheckedHandler: PropTypes.func.isRequired,
checkboxes: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
};
const mapStateToProps = state => ({
checkboxes: state.cancellations.checkboxes,
});
export default compose(
connect(
mapStateToProps,
dispatch => ({
columnsFilterHandler: (value, isChecked, valueName) => {
dispatch(toggleCheckboxAction(value, isChecked, valueName));
},
setCheckboxesToCheckedHandler: isChecked => {
dispatch(setCheckboxesToChecked(isChecked));
},
}),
),
)(translate()(CheckboxList));
On the onChange function is where I play with the checkboxes, setting them to checked or !checked.
The issue comes from the function setCheckboxesToTrue on the line item.checked = !item.checked;
This is the action:
const setCheckboxesToChecked = isChecked => ({
type: ActionTypes.CHECKED_ALL_CHECKBOXES,
payload: { isChecked },
});
And this is the reducer to set them true or false:
[ActionTypes.TOGGLE_CHECKBOX](state, action) {
return {
...state,
checkboxes: initialState.checkboxes.map(checkbox => {
if (checkbox.id !== action.payload.id) {
return checkbox;
}
return {
...checkbox,
checked: !checkbox.checked,
};
}),
};
},
And this should be the reducer to set them all to true:
[ActionTypes.CHECKED_ALL_CHECKBOXES](state, action) {
return {
...state,
checkboxes: initialState.checkboxes.map(checkbox => {
if (checkbox.id !== action.payload.id) {
return checkbox;
}
return {
...checkbox,
checked: checkbox.checked,
};
}),
};
},
Do you guys know a better way to do what I am attempting to do or something to fix the error I am getting?
So, ESLint is complaining for exactly the reason it says: you are modifying the value of a function parameter. While this is perfectly valid JS and will work, it's highly discouraged because it makes it very easy to create bugs. Specifically:
items.map(item => {
if (item) {
item.checked = !item.checked; // HERE, you modify `item` which is the parameter passed to the map callback function
setCheckboxesToCheckedHandler(item.checked);
}
return item;
});
What you should do (and have done elsewhere in your code so you might have just forgotten to here):
items.map(item => {
const newItem = { ...item }; // Create a copy of `item` so that you don't need to modify the one coming in as the function parameter
if (item) {
newItem.checked = !newItem.checked;
setCheckboxesToCheckedHandler(newItem.checked);
}
return newItem;
});