Searching narrow down results React - javascript

I am trying to explore searching and narrowing down to certain results. Problem is that search based on output from one filed is working ok but when i want to narrow down the results using second or third filed it is not working. I think issue is in here i do not know how to modify it to have this narrow results.
if (this.state.data !== null) {
result = this.state.data.filter(state => {
const regex = new RegExp(
`^${this.state.name || this.state.email || this.state.body}`,
"gi"
);
return (
state.name.match(regex) ||
state.email.match(regex) ||
state.body.match(regex)
);
});
}
Search
import React, { Component } from "react";
import Table from "./Table";
import axios from "axios";
export default class Main extends Component {
state = {
data: null
};
onChange = e => {
this.setState({
[e.target.name]: e.target.value
});
axios
.get("https://jsonplaceholder.typicode.com/comments")
.then(res =>
this.setState({
data: res.data
})
)
.catch(err => console.log(err));
};
render() {
let result;
if (this.state.data !== null) {
result = this.state.data.filter(state => {
const regex = new RegExp(
`^${this.state.name || this.state.email || this.state.body}`,
"gi"
);
return (
state.name.match(regex) ||
state.email.match(regex) ||
state.body.match(regex)
);
});
}
console.log(this.state.name);
console.log(this.state.email);
console.log(this.state.body);
console.log(result);
console.log(this.state.data);
return (
<div>
<table>
<thead>
<tr>
<th>
<input
label="Name"
name="name"
placeholder="Name "
onChange={this.onChange}
/>
</th>
<th>
<input
label="Name"
name="email"
placeholder="Email "
onChange={this.onChange}
/>
</th>
<th>
<input
label="Name"
name="body"
placeholder="Body "
onChange={this.onChange}
/>
</th>
</tr>
</thead>
{result !== undefined ? <Table data={result} /> : <p>Loading</p>}
</table>
</div>
);
}
}
Table.js
import React, { Component } from "react";
export default class Table extends Component {
render() {
const { data } = this.props;
console.log(data);
return (
<tbody>
{data.map(el => (
<tr key={el.id}>
<td>{el.name}</td>
<td>{el.email}</td>
<td>{el.body}</td>
</tr>
))}
</tbody>
);
}
}

I think if you are okay with using .startsWith(), this would be a very clean and readable solution without Regular Expressions. They might not be necessary here:
result = this.state.data.filter(record => {
return (
record.name.starsWith(this.state.name) ||
record.email.starsWith(this.state.email) ||
record.body.starsWith(this.state.body)
);
});
.startsWith() is not supported by IE, but you can polyfill it as described here:
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function(search, pos) {
pos = !pos || pos < 0 ? 0 : +pos;
return this.substring(pos, pos + search.length) === search;
}
});
}
EDIT:
If you want all the filters to match, just use && instead of ||. Also, if you want to find the string anywhere in the data (and not just at the beginning) the code could look like this:
result = this.state.data.filter(record => {
return (
record.name.indexOf(this.state.name) !== -1 &&
record.email.indexOf(this.state.email) !== -1 &&
record.body.indexOf(this.state.body) !== -1
);
});
This still avoids RegEx, because it's not really necessary here.

Related

React Form loses input on reload

