How to do a search in the state (react) - javascript

I am able to store a data array containing the login and pass objects. I created an input field in which I write what I want to find in the state. How can I filter the state and display only matching items?
Constructor
class List extends Component {
constructor(props) {
super(props);
this.state = {
data: [
{ login: "login", pass: "pass" },
{ login: "login2", pass: "pass2" }
],
login: "",
pass: "",
find: ""
};
Adding and displaying data
add(e) {
e.preventDefault();
this.setState({
[e.target.name]: e.target.value
});
}
show(e) {
e.preventDefault();
if (!this.state.login.length || !this.state.pass.length) {
return;
} else {
const newUser = {
login: this.state.login,
pass: this.state.pass
};
this.setState(state => ({
data: state.data.concat(newUser)
}));
}
}
Search
filterUsers(e) {
let { data } = this.state;
//console.log(this.temp.login);
this.setState({
find: e.currentTarget.value
});
}
Render
<input onInput={this.filterUsers.bind(this)} />
<div>{this.state.find}</div>
{this.state.data.map((val, index) => (
<>
<td>{val.login}</td>
<td>{val.pass}</td>
<br />
<div />
</>
))}

What property are you filtering on? Login?
I recommend creating a filtered data array in your state so you don't modify the original.
filterUsers(event)
{
let filteredArray = this.state.data.filter((user) =>
{
user.login === event.currentTarget.value;
//or if you want partial matches
//user.login.includes(event.currentTarget.value)
})
this.setState({
filteredList: filteredArray
});
}

If you want to have your state.data remained untouched you can filter out your search-term in the render method.
filterUsers(e) {
this.setState({
find: e.currentTarget.value
});
}
<input onInput={this.filterUsers.bind(this)} />
<div>{this.state.find}</div>
{this.state.data.filter(x => x.login.includes(this.state.find)).map((val, index) => (
<>
<td>{val.login}</td>
<td>{val.pass}</td>
<br />
<div />
</>
))}

Related

Set initial class variable from axios request in React

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)
}

Set initialVariables in Formik from state if it is in edit mode

I'm using Formik for validating some data. It works fine when it should create new entity, but there are problems when I want to edit an entity.
The edit mode must be activated from the state (this.state.edit === true), also the data of the entity is stored on the state, for example this.state.name has a string value there.
I put a console log in render, the problem is that the log is printed several times, the first time with empty string on this.sate.name and the value of this.state.edit is false. The next prints it is correct, this edit on true and name containing a value.
Here is the code:
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Formik, Form, Field } from 'formik';
import { Input, Button, Label, Grid } from 'semantic-ui-react';
import { connect } from 'react-redux';
import * as Yup from 'yup';
import { Creators } from '../../../actions';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
name: '',
redirectCreate: false,
redirectEdit: false,
edit: false,
};
}
componentDidMount() {
const {
getCompany,
getCompanies,
location: { pathname },
} = this.props;
getCompanies({
name: '',
});
if (pathname.substring(11) !== 'create') {
getCompany(pathname.substring(16));
this.setState({
edit: true,
});
this.setState({
name: this.props.company.name,
});
}
}
handleSubmitCreate = e => {
e.preventDefault();
const { createCompany, getCompanies } = this.props;
createCompany(this.state);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmit = values => {
const { createCompany, getCompanies } = this.props;
createCompany(values);
this.setState({ redirectCreate: true });
getCompanies(this.props.query);
};
handleSubmitEdit = e => {
e.preventDefault();
const { name } = this.state;
const { updateCompany } = this.props;
updateCompany(this.props.company._id, {
name,
});
this.setState({ redirectEdit: true });
};
render() {
let title = 'Create company';
let buttonName = 'Create';
let submit = this.handleSubmitCreate;
const { redirectCreate, redirectEdit } = this.state;
if (redirectCreate) {
return <Redirect to="/companies" />;
}
if (redirectEdit) {
return <Redirect to={`/companies/${this.props.company._id}`} />;
}
if (this.state.edit) {
title = 'Edit company';
buttonName = 'Edit';
submit = this.handleSubmitEdit;
}
console.log('state: ', this.state); // first time it is empty, next times it has data
let initialValues = {};
if (this.state.edit) {
initialValues = {
name: this.state.name,
};
} else {
initialValues = {
name: '',
};
}
const validationSchema = Yup.object({
name: Yup.string().required('This field is required'),
});
return (
<>
<Button type="submit" form="amazing">
create company
</Button>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ values, errors, touched, setValues, setFieldValue }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field name="name" as={Input} placeholder="write a name" />
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
<Button type="submit" floated="right" form="amazing">
{buttonName} company
</Button>
</Form>
)}
</Formik>
</>
);
}
}
const mapStateToProps = state => ({
companies: state.companies.companies,
company: state.companies.selectedCompany,
query: state.companies.query,
});
const mapDispatchToProps = {
getCompanies: Creators.getCompaniesRequest,
createCompany: Creators.createCompanyRequest,
getCompany: Creators.getCompanyRequest,
updateCompany: Creators.updateCompanyRequest,
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);
I put the whole file here to have more context. Is it a way to set the initialValue of name with the value from this.state.name and put it inside the input field?
By default Formik does not re-render if the initial values change. You can pass enableReinitialize prop to Formik component to allow it.
As you said in the comment, first time it renders, it has no data, hence it does initialise Formik with empty values. With that prop, it should re-render if the initial values change.
https://formik.org/docs/api/formik#enablereinitialize-boolean

