Cannot setState() on dynamically generated components - reactjs

Let keyComponent filters a string for keywords and return them with an event handler(to toggle them) and generates a state (targetState) within this.state. The problem is that if I click on any of the keywords the state isn't updated/changed. I can see all the states being generated in this.state through console.log. they are simply not updating when clicked, no errors either.
I would appreciate some help ;-)
import React from 'react';
import { render } from 'react-dom';
import { sectionUpsert } from '/imports/api/userProgress/upsertMethods.jsx';
export default class LetsCheck extends React.Component {
constructor(props) {
super(props);
this.state = {
reference: props.reference,
text: props.text,
keys: props.keys,
CorrectArray: [],
};
}
handleClick(e, TxtBit, targetState) {
console.log(targetState);
console.log(this.state.targetState);
let tempA = this.state.CorrectArray;
let target = targetState;
tempA.push(TxtBit);
let obj = { [target]: true, }
console.log(obj);
this.setState(obj);
// this.setState({
// CorrectArray: tempA,
// [target]: true,
// });
console.log(this.state);
}
handleUnclick(e, TxtBit, targetState) {
console.log('unclicked' + TxtBit + index);
}
componentWillUnmount() {
let keys = this.state.keys;
let correct = this.state.CorrectArray;
let keyWW = keys.filter(function(key){
return !correct.includes(key) && keys.indexOf(key) % 2 === 0
});
const secData = {
userId: Meteor.userId(),
ref: this.state.reference,
keyWR: this.state.CorrectArray,
keyWW: keyWW,
porSect: Math.round((this.state.CorrectArray.length / (keyWW.length + this.state.CorrectArray.length)) * 100),
};
sectionUpsert.call(secData);
}
render() {
let keys = this.state.keys;
let clicked = this.state;
let filter = keys.filter( function(key) {
return keys.indexOf(key) % 2 === 0;
});
let KeyComponent = this.state.text.map(function(TxtBit, index) {
let match = false;
let checkMatch = function(TxtBit, filter) {
for (var y = filter.length - 1; y >= 0; y--) {
if ( TxtBit == filter[y] ) {
match = true
}
}
};
checkMatch(TxtBit, filter);
if( match ) {
targetState = 'KeyBtn' + index;
clicked[targetState] = false;
return <a href="#" key={ index } style={{ display: `inline` }} onClick={ this.state[index] ? () => this.handleUnclick(this, TxtBit, targetState) : () => this.handleClick(this, TxtBit, targetState) } name={ TxtBit } className={ this.state[index] ? 'clicked': 'unclicked' } > { " "+TxtBit+ " " }</a>;
} else {
return <div key={ index } style={{ display: `inline` }} className="TxtBit"> { " "+TxtBit+ " " }</div>;
}
}.bind(this));
console.log(this.state);
return(
<div className="content">
<div className="content-padded">
<div> {KeyComponent}</div>
<p> { this.state.CorrectArray.length } / { this.state.keys.length / 2 } </p>
</div>
</div>
);
}
};

Try to bind them:
this.handleUnclick(this, TxtBit, targetState).bind(this)
or use arrow functions on handlers...
example: https://blog.josequinto.com/2016/12/07/react-use-es6-arrow-functions-in-classes-to-avoid-binding-your-methods-with-the-current-this-object/
Regards!

Related

In Ant Design "Rules" in the Form.Items of Form component are not working

