The input textbox is not letting me insert a value - reactjs

This is the updated code now. Let me know if something is not correct as I am able to compile but the issue still persists
constructor(props) {
super(props);
this.polyglot = PolyglotFactory.getPolyglot(props.pageLang);
this.state = {
otherInvestorSubtype: props.otherInvestorSubtype,
};
}
shouldRenderOtherSubtype = () => this.props.otherInvestorSubtype === OTHER_INVESTOR_SUBTYPE;
shouldRenderSubtype = () => {
const { investorTypeOptions, investorType } = this.props;
const investorTypeOption = investorTypeOptions.find(({ value }) => value === investorType);
return investorTypeOption !== undefined && investorTypeOption.subtypes.length > 0;
}
handleOtherInvestorSubtypeChange = (e) => {
this.setState({
otherInvestorSubtype: e.target.value,
});
this.props.handleOtherInvestorSubtypeChange();
}
renderSelectOtherSubtype = () => {
const { handleOtherInvestorSubtypeChange,
showError, registerChildValidator, pageLang } = this.props;
const { otherInvestorSubtype } = this.state;
return (
<ValidatedText
name="investor_subtype_other"
value={otherInvestorSubtype}
onChange={this.handleOtherInvestorSubtypeChange}
showError={showError}
registerValidation={registerChildValidator}
validation={validation(this.polyglot.t('inputs.investorTypes'), pageLang, rules.required())}
required
/>
);
}
This is the only information I have got for this. Let me know if something is missing.

It seems like you've set the textbox value as otherInvestorSubtype which is provided by the props. This way, the component (so that the textbox value) is not updated on textbox value change. You need to store the otherInvestorSubtype value provided by props in the InvestorType component's state as the following, in order to update the InvestorType component every time the user types something:
constructor(props) {
...
this.state {
otherInvestorSubtype: props.otherInvestorSubtype
}
}
change the renderSelectOtherSubtype method as the following:
renderSelectOtherSubtype = () => {
const { handleOtherInvestorSubtypeChange, showError, registerChildValidator, pageLang } = this.props;
const { otherInvestorSubtype } = this.state
return (
<ValidatedText
...
value={ otherInvestorSubtype }
onChange={ this.handleOtherInvestorSubtypeChange }
...
/>
);
}
and finally handle the textbox change on this component:
handleOtherInvestorSubtypeChange = (e) => {
this.setState({
otherInvestorSubtype: e.target.value
});
this.props.handleOtherInvestorSubtypeChange();
}
Hope this helps, and sorry if I have any typos.

Related

Next/React - How to correctly build up state from multiple child components

I think this is easier to explain using a codesandbox link. This follows on from a previous question of mine, which may help provide more overall context. Currently, when interacting with the child elements (i.e. inputs), the state updates to {"values":{"0":{"Text1":"test"},"1":{"bool":true}}}. The issue is that if you interact with the other inputs within a Parent component, e.g. Text2 in the Parent component with id 0, it will overwrite the value already in the state, which makes it look like this {"values":{"0":{"Text2":"test"},"1":{"bool":true}}}. I want it to look like {"values":{"0":{"Text1":"test", "Text2":"test"},"1":{"bool":true}}}.
This is my try with your problem. I would like to have childIndex instead of number like you. It would be easier to work with other components later.
Here is my codesandbox
import { useEffect, useState } from "react"
import Parent from "./Parent"
const id1 = 0
const id2 = 1
interface Boo {
childIndex: number
value: {
[name: string]: string | boolean
}
}
const GrandParent: React.FC = () => {
const [values, setValues] = useState<Boo[]>([])
const valuesChange = (e: React.ChangeEvent<HTMLInputElement>, id: number) => {
console.log("change event")
const name = e.target.name
let value: any
if (name === "bool") {
value = e.target.checked
} else {
value = e.target.value
}
setValues((prev) => {
// Update new value to values state if input already there
let updateBoo = prev.find((boo) => boo.childIndex === id)
if (updateBoo) {
// Update Values
const valKeys = Object.keys(updateBoo.value)
const valIndex = valKeys.find((val) => val === name)
if (valIndex) {
updateBoo.value[valIndex] = value
} else {
updateBoo.value = { ...updateBoo.value, [name]: value }
}
} else {
// Create new if not added
updateBoo = {
childIndex: id,
value: { [name]: value }
}
}
return [...prev.filter((boo) => boo.childIndex !== id), updateBoo]
})
}
useEffect(() => {
console.log("Render", { values })
})
return (
<>
<div>{JSON.stringify({ values }, undefined, 4)}</div>
<br />
<br />
<Parent valueChange={(e) => valuesChange(e, id1)} key={id1} />
<Parent valueChange={(e) => valuesChange(e, id2)} key={id2} />
</>
)
}
export default GrandParent
The trick is you should return the previous state of the property too:
setValues((prev) => {
return {
...prev,
[id]: {
...(prev && (prev[id] as {})), // <= return the previous property state
[name]: value
}
}
})
I'm not very good at typescript but I tried my best to solve some types' errors
you can see an example below

