Reactjs Select v2 - How to handle Ajax Typing? - reactjs

I am using reactjs select 2 but I don't know how to make it work so that when a user types something in a ajax request is made and the results are sent back.
I see it has some async options but I don't get how it works and how I would get it to work with axios.
I come up with this but it is kinda laggy when a user types(probably because it is re-rendering it after each type) and when the user selects a choice the value disappears.
export default class TestComponent extends Component {
constructor(props) {
super(props);
this.state = {value: ""};
}
onInputChange(option) {
this.getOptionsAsync(option)
}
getOptionsAsync(newInput) {
var that = this;
console.log("ffd", newInput)
axios.get(`https://localhost:44343/api/States/GetStatesByText?text=${newInput}`)
.then(function (response) {
var formatedResults = response.data.map((x)=> {
return {value: x.id, label: x.name}
})
that.setState({
options: formatedResults,
value: newInput
})
})
.catch(function (error) {
});
}
render() {
console.log(this.state.value, "value")
return (
<div className="test">
<Select
onInputChange={this.onInputChange.bind(this)}
value={this.state.value}
options={this.state.options }
/>
</div>
);
}
}

You're going to be doing an api call every single time that you type a letter with the current way you're doing things. I would recommend just loading the states once at the beginning, perhaps in your ComponentDidMount() method.
If you pass the isSearchable prop to React-Select it will automatically work as a filter anyways.
Another thing I've had to do in this case which I believe will fix your change problem is to make sure it calls the handler on change not just on input change.
Pass this prop:
<Select
value={this.state.value}
options={this.state.options }
onChange={value => {
if (value) this.onInputChange(value)
else this.onInputChange('')
}
/>
Due to the way this is automatically bound to arrow functions, you won't have to bind to this if you change your onInputChange to the following:
onInputChange = (value) => {
this.getOptionsAsync(value)
}
Finally, you should be setting the state in the above function so the value is stored.
onInputChange = (value) => {
this.getOptionsAsync(value)
this.setState({value})
}

Related

I wanna console.log the value after clicking the submit button once and to delete the previous mapped items, but it doesnt work

I'm very new to react and I got two problems:
I want to console log the input and display the mapped data after clicking the submit button once. But I get console logged the input and the mapped data after clicking the button twice.
I wanna clear the mapped list (data from previous input) and display new list items depending on the input. But the new list items are only added to the end of the previous list (only the last list item from the previous list got overwritten by the first list item of the new list).
So this is the code from my app component:
import React, { Component, Fragment } from 'react';
import './App.css';
import Display from './/Display';
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
passedValue: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
handleSubmit(event) {
this.setState({ passedValue: this.state.value });
console.log(this.state.passedValue);
event.preventDefault();
}
render() {
return (
<div>
<form className="inputContainer" onSubmit={this.handleSubmit}>
<input type="text" name="company_name" onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
<Display listDataFromParent={this.state.passedValue} />
</div>
);
}
}
export default App;
And this is my display component:
import React, { Component } from 'react'
import "./Display.css";
export default class Display extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: []
};
}
componentWillReceiveProps() {
fetch("http://localhost:5000/company?company_name=" + this.props.listDataFromParent)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
data: result
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, data } = this.state;
// if (error) {
// return <div>Error: {error.message}</div>;
// } else if (!isLoaded) {
// return <div>Loading...</div>;
// } else {
return (
<div className="display">
<h1>Kreditnehmer</h1>
<ul>
{this.props.listDataFromParent}
{data.map(item => (
<li key={item.c.company_id}>
Relation type: {item.r.relation_group}
Last name: {item.p.last_name}
</li>
))}
</ul>
</div>
);
}
}
Can anyone help?
1) setState is async method in react means it will take some time to update the component state. You can get your console log by using callback function of setState like
this.setstate({ value: e.target.value }, () => { console.log(this.state.value) });
2) in display component, your using componentWillReciveProps life cycle and inside that your using this.props.listdatafromparent which is pointing previous props. Rather than using this.props I would suggest consider props param of life cycle, means it should be like
componentWillReciveProps(props) {
// your code
Console.log(props.listdatafromparent);
}
The handleSubmit method is wrong... the console log is executed before the state is changed. You need to put it inside a callback function as a second parameter of setState.
this.setState({ passedValue: this.state.value }, () => {
console.log(this.state.passedValue);
});
Answers are:
1) Callback function should be used on setState, in order to do console.log after state is really updated.
In your case you call setState and setState is async function, which means that console.log won't wait until state is really updated.
Your code should be:
handleSubmit(event) {
this.setState({ passedValue: this.state.value },
() => console.log(this.state.passedValue));
event.preventDefault();
}
2) I would move data fetching out of componentWillReceiveProps(), since this lifecycle method will be deprecated from version 17 and it is fired on every render(). Try replacing with componentDidMount() or componentDidUpdate(). Maybe just this small change will solve your problem. If not pls post results and I will take a look again.