I am trying to use the rules in the Form. Items but because of some reason it is not supporting
NOTE: The number is not supporting but that is not an issue rules feature is not supporting at all.
NOTE: I think because it is rendering a lot that is why rules are not getting applied.
Any help to solve the issue will be great.
Here is a form component Code
import React from 'react';
import { Form, Button } from 'antd';
import { component as Template } from '#ivoyant/component-template';
class InputFormComponent extends React.Component {
state = {
formData: {},
};
enableInputFocus = (State, Props) => {
const empty = {};
const isObject = x => Object(x) === x;
const diff1 = (left = {}, right = {}, rel = 'left') =>
Object.entries(left)
.map(([k, v]) =>
isObject(v) && isObject(right[k])
? [k, diff1(v, right[k], rel)]
: right[k] !== v
? [k, { [rel]: v }]
: [k, empty]
)
.reduce(
(acc, [k, v]) => (v === empty ? acc : { ...acc, [k]: v }),
empty
);
const merge = (left = {}, right = {}) =>
Object.entries(right).reduce(
(acc, [k, v]) =>
isObject(v) && isObject(left[k])
? { ...acc, [k]: merge(left[k], v) }
: { ...acc, [k]: v },
left
);
const diff = (x = {}, y = {}) =>
merge(diff1(x, y, 'left'), diff1(y, x, 'right'));
const key = Object.keys(diff(State, Props))[0];
for (const property in Props) {
if (key == property) {
Props[property].focus = true;
} else {
Props[property].focus = false;
}
}
return Props;
};
onFinish = values => {
const { formData } = this.state;
global.console.log('Success: formData :', formData);
};
getDataFromFormItem = values => {
const { formData } = this.state;
const updatedValues = this.enableInputFocus(formData, values);
this.setState({ formData: { ...formData, ...updatedValues } });
};
onFinishFailed = errorInfo => {
global.console.log('Failed:', errorInfo);
};
render() {
const { children, properties } = this.props;
const {
submitButtonText,
formClassName,
submitButtonClassNmae,
} = properties;
const { formData } = this.state;
const childComponent = React.Children.map(children, child => {
return React.cloneElement(child, {
sendDataToForm: this.getDataFromFormItem,
updateFormData: this.updateFormData,
values: { ...formData },
});
});
return (
<div>
<Form
className={formClassName && formClassName}
onFinish={this.onFinish}
onFinishFailed={this.onFinishFailed}
>
{childComponent}
<Form.Item>
<Button
type="primary"
htmlType="submit"
className={
submitButtonClassNmae && submitButtonClassNmae
}
>
{submitButtonText && submitButtonText}
</Button>
</Form.Item>
</Form>
</div>
);
}
}
export default InputFormComponent;
Below is the code for the Input field, which is a totally different component:
import React from 'react';
import { Input, Form } from 'antd';
class InputComponent extends React.Component {
state = {
formItemData: {},
};
componentDidMount() {
const { values } = this.props;
for (const property in values) {
if (values[property].focus === true) {
this[property] && this[property].focus();
}
}
/**
* Bellow code is use when we are not passing input component as a direct child of Form component
*/
if (this.props.parentProps)
for (const property in this.props.parentProps.values) {
if (this.props.parentProps.values[property].focus === true) {
this[property] && this[property].focus();
}
}
/**
* In Bellow code true condition is exected when we are not passing input component as a direct child of Form component
*/
this.props.parentProps
? this.setState({
formItemData: { ...values, ...this.props.parentProps.values },
})
: this.setState({ formItemData: { ...values } });
}
handleOnChange = event => {
const { formItemData } = this.state;
const { id, value } = event.target;
const ID = id;
const object = {};
object[ID] = {
VALUE: value,
focus: false,
};
this.setState(
{
formItemData: { ...formItemData, ...object },
},
() => {
this.props.sendDataToForm &&
this.props.sendDataToForm(this.state.formItemData);
/**
* Bellow code is use when we are not passing input component as a direct child of Form component
*/
this.props.parentProps &&
this.props.parentProps.sendDataToForm &&
this.props.parentProps.sendDataToForm(
this.state.formItemData
);
}
);
};
render() {
const { id, className } = this.props.properties;
const { values, children } = this.props;
const { formItemData } = this.state;
const { inputFieldsConfiguration } = this.props.properties;
const childComponent = React.Children.map(children, child => {
return React.cloneElement(child, {
parentProps: { ...this.props },
});
});
return (
<div>
<div className={className && className}>
{inputFieldsConfiguration &&
// eslint-disable-next-line complexity
inputFieldsConfiguration.map(data => {
return (
<div
className={data.itemAndInputClassName}
key={data.id && data.id}
>
<div className={data.labalClassName}>
{data.label && data.label}
</div>
<Form.Item
key={data.id}
hasFeedback
name={data.id}
help={data.help && data.help}
className={
data.formItemClassName &&
data.formItemClassName
}
rules={[
{
type: "url",
}
]}
>
<Input
ref={input => {
this[data.id] = input;
}}
placeholder={
data.placeholder &&
data.placeholder
}
id={data.id && data.id}
disabled ={data.disabled && data.disabled }
value={
formItemData &&
formItemData[data.id] &&
formItemData[data.id].ID === ''
? values &&
values[data.id] &&
values[data.id].VALUE
: formItemData &&
formItemData[data.id] &&
formItemData[data.id]
.VALUE
}
className={
data.inputItemClassName &&
data.inputItemClassName
}
onChange={e =>
this.handleOnChange(e)
}
/>
</Form.Item>
</div>
);
})}
</div>
{childComponent}
</div>
);
}
}
export default InputComponent;