Adding clicked items to new array in React

I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.
Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}
I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!

React Bootstrap Typeahead specifies the length of characters in the input field

First problem: Why, if I enter one letter in input, console.log (this.state.results.length) does not show me 1. Only after entering two letters console.log (this.state.results.length) shows me 2.
Second problem: I will type three letters, then remove two letters, console.log (this.state.results.length) should show me 1 and show2. The same is when I clear the input it should show 0;
Demo here: https://stackblitz.com/edit/react-frleaq
class App extends Component {
constructor() {
super();
this.state = {
results: ''
};
}
_handleSearch = query => {
this.setState({
results: query
})
}
render() {
console.log(this.state.results.length)
return (
<div>
<AsyncTypeahead
clearButton
id="basic-example"
labelKey="name"
onSearch={this._handleSearch}
/>
</div>
);
}
}
You can use onInputChange to handle text changes and you can keep the text in state. This way you can check it's length and do whatever you want.
Example:
import React from "react";
import { AsyncTypeahead } from "react-bootstrap-typeahead";
import "bootstrap/dist/css/bootstrap.css";
import "react-bootstrap-typeahead/css/Typeahead.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
multiple: true,
options: [],
selectedUsers: [],
currentInput: ""
};
}
handleSearch = query => {
this.setState({ isLoading: true });
fetch(
`https://api.github.com/search/users?q=${query}+in:login&page=1&per_page=3`
)
.then(resp => resp.json())
.then(({ items }) => {
const options = items.map(i => ({
id: i.id,
name: i.login
}));
return { options };
})
.then(({ options }) => {
this.setState({
isLoading: false,
options
});
});
};
handleChange = selectedItems => {
this.setState({
selectedUsers: selectedItems,
currentInput: ""
});
};
handleInputChange = input => {
this.setState({
currentInput: input
});
};
render() {
const { selectedUsers, isLoading, options, currentInput } = this.state;
return (
<div>
<AsyncTypeahead
clearButton
id="basic-example"
isLoading={isLoading}
options={options}
minLength={3}
multiple
labelKey="name"
onSearch={query => this.handleSearch(query)}
onChange={selectedItems => this.handleChange(selectedItems)}
onInputChange={input => this.handleInputChange(input)}
placeholder="Search for users"
/>
<hr />
<br/>
<br/>
<br/>
{currentInput.length > 0 && <button>MY BUTTON</button>}
<hr />
Selected {selectedUsers.length} Users: <br />
<ul>
{selectedUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
}
export default App;

React JS accessing an array nested in state

class CreateEventForm extends Component {
constructor(props) {
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
},
friendsInEvent: [],
},
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
this.setState({ friendsInEvent }, () => console.log(this.state))
}
render() {
const { friends, createEvent, addFriendsToEvent } = this.props
const { fields: { name, location, description, datetime } } = this.state
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}
return (
<div {...cssForm}>
<div>
<Label label="Event Name" />
<Field onChange={setter('name')} />
<Label label="Event Date/Time" />
<Field onChange={setter('datetime')} type="datetime-local" />
<Label label="Event Location" />
<Field onChange={setter('location')} />
<Label label="Event Description" />
<Field onChange={setter('description')} />
<SubHeader title="Add Friends to the Event..." />
<div>
{friends
.mapEntries(([friendId, frn]) => [
friendId,
<div key={friendId}>
<input
value={friendId}
type='checkbox'
onChange={this.handleChange}
defaultChecked={false} />
{frn.firstName} {frn.lastName}
</div>,
])
.toList()}
</div>
<Link to="/">
<Button style="blue" name="Done" onClick={() => createEvent(this.state.fields)} />
</Link>
</div>
</div>
)
}
}
export default CreateEventForm
This code above works and stores all of the correct values as expected. However I want to move friendsInEvent[ ] inside fields{ }. To look like:
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
friendsInEvent: [],
},
},
this.handleChange = this.handleChange.bind(this);
}
How can I achieve this? Everything I have tried breaks the handleChange(e) function or overwrites fields{ } with the friendsInEvent array.
I assume I have to change the values inside the handleChange(e) function after I move the array inside this.state.field?
Also, is there a way to merge my two functions setter and handleChange(e)? I kept them separate as they handle two different types (string and array).
Thanks.
From the looks of it you'll need to make the following changes to handleChange:
handleChange(e) {
const { checked, value } = e.target;
// Ensure that you're destructuring from fields
let { friendsInEvent } = { ...this.state.fields };
if (checked) {
friendsInEvent = [...friendsInEvent, value];
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
// You need to take a copy of state, and then supply a new copy
// of the the fields property which takes a copy of the old fields
// property, and overwrites the friendsInEvent with the new data
this.setState({
...this.state,
fields: {...this.state.fields, friendsInEvent }
}, () => console.log(this.state));
}
Here's a small React-free example of how this would work.
Why can't you do it in the below way.
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
let object = {};
object.name = this.state.name;
object.location = this.state.location;
object.description = this.state.description;
object.datetime = this.state.datetime;
object.friendsInEvent = friendsInEvent;
this.setState({
fields: object
})
}
Never do setState inside render. You are not suppose to do setState inside render. Event handlers should be handled before render method but not inside of render. Try avoiding doing setState inside render.
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}

Categories

Resources