React Functional Component update property change

I am using react-star package
import ReactStars from "react-rating-stars-component";
It has one issue.
I need to the value to change on state change. But the value is not changing
I am changing the this.state.rating on ajax load.
and setting the value this.rating to be used in submit.
class CallUpdate extends Component<{ match: PropsWithRef<any> }> {
state = {
rating:0
}
rating = 0;
componentDidMount = async () => {
this.id = this.props.match.params.id;
const userCall = await axios.get(`call/show/${this.id}`);
const call: call= userCall.data.data;
this.setState({
rating: call.rating
});
}
submit = async (e: SyntheticEvent) => {
e.preventDefault();
const formData = new FormData();
formData.append('rating', this.rating);
//const formHeaders['Content-Type'] = 'multipart/form-data';
const config = {headers: {'Content-Type' :'multipart/form-data'}};
await axios.post(`call/${this.id}/update`, formData,config);
}
render() {
return (
<ReactStars
count={5}
value={this.state.rating}
onChange={(rating) => {this.rating = rating}}
size={24}
activeColor="#ffd700"
/>
);
}
}
export default CallUpdate;
////
react-starts-component I have added this function. it should be called on props change.
function updateValue(value){
if (value < 0 || value > count) {
setCurrentValue(0);
}
else {
setCurrentValue(value);
}
}
I tried changing the useEffect
useEffect(() => {
addClassNames();
validateInitialValue(props.value, props.count);
setStars(getStars(props.value));
setConfig(props);
createUniqueness();
setIsUsingIcons(iconsUsed(props));
setHalfStarAt(Math.floor(props.value));
setHalfStarHidden(props.isHalf && props.value % 1 < 0.5);
}, []);
to
useEffect(() => {
addClassNames();
validateInitialValue(props.value, props.count);
setStars(getStars(props.value));
setConfig(props);
createUniqueness();
setIsUsingIcons(iconsUsed(props));
setHalfStarAt(Math.floor(props.value));
setHalfStarHidden(props.isHalf && props.value % 1 < 0.5);
}, [props]);
But on state.rating change it is not updating the value.
How do I change the code to make it property change value?
The issue here is you are not updating the state but instead updating a property on class which doesn't cause a re-render and you are using lead whereas you have a call variable in componentDidMount which doesn't exists. You should use a state variable to hold data that might change overtime and reflect in UI.
class CallUpdate extends Component {
state = {
rating: 0
};
componentDidMount = async () => {
const id = this.props.match.params.id;
const userCall = await axios.get(`call/show/${this.id}`);
const call: call = userCall.data.data;
this.setState({
rating: call.rating
});
};
submit = async (e: SyntheticEvent) => {
e.preventDefault();
const formData = new FormData();
formData.append("rating", this.rating);
//const formHeaders['Content-Type'] = 'multipart/form-data';
const config = { headers: { "Content-Type": "multipart/form-data" } };
await axios.post(`call/${this.id}/update`, formData, config);
};
render() {
return (
<ReactStars
count={5}
value={this.state.rating}
onChange={(rating) => this.setState({rating})}
size={24}
activeColor="#ffd700"
/>
);
}
}
I had the same problem when value not updating from useMemo(). You just need to add key prop to ReactStars: key={value}