Why does this Component does not work when converted to React-Hooks? (confused about this state and destructuring part)

basically attempting to create an Autocomplete feature for a booking engine the code is in class components want to convert it to a functional component with React Hooks.Have attempted to convert but my code is showing several warnings.can provide any code snippets if needed.
(how do you convert this.state and destructure the this keyword)
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
class AutocompleteClass extends Component {
static propTypes = {
suggestions: PropTypes.instanceOf(Array)
};
static defaultProps = {
suggestions: []
};
constructor(props) {
super(props);
this.state = {
// The active selection's index
activeSuggestion: 0,
// The suggestions that match the user's input
filteredSuggestions: [],
// Whether or not the suggestion list is shown
showSuggestions: false,
// What the user has entered
userInput: ""
};
}
onChange = e => {
const { suggestions } = this.props;
const userInput = e.currentTarget.value;
// Filter our suggestions that don't contain the user's input
const filteredSuggestions = suggestions.filter(
suggestion =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
this.setState({
activeSuggestion: 0,
filteredSuggestions,
showSuggestions: true,
userInput: e.currentTarget.value
});
};
onClick = e => {
this.setState({
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: e.currentTarget.innerText
});
};
onKeyDown = e => {
const { activeSuggestion, filteredSuggestions } = this.state;
// User pressed the enter key
if (e.keyCode === 13) {
this.setState({
activeSuggestion: 0,
showSuggestions: false,
userInput: filteredSuggestions[activeSuggestion]
});
}
// User pressed the up arrow
else if (e.keyCode === 38) {
if (activeSuggestion === 0) {
return;
}
this.setState({ activeSuggestion: activeSuggestion - 1 });
}
// User pressed the down arrow
else if (e.keyCode === 40) {
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
this.setState({ activeSuggestion: activeSuggestion + 1 });
}
};
render() {
const {
onChange,
onClick,
onKeyDown,
state: {
activeSuggestion,
filteredSuggestions,
showSuggestions,
userInput
}
} = this;
let suggestionsListComponent;
if (showSuggestions && userInput) {
if (filteredSuggestions.length) {
suggestionsListComponent = (
<ul class="suggestions">
{filteredSuggestions.map((suggestion, index) => {
let className;
// Flag the active suggestion with a class
if (index === activeSuggestion) {
className = "suggestion-active";
}
return (
<li className={className} key={suggestion} onClick={onClick}>
{suggestion}
</li>
);
})}
</ul>
);
} else {
suggestionsListComponent = (
<div class="no-suggestions">
<em>No suggestions, you're on your own!</em>
</div>
);
}
}
return (
<Fragment>
<input
type="text"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
{suggestionsListComponent}
</Fragment>
);
}
}
export default AutocompleteClass;

How to properly update users count using Reactjs

I have searched StackOverflow but most of the solutions found could not solve my issue at the moment.
The Code below displays users records from the array via reactjs.
Now I have the task to display users count on content display.
In Jquery/Javascript, I can achieve it using the function below
function updateCount(userId){
alert('userId: ' +userId);
var count = (userCount[userId] == undefined) ? 1 : userCount[userId] + 1;
userCount[userId] = count;
alert('Counter: ' +userCount[userId]);
console.log('Counter: ' +userCount[userId]);
$('#' + userId + ' label.userCount').html(count);
$('#' + userId + ' label.userCount').show();
}
Now in Reactjs, I created the following function below which I added to the main code but it throws error
Reactjs functions
updateCount = (userId) => {
alert('userId: ' +userId);
var count = (userCount[userId] == undefined) ? 1 : userCount[userId] + 1;
userCount[userId] = count;
alert('Counter: ' +userCount[userId]);
console.log('Counter: ' +userCount[userId]);
this.setState(state => ({
data: userCount[userId]
}));
}
Here is the error it shows
bundle.js:109480 Uncaught ReferenceError: userCount is not defined
at App._this.updateCount
Below is the entire code so far
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
userCount: []
};
}
componentDidMount() {
var userId = "100";
//var userCount = [];
//this.userCount = [];
this.setState({
data: [
{
id: "100",
name: "Luke"
},
{
id: "200",
name: "Henry"
},
{
id: "300",
name: "Mark"
}
]
});
this.updateCount = this.updateCount.bind(this);
}
updateCount = userId => {
alert("userId: " + userId);
var count = userCount[userId] == undefined ? 1 : userCount[userId] + 1;
userCount[userId] = count;
alert("Counter: " + userCount[userId]);
console.log("Counter: " + userCount[userId]);
this.setState(state => ({
data: userCount[userId]
}));
};
render() {
return (
<span>
<label>
<ul>
{this.state.data.map((person, i) => {
if (person.id == 100) {
this.updateCount(person.id);
return (
<div key={i}>
<div>
{" "}
{person.id}: {person.name}{" "}
</div>{" "}
</div>
);
} else {
this.updateCount(person.id);
}
})}
</ul>{" "}
</label>{" "}
</span>
);
}
}
Updated Section
It shows error of unexpected token with the code below
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
userCount: [],
};
}
componentDidMount() {
// componentWillMount(){
var userId= '100';
//var userCount = [];
//this.userCount = [];
this.setState({
data: [
{ id: "100", name: "Luke"},
{ id: "200", name: "Henry"},
{ id: "300", name: "Mark" }
]
});
this.updateCount = this.updateCount.bind(this);
}
updateCount = (userId) => {
//alert('userId: ' +userId);
const {userCount} = this.state;
var count = userCount[userId] == undefined ? 1 : userCount[userId] + 1;
//var count = (userCount[userId] == undefined) ? 1 : userCount[userId] + 1;
userCount[userId] = count;
alert('Counter kk000000me: ' +userCount[userId]);
console.log('Counter: ' +userCount[userId]);
this.setState({data11: count});
}
const result = this.state.data.map((person, i) => {
if (person.id == 100) {
this.updateCount(person.id); //Every time this is called, it triggers setState
return (
<div key={i}>
<div>
{" "}
{person.id}: {person.name}{" "}
</div>{" "}
</div>
);
} else {
this.updateCount(person.id);
}
});
render() {
return (
<span>
<label>
<ul> {result} </ul>
</label>
</span>
);
}
To access your state variables, you need to do it like
this.state.userCount
In your example you're trying to access your userCount without referring to it in the state.
var count = userCount[userId] == undefined ? 1 : userCount[userId] + 1;
You should instead do:
const {userCount} = this.state;
var count = userCount[userId] == undefined ? 1 : userCount[userId] + 1;
Edit
Warning: Cannot update during an existing state transition (such as within render or another component's constructor)
To fix this issue, you have to ensure that your component is not triggering setState inside of itself. In your snippet, when you map over your data, it calls updateCount which then calls this.setState() which will trigger a re-render.
One solution would be to assign the result of your map to a const, and the render it.
const result = this.state.data.map((person, i) => {
if (person.id == 100) {
this.updateCount(person.id); //Every time this is called, it triggers setState
return (
<div key={i}>
<div>
{" "}
{person.id}: {person.name}{" "}
</div>{" "}
</div>
);
} else {
this.updateCount(person.id);
}
});
...
render() {
return (
<span>
<label>
<ul> {result} </ul>
</label>
</span>
);
}

React ref assignment inconsistencies when using a mapping function?

I'm setting up a virtualized scrolling component in React, and using refs with a recycled observer to notify the app when to prepare another batch of data. Inside my Grid component, I map over the current batch of data and assign a ref to a sentinel div, except that ref returns null in componentDidMount(). I don't understand why since componentDidMount fires after render executes, so the reference should be available.
The only workaround to this I've found is using this janky solution in my componentDidMount: setTimeout(() => this.observer.observe(this.targetRef.current), 0);.
import React, { Component, createRef } from "react";
class Grid extends Component {
constructor(props) {
super(props);
this.state = {
batch: []
};
this.observer = null;
this.targetRef = null;
this.lastRowFirstVisible =
props.rowCount * props.columnCount - props.columnCount;
this.config = {
rootMargin: "0px",
threshold: 1
};
this.setTargetRef = element => {
this.targetRef = element;
};
}
componentDidMount() {
const { startIndex, numberToDisplay } = this.props;
this.setBatch(startIndex, numberToDisplay);
this.observer = new IntersectionObserver(function(entries, self) {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(entry);
// self.unobserve(entry.target);
}
});
}, this.config);
setTimeout(() => this.observer.observe(this.targetRef), 0);
}
setBatch = (startIndex, numberToDisplay) => {
const batch = this.getBatch(startIndex, numberToDisplay);
this.setState({ batch });
};
getBatch = (startIndex, numberToDisplay) => {
const { data } = this.props;
return data.slice(startIndex, numberToDisplay);
};
// TO DO
updateObserver = () => {
this.observer.observe(this.targetRef.current);
};
render() {
const { lastRowFirstVisible } = this;
const { batch } = this.state;
const { elementWidth, elementHeight } = this.props;
console.log(lastRowFirstVisible);
return (
<>
{batch.map((element, localIndex) => {
const { index } = element;
console.log(localIndex === lastRowFirstVisible);
return localIndex === lastRowFirstVisible ? (
<div
id={index}
key={index}
style={{ width: elementWidth, height: elementHeight }}
className="card"
ref={this.setTargetRef}
>
{this.props.renderRow(element)}
</div>
) : (
<div
key={index}
style={{ width: elementWidth, height: elementHeight }}
className="card"
>
{this.props.renderRow(element)}
</div>
);
})}
</>
);
}
}
export default Grid;
Expected results: render function finishes executing, assigns DOM node to this.targetRef for use in componentDidMount()
Actual results: this.targetRef is still null in componentDidMount()

