I'm trying to use a text input to filter a ViewList but it seems like because the initial state of 'text' is "", it always loops in a way everytime a type something it goes back to "" deleting everything a type
The filter function goes
filterSearch (texto) {
const newData = this.Data.filter((item) => {
const itemData = item.nombre.toUpperCase()
const textData = this.texto.toUpperCase()
return itemData.indexOf(textData) > -1
})
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newData),
text: texto
})
}
and is called from a textInput
<TextInput
style={styles.busqueda}
placeholder= 'Buscar'
onChangeText={(text) => this.filterSearch.bind(text)}
value={this.state.text}
>
</TextInput>
it seems like filterSearch isnt being called the right way...
There are 2 changes you need to do:
1/ Use the correct syntax to execute your function:
onChangeText={(text) => this.filterSearch(text)}
Actually, the onChangeText function was lexical bound to the component already (by using () => {})
2/ You also need to bind the filterSearch function to the react component too, then you can use this inside it (by either way below):
a) Using lexical binding:
filterSearch = (texto) => {
//...
}
b) Bind the function in your constructor:
constructor(props) {
super(props);
// ...
this.filterSearch = this.filterSearch.bind(this);
}
onChangeText={(text) => this.filterSearch(text)}
or
onChangeText={this.filterSearch.bind(this)}
Related
I have a Material UI Autocomplete combo-box child component class that fetches results as the user types:
...
fetchIngredients(query) {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState({
options: options
});
});
}
...
<Autocomplete
options={this.state.options}
value={this.state.value}
onChange={(e, val) => {
this.setState({value: val});
}}
onInputChange={(event, newInputValue) => {
this.fetchIngredients(newInputValue);
}}
renderInput={(params) => {
// Hidden input so that FormData can find the value of this input.
return (<TextField {...params} label="Foo" required/>);
}}
// Required for search as you type implementations:
// https://mui.com/components/autocomplete/#search-as-you-type
filterOptions={(x) => x}
/>
...
This child component is actually rendered as one of many in a list by its parent. Now, say I want the parent component to be able to set the value of each autocomplete programmatically (e.g., to auto-populate a form). How would I go about this?
I understand I could lift the value state up to the parent component and pass it as a prop, but what about the this.state.options? In order to set a default value of the combo-box, I'd actually need to also pass a single set of options such that value is valid. This would mean moving the ajax stuff up to the parent component so that it can pass options as a prop. This is starting to get really messy as now the parent has to manage multiple sets of ajax state for a list of its Autocomplete children.
Any good ideas here? What am I missing? Thanks in advance.
If these are children components making up a form, then I would argue that hoisting the value state up to the parent component makes more sense, even if it does require work refactoring. This makes doing something with the filled-in values much easier and more organized.
Then in your parent component, you have something like this:
constructor(props) {
super(props);
this.state = {
values: [],
options: []
};
}
const fetchIngredients = (query, id) => {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState(prevState => {
...prevState,
[id]: options
});
});
}
const setValue = (newValue, id) => {
this.setState(prevState => {
...prevState,
[id]: newValue
};
}
render() {
return (
<>
...
{arrOfInputLabels.map((label, id) => (
<ChildComponent
id={id}
key={id}
value={this.state.values[id]}
options={this.state.options[id]}
fetchIngredients={fetchIngredients}
labelName={label}
/>
)}
...
</>
I've built a custom Input React component (think wrapper) that renders an HTML input element. When a value is entered the change is passed to the parent component like this:
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
setCurrentValue(event.target.value);
props.onChange(event);
};
Everything works as expected but now I want to write a test for it:
it('Should render a Text Input', () => {
const onChange = jest.fn();
const { queryByTestId } = renderDom(
<Input type={InputTypesEnum.TEXT} name='car' onChange={onChange} />
);
const textInput = queryByTestId('text-input');
expect(textInput).toBeTruthy();
const event = fireEvent.change(textInput, { target: { value: 'Ford' }});
expect(onChange).toHaveBeenCalledTimes(1);
});
This works fine too except that I want to add a final expect using toHaveBeenCalledWith. I've tried several things but can't figure out how to do it. Any ideas?
Update: I've been reading this: https://reactjs.org/docs/events.html#event-pooling. It appears that if I change handleChange like this:
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
event.persist();
setCurrentValue(event.target.value);
props.onChange(event);
};
then the received object from onChange does change to include my test data. That said, I don't like the idea of altering an important feature of production code (in this case, event pooling) simply to accommodate a test.
You can do something like this with toHaveBeenCalledWith
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
target: expect.objectContaining({
value: "Ford"
})
})
);
So I am practicing React and wanted to display a "Arya's kill list" ], I wanted to make it possible to update it. So in my ToKill component when you double click on a character it shows inputs with values. But it is not possible to update them.
I wrote a function in my main App component it looks like this :
const toKillPpl = { ...this.state.toKill }
toKillPpl[index] = updatedToKill
this.setState({ toKillPpl })
}
next I pass it to ToKillList component with a state :
doubleClick = {this.doubleClickHandler}
deleteToKill = {this.deleteToKillHandler}
backBtn = {this.backBtnHandler}
state = {this.state}
toKillState = {this.state.toKill}
update = {this.toKillUpdate}
/>
in my ToKillList component I map over my state and I pass this function with a state of a person (toKillPerson) :
const ToKillList = (props) => props.state.toKill.map((toKill, index) => {
return <ToKill
double ={() => props.doubleClick(index)}
formDouble={toKill.formDouble}
click ={() => props.deleteToKill(index)}
backBtn ={() => props.backBtn(index)}
key={index + toKill.name}
index={index}
toKillPerson ={props.toKillState[index]}
update={props.update}
name={toKill.name}
cause={toKill.cause}
img={toKill.img}
/>
})
Finally in my ToKill component I write a function "handleChange" :
handleChange = (e) => {
const updatedToKill = {
...this.props.toKillPerson,
[e.currentTarget.name]: e.currentTarget.value
}
this.props.update(this.props.index, updatedToKill)
}
And here are inputs:
<input
type="text"
name="name"
className="hero-name"
onChange={this.handleChange}
value={this.props.name}
/>
<input
type="text"
name="img"
onChange={this.handleChange}
value={this.props.img}
/>
<input
type="text"
name="cause"
className="hero-cause"
onChange={this.handleChange}
value={this.props.cause}
/>
And it doesn't work. Is it a good approach, or I messed it up completely?
In case I wasn't clear here is a github repo: https://github.com/jakubmas/Aryas-Kill-List
Two correction in update method in your code.
1) You are not correctly copying over object,
const toKillPpl = { ...this.state.toKill }
This creates a shallow copy, you need deep cloning for this. You could either use JSON.strigify or lodash deepClone method.
2) You are not updating toKill state which is being passed to child components.
Here is the updated method:
toKillUpdate = (index, updatedToKill) => {
// const toKillPpl = JSON.parse(JSON.stringify(this.state.toKill)); // use this
const toKillPpl = _.cloneDeep(this.state.toKill); // or this
toKillPpl[index] = updatedToKill;
this.setState({ toKill: toKillPpl });
};
Here is the working codesandbox link
Hope that helps!!!
Another way to do this is that you could import immutability-helper (https://github.com/kolodny/immutability-helper), and use it to update state.toKill without mutating it:
import update from 'immutability-helper';
// Assuming 'updateToKill' input is an object...
handleUpdate = (index, updatedToKill) => {
this.setState(prevState => ({
toKill: update(prevState.toKill, {
[index]: {
$set: updatedToKill,
},
}),
}));
};
good day everyone,
I'm currently working on a react-native project and i have a form where i update the state when something is written to the inputs.
when i later try to access my state i get a cyclic object value error.
after some research i found out that this is caused by the onchange method used on each of the text inputs (2).
below is my code and tried out solutions.
components:
<TextInput
onChange={uname => this.setState(/* { username: uname } */ updateUsername(uname))}
placeholder={strings('login.username')}
style={styles.input}
/>
<TextInput
onChange={pwd => this.setState(/* { password: pwd } */ updatePassword(pwd) ) }
placeholder={strings('login.password')}
style={[styles.input, styles.password]}
secureTextEntry={true}
/>
< TouchableOpacity onPress = { () => this.login()} >
<Text style={styles.btn}>{strings('login.login')}</Text>
</TouchableOpacity>
as you can see from the comments inside the onchange prop of the textInput i did try 2 methods of updating the state:
i tried updating it the regular way by using the setState method on the component. then i tried defining 2 functions outside of the class then using them to update the class. neither solution made any difference.
const updateUsername = (username) => {
return (prevState, currProps) => {
return {...prevState, username}
}
}
const updatePassword = (password) => {
return (prevState, currProps) => {
return {
...prevState,
password
}
}
}
class Login extends Component {
....
}
this is the function where i get the error.it is defined inside the component class and is triggered when the button is clicked.
login() {
console.log(this.state)
}
i was wondering if anyone has any idea on how to solve this issue.
thank you everyone in advance for your time and effort.
Use onChangeText instead of onChange.
onChange passes a nativeEvent object, while onChangeText just passes the string.
I am new to react and am trying to add string values in an array. I am using Material-UI objects.
My state has
this.state: {
roles: []
}
A button pushes an undefined element in roles, incrementing its length.
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
So now we have some length to the roles array.
The Textfield is generated with
this.state.roles.map((item, i)=> {
return (
<TextField id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
the onchange event is handled as below
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
The console.log statements generate output like
roles[0]
[undefined]
I expect
roles[0]
["somedata"]
what is going wrong here? The data does not get set in the roles array.
The whole code file is
const styles = theme => ({
error: {
verticalAlign: 'middle'
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 300
},
submit: {
margin: 'auto',
marginBottom: theme.spacing.unit * 2
}
})
class AddModule extends Component {
constructor() {
super();
this.state = {
roles:[],
open: false,
error: ''
}
}
clickSubmit = () => {
const module = {
roles: this.state.roles || undefined
}
create(module).then((data) => {
if (data.error) {
this.setState({error: data.error})
} else {
this.setState({error: '', 'open': true});
}
})
}
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
render() {
const {classes} = this.props;
return (
<div>
<Button onClick={this.clickAddRole} >Add Role</Button>
{
this.state.roles.map((item, i)=> {
return (
<TextField className={classes.textField} id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
}
</div>
)
}
}
I think you're making the whole code a bit overcomplicated creating names for each input field. What I would do is change the handleRolesChange or handleChange (not really sure if you changed its name) method so that it takes the index instead of a name.
handleRolesChange = index => event => {
const { roles } = this.state;
const newRoles = roles.slice(0); // Create a shallow copy of the roles
newRoles[index] = event.target.value; // Set the new value
this.setState({ roles: newRoles });
}
Then change the render method to something like this:
this.state.roles.map((item, index) => (
<TextField
id={`roles[${index}]`}
label={`role ${index}`}
key={index}
onChange={this.handleRolesChange(index)}
/>
))
Guy I have the issue (maybe temporarily).
I an array-element is a child of the array. so changing the data in the array-element does not need setState.
So this is what I did....
handleRolesChange = name => event => {
const i = [name];
this.state.roles[i]=event.target.value;
}
I also change the Textfield onchange parameter to
onChange={this.handleRolesChange(i)}
where i is the index starting from zero in the map function.
All this works perfectly as I needed.
However, if you think that I have mutated the roles array by skipping setState, I will keep the Question unanswered and wait for the correct & legitimate answer.
Thanks a lot for your support guys.
We must try and find the solution for such basic issues. :)
Are you positive it's not being set? From React's docs:
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
Usually logging state in the same block you set the code in will print the previous state, since state has not actually updated at the time the console.log fires.
I would recommend using React Dev Tools to check state, instead of relying on console.log.