Cannot delete multiple items on react using firestore

I am trying to delete multiple items on click of checkbox using firestore. But, onSnapshot method of firestore is causing issue with the state.
After running the code I can click on checkbox and delete the items, the items get deleted too but I get an error page, "TyperError: this.setState is not a function" in onCollectionUpdate method.
After refreshing the page I can see the items deleted.
Here's my code:
class App extends React.Component {
constructor(props) {
super(props);
this.ref = firebase.firestore().collection('laptops');
this.unsubscribe = null;
this.state = { laptops: [], checkedBoxes: [] };
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.deleteProducts = this.deleteProducts.bind(this);
}
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState = { checkedBoxes: arr };
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState = {
checkedBoxes: items
}
}
}
deleteProducts = () => {
const ids = this.state.checkedBoxes;
ids.forEach((id) => {
const delRef = firebase.firestore().collection('laptops').doc(id);
delRef.delete()
.then(() => { console.log("deleted a laptop") })
.catch(err => console.log("There is some error in updating!"));
})
}
onCollectionUpdate = (querySnapshot) => {
const laptops = [];
querySnapshot.forEach((doc) => {
const { name, price, specifications, image } = doc.data();
laptops.push({
key: doc.id,
name,
price,
specifications,
image
});
});
this.setState({ laptops });
console.log(laptops)
}
componentDidMount = () => {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
getLaptops = () => {
const foundLaptops = this.state.laptops.map((laptop) => {
return (
<div key={laptop.key}>
<Container>
<Card>
<input type="checkbox" className="selectsingle" value="{laptop.key}" checked={this.state.checkedBoxes.find((p) => p.key === laptop.key)} onChange={(e) => this.toggleCheckbox(e, laptop)} />
...carddata
</Card>
</Container>
</div>
);
});
return foundLaptops;
}
render = () => {
return (
<div>
<button type="button" onClick={this.deleteProducts}>Delete Selected Product(s)</button>
<div className="row">
{this.getLaptops()}
</div>
</div>
);
}
}
export default App;
In the toggleCheckbox function you set the this.setState to a object.
You will need to replace that with this.setState({ checkedBoxes: items})
So you use the function instead of setting it to a object
You probably just forgot to bind the onCollectionUpdate so this referes not where you expectit to refer to.
Can you pls also change the this.setState bug you have there as #David mentioned also:
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState({ checkedBoxes: arr });
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState({
checkedBoxes: items
})
}
}
If you already did that pls update your question with the latest code.

Two components get called when page refreshes and the state is altered React