ComponentDidMount Updates State, but doesnt seem to re-render component

The problem I face is that it doesn't seem that componentDidMount is re-rendering my component, even though it is updating the state. Lot of code coming up, but it gives context to the issue I'm having. If I need to, I can upload screenshots of what is happening.
Here's the constructor:
export class Upload extends React.Component<RouteComponentProps<{}>, UploadTaggingOptions> {
constructor(props: any) {
super(props);
this.state = {
photographer: [
{ id: null, value: '', label: '' },
],
};
}
Here's component did mount:
componentDidMount {
//Fetch request for photographers from the db
fetch("http://localhost:49775/api/photographers")
.then(res => res.json())
.then((result) => {
var photographerData = this.state!.photographer;
var y = 0;
//Remove the empty object first and foremost. The list should now be totally empty
photographerData.shift();
//The loop to add the galleries to the galleryData array
for (var i in result) {
var id = result[i].id;
var value = result[i].firstname + ' ' + result[i].lastname;
var label = value;
var photographer = { "id": id, "value": value, "label": label };
photographerData.push(photographer);
y++;
}
this.setState({
isLoaded: true,
photographer: photographerData
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
alert("Error loading options for the photographers. Refresh the page. If the error persists, please contact your administrator");
}
)
And finally Render:
public render() {
return <div>
<div className="photographers">
<p><b>Photographer:</b></p>
<DropDown options={this.state!.photographer} />
</div>
}
Just for clarity's sake, there are more components on the screen (hence the extra div for the dropdown component).
I'm not sure why, but the dropdown renders with the blank options I intitialize in the constructor, componentdidupdate does the fetch request AND updates the state to the data that was fetched, but I have to click the blank value in order to load those data values into the dropdown. It is almost like it re-renders after I change the selected value instead of on state change.
I've tried moving those requests into the constructor, but have the same problem. Perhaps
EDIT: Here's the code for the Dropdown component:
import * as React from 'react';
import Select from 'react-select';
const DropDown = (props: any) => {
return (
<div className="dropdown">
<Select
closeOnSelect={!(props.stayOpen)}
disabled={props.disabled}
options={props.options}
placeholder="Select an option..."
removeSelected={props.removeSelected}
simpleValue
value={props.selectedPhotographer}
searchable={true}
multi={true}
/>
</div>
)
}
export default DropDown;
From react official documentation:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
But in your code you are mutating it, albeit via an assignment to another variable:
var photographerData = this.state!.photographer;
// this DOES mutate the original array.
photographerData.shift();
This can mess with Reacts batching update strategy and can cause delays.
If you do not need the data from original state, you can just do:
var photographerData = [];
window.onload = function() {
console.log('Testing');
let testArr1 = [1, 2, 3]
console.log('TestArr1 length before shift: ' + testArr1.length);
let testArr2 = testArr1;
testArr2.shift();
console.log('TestArr1 length after shift: ' + testArr1.length);
}

ReactJS, Checkbox doesn't

I Have a ReactJS checkbox component. When onChange is called I can log the new state and see it changing, but it never actually re-renders the checkbox into the new state. So the ADD_ID action is never called. See code below:
class CheckBox extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: true
}
}
changing = (e) => {
this.setState(prevState => ({checked: !prevState.checked}), () => {
console.log(this.state.checked); // false
this.state.checked
? store.dispatch({ type: 'ADD_ID', id: this.props.id })
: store.dispatch({ type: 'REMOVE_ID', id: this.props.id });
});
}
render() {
return (
<label>
Include
<input onChange={this.changing} checked={this.state.checked} type='checkbox'/>
</label>
)
}
}
Is there a lifecycle hook that I have to call? I was under the impression that the component would re-render when either it's props or state changes, in this case, as shown by the console.log(this.state.checked), the state has changed, but the component doesn't re-render.
The event has already a checked property for you. You're doing it in a way that's a bit weird.
Change your function to something like:
handleChange = (e) => {
const isChecked = e.target.checked
if(isChecked){
store.dispatch({type:'ADD_ID', id:this.props.id})
} else {
store.dispatch({type:'REMOVE_ID', id:this.props.id})
}
this.setState(checked: isChecked)
}
It's however still strange that you're using at the same time internal state and Redux. You may want to rethink your approach here.
React docs on forms, which I recommend you to read in 5 min:
https://reactjs.org/docs/forms.html

How to know if all the setState updates have been applied to the state in a React component?

I was reading the documentation about React setState, which says:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
Now I have a component like this:
class NoteScreenComponent extends React.Component {
constructor() {
super();
this.state = { note: Note.newNote() }
}
componentWillMount() {
this.setState({ note: this.props.note });
}
noteComponent_change = (propName, propValue) => {
this.setState((prevState, props) => {
let note = Object.assign({}, prevState.note);
note[propName] = propValue;
return { note: note }
});
}
title_changeText = (text) => {
this.noteComponent_change('title', text);
}
body_changeText = (text) => {
this.noteComponent_change('body', text);
}
saveNoteButton_press = () => {
// Save note to SQL database
Note.save(this.state.note)
}
render() {
return (
<View>
<TextInput value={this.state.note.title} onChangeText={this.title_changeText} />
<TextInput value={this.state.note.body} onChangeText={this.body_changeText} />
<Button title="Save note" onPress={this.saveNoteButton_press} />
</View>
);
}
}
What I'm wondering is, since setState does not update the state immediately, how can I know if the note I'm saving in saveNoteButton_press is the current version of the state? Is there some callback or something that I could poll to know if state has been fully updated?
What they are warning against is trying to do something in the same event loop.
method = () => {
this.setState({ note: 'A' })
saveNote(this.state.note) // <-- this.state.note will not have been updated yet.
}
or to setState using previous state:
method = () => {
let note = this.state.note // possible that `this.state.note` is scheduled to change
this.setState({ note: note + 'B' })
}
Since your user is going to be pushing the button after the setState scheduling, the state will have already been updated.
..but for theory's sake, let's imagine that somehow the input event and button happen in the exact same moment.. what would be the correct solution? If it was a single function call you probably wouldn't be using the new state since you already have the new note and the previous state.
method = (text) => {
let noteToSave = this.state.note + text // old state + new value
saveNote(noteToSave) // maybe this will fail
.then(response => this.setState({ note: noteToSave }))
.catch(err => this.setState({ error: 'something went wrong' }))
// optimistically update the ui
this.setState({ note: noteToSave })
}
but probably the most likely solution is to just pass what you want as an argument where you use it, rather than trying to access state which might be in a race condition, since render will happen after any state transitions.
method = (note) => {
noteToSave(note)
}
render() {
return (
<Button onPress={() => this.method(this.state.note)} /> <-- must be up to date here
)
}

How to allow updates to input without updating props in React?

I would like to allow a user to enter changes in an input field without propagating them to the parent. I did this by returning out of the onChange function whenever I don't want to propagate. However this seems to undo the character I typed.
Here is a use case. I have a number field. I want to trigger onChange on parent when there is a number entered, but ignore "." and ","s (formatters.staticToFloat removes them).
export default class NumberField extends React.Component {
render () {
var props = this.props;
var format = props.formatter || formatters.number;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={!_.isUndefined(props.value) ? format(props.value) : null}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
return;
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
So far the best way I've come up with is maintaining a separate formattedValue state that only gets set when I want to override the default formatting. It works, but seems like a super dirty solution.
export default class NumberField extends React.Component {
constructor () {
super();
this.state = {
formattedValue: null
};
}
render () {
var props = this.props;
var state = this.state;
var format = props.formatter || formatters.number;
var inputValue = state.formattedValue || (
!_.isUndefined(props.value) ?
format(props.value) :
null
);
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={inputValue}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
this.setState({
formattedValue: e.target.value
});
} else {
this.setState({
formattedValue: null
});
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
Sounds like a case for using state to store the formatted value inside the component, and use a special variant of setState with a callback.
all user input (including . and ,) is put in state and rendered in input field back to user.
only when the format is correct, the parent onChange() is called.
The parent may actually do a re-render triggered by the onChange() call. Therefore, we need to make sure that the last entered character is updated in state, and only after that the parent's onChange() will be called.
Your component would look like as follows:
export default class NumberField extends React.Component {
constructor (props) {
super(props);
this.state = {
value: !_.isUndefined(props.value) ?
props.formatter ? props.formatter(props.value).toString() : formatters.number(props.value).toString()
: null;
};
}
render () {
var props = this.props;
var state = this.state;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={state.value}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
// if numValue is different from current state
// then it must be an OK update,
// so we update state AND call parent if function exists
if (numValue.toString() != this.state.value) {
this.setState({
value: numValue
},
this.callParent // here is the magic: we pass a callback, to be called after state update and after re-render
);
} else {
// otherwise we only update state (to display invalid character)
this.setState({
value: numValue
});
}
}
callParent() {
// state is updated and component has re-rendered when this is called
// so we can use state.value to inform parent
if (this.props.onChange) {
this.props.onChange({
value: this.state.value,
valid: validation.isValid(this.state.value, this.props.validation)
});
}
}
};
This may be a bit of overkill: you keep a state (formatted value) that you also communicate to the parent. If your parent ALWAYS passes down the newly forwarded input, then you could make your component a lot simpler:
No state, but simply re-render based on props.
You only really need state if the value presented to the user in the input field can deviate from what you communicate to parent.

Resources