react doesn't rerender component when props changed

I'm changing the class attribute of my props and then i want the component to rerender with the new classes but that doesn't work. I've read about the shouldComponentUpdate method but that method never gets called.
var ReactDOM = require('react-dom');
var React = require('react');
class Button extends React.Component {
constructor(props) {
super(props);
console.log("BUTTON")
console.log(props);
var options = props.options;
}
componentWillMount () {
var defaultFeatureOptionId = this.props.feature.DefaultFeatureOptionId;
this.props.options.forEach((option) => {
var classes = "";
if (option.Description.length > 10) {
classes = "option-button big-button hidden";
} else {
classes = "option-button small-button hidden";
}
if (option.Id === defaultFeatureOptionId) {
classes = classes.replace("hidden", " selected");
option.selected = true;
}
option.class = classes;
});
}
shouldComponentUpdate(props) {
console.log("UPDATE");
}
toggleDropdown(option, options) {
console.log(option);
console.log(options)
option.selected = !option.selected;
options.forEach((opt) => {
if (option.Id !== opt.Id) {
opt.class = opt.class.replace("hidden", "");
}
else if(option.Id === opt.Id && option.selected) {
opt.class = opt.class.replace("", "selected");
}
});
}
render() {
if (this.props.options) {
return (<div> {
this.props.options.map((option) => {
return <div className={ option.class } key={option.Id}>
<div> {option.Description}</div>
<img className="option-image" src={option.ImageUrl}></img>
<i className="fa fa-chevron-down" aria-hidden="true" onClick={() => this.toggleDropdown(option, this.props.options) }></i>
</div>
})
}
</div>
)
}
else {
return <div>No options defined</div>
}
}
}
module.exports = Button;
I have read a lot of different thing about shouldComponentUpdate and componentWillReceiveProps but there seems to be something else i'm missing.
You cannot change the props directly, either you call a parent function to change the props that are passed to your component or in your local copy that you createm you can change them. shouldComponentUpdate is only called when a state has changed either directly or from the props, you are not doing any of that, only modifying the local copy and hence no change is triggered
Do something like
var ReactDOM = require('react-dom');
var React = require('react');
class Button extends React.Component {
constructor(props) {
super(props);
console.log(props);
this.state = {options = props.options};
}
componentWillRecieveProps(nextProps) {
if(nextProps.options !== this.props.options) {
this.setState({options: nextProps.options})
}
}
componentWillMount () {
var defaultFeatureOptionId = this.props.feature.DefaultFeatureOptionId;
var options = [...this.state.options]
options.forEach((option) => {
var classes = "";
if (option.Description.length > 10) {
classes = "option-button big-button hidden";
} else {
classes = "option-button small-button hidden";
}
if (option.Id === defaultFeatureOptionId) {
classes = classes.replace("hidden", " selected");
option.selected = true;
}
option.class = classes;
});
this.setState({options})
}
shouldComponentUpdate(props) {
console.log("UPDATE");
}
toggleDropdown(index) {
var options = [...this.state.options];
var options = options[index];
option.selected = !option.selected;
options.forEach((opt) => {
if (option.Id !== opt.Id) {
opt.class = opt.class.replace("hidden", "");
}
else if(option.Id === opt.Id && option.selected) {
opt.class = opt.class.replace("", "selected");
}
});
this.setState({options})
}
render() {
if (this.state.options) {
return (<div> {
this.state.options.map((option, index) => {
return <div className={ option.class } key={option.Id}>
<div> {option.Description}</div>
<img className="option-image" src={option.ImageUrl}></img>
<i className="fa fa-chevron-down" aria-hidden="true" onClick={() => this.toggleDropdown(index) }></i>
</div>
})
}
</div>
)
}
else {
return <div>No options defined</div>
}
}
}
module.exports = Button;

Resources