Basically, I have one component, let's call it component1 and a second component, which has been created by duplicating the first one called component2. I had to duplicate it, because some objects inside it had to be altered before sending them to the further components.
On one page I have an onClick event which triggers component1 which opens a modal and on another page, component2 is trigger the same as for the first one.
The problem occurs here, if I'm on the second page where the modal from component2 is opened and I refresh the page, both components are called, of course component1 is the first one called and the state is altered by this component which makes me not having the desired information in the second component.
As far as I understood, because of the fact that in both components, mapStateToProps is altering my state, both components are called. Not really sure though that I understood right.
Here is my component1 summary:
class LivePlayerModal extends React.Component {
constructor(props) {
super(props);
this.highlightsUpdated = null;
}
componentDidMount() {
const queryParam = UrlHelper.getParamFromLocation(IS_QUALIFICATION, window.location);
if (queryParam === null) {
ScoringLoader.subscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.subscribe(endpointNames.PLAYERS);
ScoringLoader.subscribe(endpointNames.LEADERBOARD);
ScoringLoader.subscribe(endpointNames.COURSE);
ScoringLoader.subscribe(endpointNames.STATISTICS);
}
//TODO: make fixed fetch on timeout
this.fetchHighlights();
}
componentDidUpdate(prevProps) {
if (prevProps.playerId !== this.props.playerId) {
this.highlightsUpdated = null;
}
this.fetchHighlights();
}
componentWillUnmount() {
ScoringLoader.unsubscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.unsubscribe(endpointNames.PLAYERS);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
}
render() {
const {
isOpen, scoringPlayer, isQualification, ...rest
} = this.props;
const highlightGroups = getHighlights(this.getCloudHighlights());
if (isQualification) {
return null;
}
return (
<ReactModal isOpen={isOpen} onCloseCb={this.hide}>
<div className="live-player">
{
scoringPlayer === undefined &&
<BlockPlaceholder minHeight={400}>
<BlockSpinner />
</BlockPlaceholder>
}
{
scoringPlayer === null &&
<LivePreMessage
model={{
title: '',
body: 'Player data coming soon'
}}
bemList={[bemClasses.LIGHT]}
/>
}
{
scoringPlayer &&
<LivePlayerLayout
{...rest}
scoringPlayer={scoringPlayer}
highlightGroups={highlightGroups}
/>
}
</div>
</ReactModal>
);
}
}
const mapStateToProps = (state, ownProps) => {
const isQualification = state.scoring.isQualification;
const { playerId } = ownProps;
const sitecorePlayers = state.scoring[endpointNames.PLAYERS];
const scoringLeaderboard = state.scoring[endpointNames.LEADERBOARD];
const getScoringPlayer = () => {
};
return ({
isQualification,
liveScanner: state.scoring[endpointNames.LIVE_SCANNER],
scoringLeaderboard,
scoringPlayer: getScoringPlayer(),
scoringStats: state.scoring[endpointNames.STATISTICS],
scoringCourse: state.scoring[endpointNames.COURSE],
sitecorePlayers: state.scoring[endpointNames.PLAYERS],
cloudMatrix: state.cloudMatrix
});
};
const mapDispatchToProps = (dispatch) => ({
fetchPlayerHighlights: (feedUrl) => dispatch(fetchFeed(feedUrl))
});
const LivePlayerCardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(LivePlayerModal);
export default LivePlayerCardContainer;
Here is my component2 summary :
class QualificationLivePlayerModal extends React.Component {
constructor(props) {
super(props);
this.highlightsUpdated = null;
}
shouldComponentUpdate(nextProps) {
return nextProps.isQualification;
}
componentDidMount() {
ScoringLoader.subscribe(endpointNames.SUMMARY_FINAL);
ScoringLoader.subscribe(endpointNames.SUMMARY_REGIONAL);
ScoringLoader.subscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.subscribe(endpointNames.PLAYERS);
ScoringLoader.subscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
//TODO: make fixed fetch on timeout
this.fetchHighlights();
}
componentDidUpdate(prevProps) {
if (prevProps.playerId !== this.props.playerId) {
this.highlightsUpdated = null;
}
this.fetchHighlights();
}
componentWillUnmount() {
ScoringLoader.unsubscribe(endpointNames.SUMMARY_FINAL);
ScoringLoader.unsubscribe(endpointNames.SUMMARY_REGIONAL);
ScoringLoader.unsubscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
}
render() {
const {
scoringPlayer, summaryFinal, ...rest
} = this.props;
const highlightGroups = getHighlights(this.getCloudHighlights());
const queryParam = UrlHelper.getParamFromLocation(IS_QUALIFICATION, window.location);
const open = (queryParam === 'true');
if (scoringPlayer !== undefined && scoringPlayer !== null) scoringPlayer.id = scoringPlayer.entryId;
return (
<ReactModal isOpen={open} onCloseCb={this.hide}>
<div className="qual-live-player">
{
scoringPlayer === undefined &&
<BlockPlaceholder minHeight={400}>
<BlockSpinner />
</BlockPlaceholder>
}
{
scoringPlayer === null &&
<LivePreMessage
model={{
title: '',
body: 'Player data coming soon'
}}
bemList={[bemClasses.LIGHT]}
/>
}
{
scoringPlayer &&
<LivePlayerLayout
{...rest}
scoringPlayer={scoringPlayer}
highlightGroups={highlightGroups}
/>
}
</div>
</ReactModal>
);
}
}
const mapStateToProps = (state, ownProps) => {
const isQualification = state.scoring.isQualification;
const { playerId, location } = ownProps;
const locationIdFromQueryParam = UrlHelper.getParamFromLocation(LOCATION_ID, window.location);
const locationId = location !== null ? location.locationId : locationIdFromQueryParam;
const sitecorePlayers = state.scoring[endpointNames.PLAYERS];
const summaryRegional = state.scoring[endpointNames.SUMMARY_REGIONAL];
const summaryFinal = state.scoring[endpointNames.SUMMARY_FINAL];
const scoringLeaderboard = getLeaderboardBasedOnLocation(locationId, summaryFinal, summaryRegional);
const currentRound = getCurrentRound(locationId, summaryFinal, summaryRegional);
const getScoringPlayer = () => {
};
return ({
isQualification,
liveScanner: state.scoring[endpointNames.LIVE_SCANNER],
scoringLeaderboard,
scoringPlayer: getScoringPlayer(),
scoringCourse: getScoringCourseFromQualificationFeed(),
sitecorePlayers: state.scoring[endpointNames.PLAYERS],
cloudMatrix: state.cloudMatrix,
});
};
const mapDispatchToProps = (dispatch) => ({
fetchPlayerHighlights: (feedUrl) => dispatch(fetchFeed(feedUrl))
});
const QualificationLivePlayerCardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(QualificationLivePlayerModal);
export default QualificationLivePlayerCardContainer;
Basically, the problem i ve got here, is that in state.scoring I do not have the information for the endpoints present in the return statement of the render method before the page finishes the refresh process, which later on makes my app to break.
Hope I've been clear enough.
Is there a solution for waiting the endpoints to get called or even not loading the first component at all?