im having trouble with a project of mine with the useState hook. This is for a declaration of an event.
This component can be opened from a list of all the other declas{ which works fine} and then everything shows however if you reload the page/open it from a link nothing shows up in the following fields even when it does get the decla using using the useEffect hoop(at least it logs it out to the console)
TLDR;
when component called/opened from a list it works and loads all the data but when the page is reloaded it is all lost and doesnt load it again with useState
ive taken the liberty to reduces the amount of data show as it is a pretty long file already.
import { useContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import Select from "react-select";
import CreatableSelect from "react-select/creatable";
import DeclaContext from "../../context/DeclaContext";
import LedenContext from "../../context/LedenContext";
import EventContext from "../../context/EventContext";
export const DeclaFrom = () => {
let { id } = useParams();
const navigate = useNavigate();
const { user, leden } = useContext(LedenContext);
const { events } = useContext(EventContext);
let { declas, GET, GET_decla, POST, PUT, DELETE, boekstuks, POST_boekstuk } =
useContext(DeclaContext);
// useEffect(() => {
// const get = async () => {
// await GET();
// const data = await GET_decla(id);
// setDecla(data);
// };
// get();
// // eslint-disable-next-line
// }, [id]);
console.log(declas?.find((de) => (de.id === id ? true : false)));
const [decla, setDecla] = useState(declas?.find((de) => de.id === id));
const [event, setEvent] = useState(decla && decla?.event);
const [owner, setOwner] = useState(decla ? decla?.owner : user.lid_id);
const [content, setContent] = useState(decla ? decla?.content : "");
const [total, setTotal] = useState(decla ? decla?.total : 0);
const [receipt, setReceipt] = useState(decla && decla?.receipt);
const [boekstuk, setBoekstuk] = useState(decla ? decla?.boekstuk : "");
const [content_ficus, setContent_ficus] = useState(
decla ? decla?.content_ficus : ""
);
const optionsLeden = [
{ label: "Select All", value: "all" },
...leden?.map((lid) => ({
value: lid.id,
label: lid?.initials,
})),
];
const [defaultValues, setDefaultValues] = useState(
decla
? decla?.present?.map((pres) =>
optionsLeden?.find((x) => x.value === pres)
)
: []
);
const optionsBoekstuk = boekstuks?.map((boekstuk) => ({
value: boekstuk?.id,
label: boekstuk?.name,
}));
const optionsEvents = events?.map((event) => ({
value: event.id,
label: event.description + " " + event.start_date,
}));
async function createBookstuk(inputValue) {
const BSid = await POST_boekstuk({ name: inputValue });
setBoekstuk(optionsBoekstuk?.find((x) => x.id === BSid)?.value);
}
const onDelete = (e) => {
e.preventDefault();
DELETE({
id,
});
setDeleted(true);
};
const onSubmit = (e) => {
// if (!event | !content | !total | !present) {
// alert("Je moet een evenement kiezen");
// return;
// }
e.preventDefault();
if (decla) {
PUT({
id,
event,
content,
total,
receipt,
boekstuk,
});
navigate("/declas");
} else {
POST({
event,
content,
total,
receipt,
boekstuk,
});
}
setDeleted(false);
};
return (
<div className="columns">
<div className="column is-half is-offset-3">
<form>
<table>
<tbody>
{user.roles.includes("Fiscus") && (
<tr>
<th>
<label htmlFor="id_lid">Lid:</label>
</th>
<td className="field">
<Select
defaultValue={optionsLeden?.find(
(x) => x.value === owner
)}
options={optionsLeden?.filter(
(x) => !["all", 19900].includes(x.value)
)}
name="owner"
id="id_present"
onChange={(e) => {
setOwner(e.value);
}}
/>
</td>
</tr>
)}
<tr>
<th>
<label htmlFor="id_event">Event:</label>
</th>
<td className="field">
<Select
defaultValue={optionsEvents?.find(
(x) => x.value === event?.id
)}
options={optionsEvents}
name="event"
onChange={(e) => {
setEvent(e.value);
}}
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_total">Total:</label>
</th>
<td className="field">
<input
type="number"
onChange={(e) => setTotal(e.target.value)}
name="total"
value={total}
step="any"
className="input"
required
id="id_total"
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_present">Present:</label>
</th>
<Select
isMulti
value={defaultValues}
options={optionsLeden}
required
name="present"
id="id_present"
onChange={(e) => {
if (e.some((val) => val.value === "all")) {
setPresent(
optionsLeden
?.filter((x) => x.value !== "all")
.map((x) => x.value !== "all" && x.value)
); // change the value going to the API
setDefaultValues(
optionsLeden
?.filter((x) => x.value !== "all")
.map((x) => x.value !== "all" && x)
); // change the values displayed
} else {
setPresent(e?.map((x) => x.value)); // change the value going to the API
setDefaultValues(e?.map((x) => x)); // change the values displayed
}
}}
/>
</tr>
<tr>
<th>
<label htmlFor="id_receipt">Receipt:</label>
</th>
<td className="field">
<input
type="file"
name="receipt"
required
accept="image/*"
onChange={(e) => {
setReceipt(e.target.files[0]);
}}
className="input"
id="id_receipt"
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_boekstuk">Boekstuk:</label>
</th>
<td className="field">
<CreatableSelect
defaultValue={optionsBoekstuk?.find(
(x) => x.value === boekstuk
)}
options={optionsBoekstuk}
name="boekstuk"
id="id_boekstuk"
onCreateOption={createBookstuk}
onChange={(e) => {
setBoekstuk(e.value);
}}
/>
</td>
</tr>
<tr>
<th>
<label htmlFor="id_content_ficus">Content ficus:</label>
</th>
<td className="field">
<textarea
onChange={(e) => setContent_ficus(e.target.value)}
name="content_ficus"
value={content_ficus}
cols="40"
rows="10"
maxLength="100"
className="input"
id="id_content_ficus"
></textarea>
</td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
);
};
export default DeclaFrom;
Thanks for your help
I had a similar issue in a recent project. The problem you're facing is that your state is being erased when you reload the page.
In your useEffect hook, remove id from the dependency array (but leave the empty array) so that it will run once on every render. This way when you reload the page useEffect will run your api call and re-populate your state.
useEffect(() => {
const get = async () => {
await GET();
const data = await GET_decla(id);
setDecla(data);
};
get();
}, []);
veel geluk!
useState values will not persist after page reloads: this is a standard behavior of React. After page reload, all your useState values will reset to their default values.
If you need values to persist after refresh, then you will want to use a browser utility like localStorage.
As an example, this is how I would set/get the value:
const stateFromLocalStorage = localStorage.getItem("itemKey")
const [someState, setSomeState] = useState(stateFromLocalStorage || defaultState)
const settingState = (key, value) => {
localStorage.setItem(key, value)
setSomeState(value)
}
I grab the item from localStorage first, and attempt to assign it as the default value of the useState. If the local storage value doesn't exist, then stateFromLocalStorage will be undefined, and it will set defaultState instead because of the logical || operator.
When setting the state, I set it both with the useState setter function, as well as the local storage setItem function. That way local storage and component state will stay in sync.

alert is being called 2 times for 1 call in React [duplicate]

I have an idea that this may be because I am doing some styling things to change my radio button, but I am not sure. I am setting an onClick event that is calling my function twice. I have removed it to make sure it wasn't being triggered somewhere else and the onClick seems to be the culprit.
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
my function is just for now a simple console log of the shipping option:
changeShipping(shipOption){
console.log('clicked') // happening twice
}
If there isn't any reason you see here why this would happen I can post the rest of the code, but there is a lot and I don't think it would pertain to this, but I think this is a good starting place.
Full code:
import React, { Component } from 'react'
import fetch from 'isomorphic-fetch'
import { Subscribe } from 'statable'
import { FoldingCube } from 'better-react-spinkit'
import styles from './styles'
import { cost, cartState, userInfo, itemState, Api } from '../../state'
import { removeCookies, resetCart } from '../../../injectState'
export default class ShippingOptions extends Component {
constructor(props) {
super(props)
this.state = {
shippingOption: {}
}
this.changeShipping = this.changeShipping.bind(this)
}
async changeShipping(shipOption) {
const shipKey = Object.keys(shipOption)[0]
// if (userInfo.state.preOrderInfo.setShip[shipKey] === shipOption[shipKey]) {
// return
// }
let updatedShipOption = {}
Object.keys(shipOption).forEach(k => {
updatedShipOption = userInfo.state.preOrderInfo.setShip
? { ...userInfo.state.preOrderInfo.setShip, [k]: shipOption[k] }
: shipOption
})
userInfo.setState({
preOrderInfo: {
...userInfo.state.preOrderInfo,
setShip: updatedShipOption
}
})
// Make request to change shipping option
const { preOrderInfo } = userInfo.state
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(preOrderInfo),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
}
async componentDidMount() {
if (cartState.state.tab === 2) {
const { shipping } = userInfo.state
const { items, coupon } = itemState.state
let updated = { ...shipping }
const names = updated.shippingFullName.split(' ')
updated.shippingFirst = names[0]
updated.shippingLast = names[1]
delete updated.shippingFullName
updated.site = cartState.state.site
updated.products = items
updated.couponCode = coupon
updated.addressSame = userInfo.state.addressSame
cartState.setState({
loading: true
})
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(updated),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
return
shippingRes.products.forEach(product => {
const regexp = new RegExp(product.id, 'gi')
const updatedItem = items.find(({ id }) => regexp.test(id))
if (!updatedItem) {
console.warn('Item not found and being removed from the array')
const index = itemState.state.items.indexOf(updatedItem)
const updated = [...itemState.state.items]
updated.splice(index, 1)
itemState.setState({
items: updated
})
return
}
updatedItem.price = product.price
itemState.setState({
items: itemState.state.items.map(
item => (item.id === product.id ? updatedItem : item)
)
})
})
updated.shippingOptions = shippingRes.shippingOptions
Object.keys(updated.shippingOptions).forEach(k => {
this.setState({
shippingOption: { ...this.state.shippingOption, [k]: 0 }
})
updated.setShip = updated.setShip
? { ...updated.setShip, [k]: 0 }
: { [k]: 0 }
})
updated.success = shippingRes.success
updated.cartId = shippingRes.cartId
updated.locations = shippingRes.locations
userInfo.setState({
preOrderInfo: updated
})
cost.setState({
tax: shippingRes.tax,
shipping: shippingRes.shipping,
shippingOptions:
Object.keys(updated.shippingOptions).length > 0
? updated.shippingOptions
: null
})
cartState.setState({
loading: false,
apiErrors: shippingRes.errors.length > 0 ? shippingRes.errors : null
})
if (shippingRes.errors.length > 0) {
removeCookies()
shippingRes.errors.forEach(err => {
if (err.includes('CRT-1-00013')) {
itemState.setState({ coupon: '' })
}
})
}
}
}
render() {
return (
<Subscribe to={[cartState, cost, itemState]}>
{(cart, cost, itemState) => {
if (cart.loading) {
return (
<div className="Loading">
<div className="Loader">
<FoldingCube size={50} color="rgb(0, 207, 255)" />
</div>
</div>
)
}
if (cart.apiErrors) {
return (
<div className="ShippingErrors">
<div className="ErrorsTitle">
Please Contact Customer Support
</div>
<div className="ErrorsContact">
(contact information for customer support)
</div>
<div className="Msgs">
{cart.apiErrors.map((error, i) => {
return (
<div key={i} className="Err">
{error}
</div>
)
})}
</div>
<style jsx>{styles}</style>
</div>
)
}
return (
<div className="ShippingOptionsContainer">
<div className="ShippingOptions">
{cost.shippingOptions ? (
<div className="ShipOptionLine">
{Object.keys(cost.shippingOptions).map((k, i) => {
const shipOptions = cost.shippingOptions[k]
const updatedProducts =
shipOptions.products.length === 0
? []
: shipOptions.products.map(product =>
itemState.items.find(
item => item.id === product.id
)
)
return (
<div className="ShippingInputs" key={i}>
{shipOptions.options.map((shipOption, i) => {
return (
<div className="ShippingSection" key={i}>
<div className="SectionTitle">
4. {shipOption.name} Shipping Options
</div>
{updatedProducts.length > 0 ? (
<div className="ShippingProducts">
{updatedProducts.map((product, i) => (
<div key={i}>
for{' '}
{shipOption.name === 'Freight'
? 'Large'
: 'Small'}{' '}
{product.name} from{' '}
{k.charAt(0).toUpperCase() + k.slice(1)}
</div>
))}
</div>
) : null}
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
</div>
)
})}
</div>
)
})}
</div>
) : null}
</div>
<style jsx>{styles}</style>
</div>
)
}}
</Subscribe>
)
}
}
Its because your app component is a wrap in StrictMode.
<React.StrictMode>
<App />
</React.StrictMode>,
If you are using create-react-app then it is found in index.js
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and later restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
https://github.com/facebook/react/issues/12856#issuecomment-390206425
The problem is html related, rather than React related. By default clicking a label will also trigger the onClick event of the input element it is associated with. In your case the onClick event is attached to both the label and the input. So by clicking on the label, the event is fired twice: once for the label and once for the input associated with it.
Edit: Attaching the onClick listener to the input is a possible solution to the problem
Prevent calling twice by using e.preventDefault().
changeShipping(e){
e.preventDefault();
console.log('clicked');
}
e.stopPropagation() is also worth exploring. I was handling an onMouseDown event, and preventDefault was not preventing multiple calls.
https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
In my case, it needs both to avoid redundant calls
<React.StrictMode>
<App />
</React.StrictMode>
from Nisharg Shah
e.preventDefault();
from Amruth
and one more thing for functional component where useEffect() method is called twice for me is bcoz, there were only one useEffect used with holds all methods to bind in FC (functional component) and dependency list. so after the change it looks like as follows:
useEffect(() => {
console.log("AFTER CHANGE : ", data) // move to below method
handleSubmit.bind(this);
handleCancel.bind(this);
testChange.bind(this);
}, [
data // move to below method
]);
useEffect(() => {
console.log("AFTER CHANGE : ", data)
}, [data]);
we should have without dependency list for bindings
To view the changed data after onChange(), then we should have useEffect with dependencyList of the data we are looking for.
Hope this helps. Happy coding . . . .

React search (filter) function, add parameter to search

****EDIT****: shouldnt be as easy as add: client.clientno.toLowerCase().indexOf(filter) !== -1 || client.id === filter? in the submitFilter function? The problem here is that I get an error "TypeError: Cannot read property 'toLowerCase' of null
(anonymous function)
Im trying to edit a filter search function I've got, but Im not understanding where to add the new parameter to search or if I should deconstruct the object "client" to search more specifically. Right now I can only search by client.text (which is the client name) but I want also be able to search by clientno (client number), which is not a number but a string that looks like this: "C123456". Can someone explain where should i add my client.clientnoparameter and how can I filter by that parameter without loosing the filter by client.text?
my code with the "search filter": (THank you very much in advance to any help!)
import React, {Component} from 'react';
import {Form, FormGroup, Input} from 'reactstrap';
import {StatusList} from './ClientList';
import {FormattedMessage, injectIntl} from 'react-intl';
class _SearchClientsContainer extends Component {
constructor(props) {
super(props);
this.timer = null;
this.state = {
filter: null,
clients: this.props.clients,
};
}
changeFilter = event => {
event.persist();
clearTimeout(this.timer);
this.setState({filter: event.target.value});
this.timer = setTimeout(() => {
this.submitFilter(event);
}, 200);
};
submitFilter = event => { //<---------- my submit filter "search" function
event.preventDefault();
const filter = this.state.filter ? this.state.filter.toLowerCase() : '';
const clients = filter.length === 0 ? this.props.clients : this.props.clients.filter(client => {
return (
client.text.toLowerCase().indexOf(filter) !== -1 || client.id === filter
// <----- here I return the client.text and I need to filter by client.clientno as well which is a string that looks like this: C123456
);
});
this.setState({clients});
};
render() {
return (
<>
<div>
<h3 className="mb-3">
<FormattedMessage id="header.navbar.clientlist.title" />
</h3>
<Form className="mb-4" onSubmit={this.submitFilter}>
<FormGroup>
<Input
type="text"
placeholder={this.props.intl.formatMessage({id: 'header.navbar.clientlist.filter'})}
onChange={this.changeFilter}
/>
</FormGroup>
</Form>
</div>
<StatusList clients={this.state.clients} />
</>
);
}
}
export const SearchClientsContainer = injectIntl(_SearchClientsContainer);
I forgot to mention, this is how I display the list:
import React, {memo} from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
const StatusItem = ({client}) => {
return (
<p className="mb-2 cursor-pointer" onClick={() => (window.location.href = client.viewLink)}>
<strong>{client.text}</strong> {client.clientno && <span>({client.clientno})</span>}
</p>
);
};
StatusItem.propTypes = {
client: PropTypes.object.isRequired,
};
const _StatusList = ({clients}) => {
const favClients = clients.filter(c => c.favorite);
const normalClients = clients.filter(c => !c.favorite);
return (
<div>
<h3 className="mb-3">
<FormattedMessage id="header.navbar.clientlist.favourites" />
</h3>
<div className="mb-4">
{favClients.map(c => (
<StatusItem client={c} key={c.id} />
))}
</div>
<h3 className="mb-3">
<FormattedMessage id="header.navbar.clientlist.clients" />
</h3>
<div>
{normalClients.map(c => (
<StatusItem client={c} key={c.id} />
))}
</div>
</div>
);
};
_StatusList.propTypes = {
clients: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export const StatusList = memo(_StatusList);
For TypeError: Cannot read property 'toLowerCase' of null (anonymous function)
(client && client.toLowerCase().clientno ? client.clientno : '').toLowerCase().indexOf(filter) !== -1 || client.id === filter
Also you can filter like this
this.props.clients.filter(client => {
return (
client.text.toLowerCase().includes(filter)
|| client.clientno.toLowerCase().includes(filter)
|| client.id === filter
);
});

Why state array is not populated when component is rendered?

I have created a component that sorts and filters an HTML table. The functionality is correct but I have a problem where my table renders "No asset records found." but when I click on one of the headers it displays the contents of the data array in state. I am truly stuck and confused on this strange behaviour. I think the problem might be with the filterAssets function because if I change from this:
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
to this:
let filterAssets = this.props.assetManagement.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
Here is the code below if it helps
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { getAssetManagement } from '../../actions/asset-management'
class AssetManagement extends Component {
static propTypes = {
assetManagement: PropTypes.array.isRequired,
getAssetManagement: PropTypes.func.isRequired
}
componentDidMount() {
this.props.getAssetManagement()
}
state = {
name: '',
search: '',
data: []
}
sortBy = this.sortBy.bind(this)
compareBy = this.compareBy.bind(this)
onSubmit = e => {
e.preventDefault()
}
onChange = e =>
this.setState({
[e.target.name]: e.target.value
})
updateSearch = e =>
this.setState({
search: e.target.value.substr(0, 20)
})
compareBy(key) {
return (a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
}
}
sortBy(key) {
let arrayCopy = [...this.props.assetManagement]
this.state.data.sort(this.compareBy(key))
this.setState({ data: arrayCopy })
}
render() {
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
return (
<Fragment>
{/* Search input */}
<div class="input-group mb-1">
<div class="input-group-prepend">
<span class="input-group-text btn-secondary">
<i class="fas fa-search" />
</span>
</div>
<input
className="form-control"
type="text"
placeholder="Search Asset"
onChange={this.updateSearch.bind(this)}
value={this.state.search}
/>
</div>
{/* Asset management table */}
<div className="table-responsive">
<table className="table table-bordered text-center">
<thead>
<tr>
<th onClick={() => this.sortBy('id')}>ID</th>
<th onClick={() => this.sortBy('name')}>Name</th>
</tr>
</thead>
<tbody>
{filterAssets != 0 ? (
filterAssets.map(a => (
<tr key={a.id}>
<td>{a.id}</td>
<td>{a.name}</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No asset records found.</td>
</tr>
)}
</tbody>
</table>
</div>
</Fragment>
)
}
}
const mapStateToProps = state => ({
assetManagement: state.assetManagement.assetManagement
})
export default connect(
mapStateToProps,
{ getAssetManagement }
)(AssetManagement)
Change filterAssets != 0 to filterAssets.length > 0
One first render:
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
Your this.state.data is empty, only this.props.assetManagement available if you handle redux properly so no wonder it you cannot get anything from filtering.
Btw: filterAssets != 0 is absolutely wrong, so go ahead and change this line first.
When you use the alternative syntax for a React Component without using a constructor you no longer have access to props. So if you go back to using a standard constructor the problem disappears, e.g.:
constructor(props) {
super(props);
this.state = {
name: "",
search: "",
data: this.props.assetManagement
};
this.sortBy = this.sortBy.bind(this);
this.compareBy = this.compareBy.bind(this);
}
The real problem you have here is that you have two source of data: state.data and props.assetManagement - you retrieve from redux and get newest data from props.assetManagement, but when you need to trigger sorting, you make a copy to state.data. Then problem arises since you don't copy from props.assetManagement to state.data until you trigger sortBy function.
A solution for that is to get rid of state.data and store the sorting key in state. You can update, reset that key value, and sorting logic should be apply to props.assetManagement only:
class AssetManagement extends Component {
static propTypes = {
assetManagement: PropTypes.array.isRequired,
getAssetManagement: PropTypes.func.isRequired
}
componentDidMount() {
this.props.getAssetManagement()
}
state = {
name: '',
search: '',
sortingKey: ''
}
sortBy = this.sortBy.bind(this)
compareBy = this.compareBy.bind(this)
onSubmit = e => {
e.preventDefault()
}
onChange = e =>
this.setState({
[e.target.name]: e.target.value
})
updateSearch = e =>
this.setState({
search: e.target.value.substr(0, 20)
})
compareBy(key) {
return (a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
}
}
sortBy(key) {
if (key !== this.state.sortingKey) {
this.setState({ sortingKey: key });
}
}
render() {
let sortAssets = !!this.state.sortingKey ?
this.props.assetManagement.sort(this.compareBy(this.state.sortingKey)) :
this.props.assetManagement;
let filterAssets = sortAssets.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
});
return (
<Fragment>
{/* Search input */}
<div class="input-group mb-1">
<div class="input-group-prepend">
<span class="input-group-text btn-secondary">
<i class="fas fa-search" />
</span>
</div>
<input
className="form-control"
type="text"
placeholder="Search Asset"
onChange={this.updateSearch.bind(this)}
value={this.state.search}
/>
</div>
{/* Asset management table */}
<div className="table-responsive">
<table className="table table-bordered text-center">
<thead>
<tr>
<th onClick={() => this.sortBy('id')}>ID</th>
<th onClick={() => this.sortBy('name')}>Name</th>
</tr>
</thead>
<tbody>
{filterAssets != 0 ? (
filterAssets.map(a => (
<tr key={a.id}>
<td>{a.id}</td>
<td>{a.name}</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No asset records found.</td>
</tr>
)}
</tbody>
</table>
</div>
</Fragment>
)
}
}
Sample code: https://codesandbox.io/s/n91pq7073l

Why is my function being called twice in React?

I have an idea that this may be because I am doing some styling things to change my radio button, but I am not sure. I am setting an onClick event that is calling my function twice. I have removed it to make sure it wasn't being triggered somewhere else and the onClick seems to be the culprit.
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
my function is just for now a simple console log of the shipping option:
changeShipping(shipOption){
console.log('clicked') // happening twice
}
If there isn't any reason you see here why this would happen I can post the rest of the code, but there is a lot and I don't think it would pertain to this, but I think this is a good starting place.
Full code:
import React, { Component } from 'react'
import fetch from 'isomorphic-fetch'
import { Subscribe } from 'statable'
import { FoldingCube } from 'better-react-spinkit'
import styles from './styles'
import { cost, cartState, userInfo, itemState, Api } from '../../state'
import { removeCookies, resetCart } from '../../../injectState'
export default class ShippingOptions extends Component {
constructor(props) {
super(props)
this.state = {
shippingOption: {}
}
this.changeShipping = this.changeShipping.bind(this)
}
async changeShipping(shipOption) {
const shipKey = Object.keys(shipOption)[0]
// if (userInfo.state.preOrderInfo.setShip[shipKey] === shipOption[shipKey]) {
// return
// }
let updatedShipOption = {}
Object.keys(shipOption).forEach(k => {
updatedShipOption = userInfo.state.preOrderInfo.setShip
? { ...userInfo.state.preOrderInfo.setShip, [k]: shipOption[k] }
: shipOption
})
userInfo.setState({
preOrderInfo: {
...userInfo.state.preOrderInfo,
setShip: updatedShipOption
}
})
// Make request to change shipping option
const { preOrderInfo } = userInfo.state
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(preOrderInfo),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
}
async componentDidMount() {
if (cartState.state.tab === 2) {
const { shipping } = userInfo.state
const { items, coupon } = itemState.state
let updated = { ...shipping }
const names = updated.shippingFullName.split(' ')
updated.shippingFirst = names[0]
updated.shippingLast = names[1]
delete updated.shippingFullName
updated.site = cartState.state.site
updated.products = items
updated.couponCode = coupon
updated.addressSame = userInfo.state.addressSame
cartState.setState({
loading: true
})
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(updated),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
return
shippingRes.products.forEach(product => {
const regexp = new RegExp(product.id, 'gi')
const updatedItem = items.find(({ id }) => regexp.test(id))
if (!updatedItem) {
console.warn('Item not found and being removed from the array')
const index = itemState.state.items.indexOf(updatedItem)
const updated = [...itemState.state.items]
updated.splice(index, 1)
itemState.setState({
items: updated
})
return
}
updatedItem.price = product.price
itemState.setState({
items: itemState.state.items.map(
item => (item.id === product.id ? updatedItem : item)
)
})
})
updated.shippingOptions = shippingRes.shippingOptions
Object.keys(updated.shippingOptions).forEach(k => {
this.setState({
shippingOption: { ...this.state.shippingOption, [k]: 0 }
})
updated.setShip = updated.setShip
? { ...updated.setShip, [k]: 0 }
: { [k]: 0 }
})
updated.success = shippingRes.success
updated.cartId = shippingRes.cartId
updated.locations = shippingRes.locations
userInfo.setState({
preOrderInfo: updated
})
cost.setState({
tax: shippingRes.tax,
shipping: shippingRes.shipping,
shippingOptions:
Object.keys(updated.shippingOptions).length > 0
? updated.shippingOptions
: null
})
cartState.setState({
loading: false,
apiErrors: shippingRes.errors.length > 0 ? shippingRes.errors : null
})
if (shippingRes.errors.length > 0) {
removeCookies()
shippingRes.errors.forEach(err => {
if (err.includes('CRT-1-00013')) {
itemState.setState({ coupon: '' })
}
})
}
}
}
render() {
return (
<Subscribe to={[cartState, cost, itemState]}>
{(cart, cost, itemState) => {
if (cart.loading) {
return (
<div className="Loading">
<div className="Loader">
<FoldingCube size={50} color="rgb(0, 207, 255)" />
</div>
</div>
)
}
if (cart.apiErrors) {
return (
<div className="ShippingErrors">
<div className="ErrorsTitle">
Please Contact Customer Support
</div>
<div className="ErrorsContact">
(contact information for customer support)
</div>
<div className="Msgs">
{cart.apiErrors.map((error, i) => {
return (
<div key={i} className="Err">
{error}
</div>
)
})}
</div>
<style jsx>{styles}</style>
</div>
)
}
return (
<div className="ShippingOptionsContainer">
<div className="ShippingOptions">
{cost.shippingOptions ? (
<div className="ShipOptionLine">
{Object.keys(cost.shippingOptions).map((k, i) => {
const shipOptions = cost.shippingOptions[k]
const updatedProducts =
shipOptions.products.length === 0
? []
: shipOptions.products.map(product =>
itemState.items.find(
item => item.id === product.id
)
)
return (
<div className="ShippingInputs" key={i}>
{shipOptions.options.map((shipOption, i) => {
return (
<div className="ShippingSection" key={i}>
<div className="SectionTitle">
4. {shipOption.name} Shipping Options
</div>
{updatedProducts.length > 0 ? (
<div className="ShippingProducts">
{updatedProducts.map((product, i) => (
<div key={i}>
for{' '}
{shipOption.name === 'Freight'
? 'Large'
: 'Small'}{' '}
{product.name} from{' '}
{k.charAt(0).toUpperCase() + k.slice(1)}
</div>
))}
</div>
) : null}
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
</div>
)
})}
</div>
)
})}
</div>
) : null}
</div>
<style jsx>{styles}</style>
</div>
)
}}
</Subscribe>
)
}
}
Its because your app component is a wrap in StrictMode.
<React.StrictMode>
<App />
</React.StrictMode>,
If you are using create-react-app then it is found in index.js
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and later restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
https://github.com/facebook/react/issues/12856#issuecomment-390206425
The problem is html related, rather than React related. By default clicking a label will also trigger the onClick event of the input element it is associated with. In your case the onClick event is attached to both the label and the input. So by clicking on the label, the event is fired twice: once for the label and once for the input associated with it.
Edit: Attaching the onClick listener to the input is a possible solution to the problem
Prevent calling twice by using e.preventDefault().
changeShipping(e){
e.preventDefault();
console.log('clicked');
}
e.stopPropagation() is also worth exploring. I was handling an onMouseDown event, and preventDefault was not preventing multiple calls.
https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
In my case, it needs both to avoid redundant calls
<React.StrictMode>
<App />
</React.StrictMode>
from Nisharg Shah
e.preventDefault();
from Amruth
and one more thing for functional component where useEffect() method is called twice for me is bcoz, there were only one useEffect used with holds all methods to bind in FC (functional component) and dependency list. so after the change it looks like as follows:
useEffect(() => {
console.log("AFTER CHANGE : ", data) // move to below method
handleSubmit.bind(this);
handleCancel.bind(this);
testChange.bind(this);
}, [
data // move to below method
]);
useEffect(() => {
console.log("AFTER CHANGE : ", data)
}, [data]);
we should have without dependency list for bindings
To view the changed data after onChange(), then we should have useEffect with dependencyList of the data we are looking for.
Hope this helps. Happy coding . . . .

Categories

Resources