Flow Type complains about class property assigned via react ref

I've started using Flow type on top of a project created with create-react-app tool. I struggle to make a simple scenario work where a class property is filled with element reference in render method but throws 2 errors. What am I doing wrong? All the checks should prevent those warnings.
class MyComponent extends React.Component<*> {
input: ?HTMLInputElement;
componentDidUpdate = () => {
if (this.input) {
this.input.focus();
if (this.input.value) {
const valueLength = this.input.value.length;
this.input.setSelectionRange(valueLength, valueLength);
}
}
};
render() {
return <input ref={ref => (this.input = ref)} />;
}
}
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/todo/index.js:38:28
Property value is missing in null or undefined [1].
[1] 33│ input: ?HTMLInputElement;
34│
35│ componentDidUpdate = () => {
36│ if (this.input) {
37│ this.input.focus();
38│ if (this.input.value) {
39│ const valueLength = this.input.value.length;
40│ this.input.setSelectionRange(valueLength, valueLength);
41│ }
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/todo/index.js:40:28
Cannot call this.input.setSelectionRange because property setSelectionRange is missing in null or undefined [1].
[1] 33│ input: ?HTMLInputElement;
34│
35│ componentDidUpdate = () => {
36│ if (this.input) {
37│ this.input.focus();
38│ if (this.input.value) {
39│ const valueLength = this.input.value.length;
40│ this.input.setSelectionRange(valueLength, valueLength);
41│ }
42│ }
43│ };
Since you're calling methods, flow assumes that things could change at anytime. You need to keep a reference to the input and then you're all good. Something like below:
class MyComponent extends React.Component<*> {
input: ?HTMLInputElement;
componentDidUpdate = () => {
const { input } = this
if (input) {
input.focus();
if (input.value) {
const valueLength = input.value.length;
input.setSelectionRange(valueLength, valueLength);
}
}
};
render() {
return <input ref={ref => (this.input = ref)} />;
}
}

Resources