react doesn't rerender component when props changed - reactjs

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;

Related

react conditional rendering with design pattern

My question is, what would be the best way to set up the conditional rendering in this code to keep the code scalable and maintainable? i mean in a render function. i would like in future when i add new component,i do not want to touch the old code to add the new component. any ideas? i already know about
{this.state.state1?Component/>:null}
import * as React from "react";
import LoginForm from "./forms/LoginForm";
import MenuForm from "./forms/MenuForm";
import HomeForm from "./forms/HomeForm";
import TermsForm from "./forms/TermsForm";
import IdentificationResultForm from "./forms/IdentificationResultForm";
import CompanyForm from "./forms/CompanyForm";
import handleAppDateInputFromClient from "../api/azureController.js";
import ListViewForm from "./forms/ListViewForm";
import PartDetails from "./forms/PartDetails";
import ThemeForm from "./forms/ThemeForm";
export default class TemplateController extends React.Component {
constructor(props) {
super(props);
this.state = {
flowComponentNumber: 0,
};
this.config = {};
this.translation = {};
}
handelSubmit = (companyName) => {
// call azure controller with companyName and config
handleAppDateInputFromClient(companyName, this.config);
console.log(companyName, this.config);
this.handleFlowComponent();
};
handleLanguage = (key, value) => {
this.translation[key] = value;
};
handleChildSubmit = (key, value) => {
this.config[key] = value;
console.log(this.config);
this.handleFlowComponent();
};
handleFlowComponent = () => {
this.setState((prevState) => ({
flowComponentNumber: prevState.flowComponentNumber + 1,
}));
};
render() {
let component;
// let next = <Button variant="primary" type="button" onClick={this.handleFlowComponent}></Button>
if (this.state.flowComponentNumber === 0) {
component = <LoginForm now={10} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 1) {
component = <HomeForm now={20} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 2) {
component = <TermsForm now={30} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 3) {
component = <IdentificationResultForm now={40} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 4) {
component = <MenuForm now={50} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 5) {
component = (
<ListViewForm
now={60}
handler={this.handleChildSubmit}
languageHandler={this.handleLanguage}
/>
);
} else if (this.state.flowComponentNumber == 6) {
component = (
<PartDetails
now={70}
handler={this.handleChildSubmit}
languageHandler={this.handleLanguage}
/>
);
} else if (this.state.flowComponentNumber === 7) {
component = <ThemeForm now={80} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 8) {
component = <CompanyForm now={90} handler={this.handelSubmit} />;
} else if (this.state.flowComponentNumber === 9) {
component = <h1 style={{ color: "greenyellow" }}> Your submit is successful </h1>;
}
return <>{component}</>;
}
}

What's wrong with my method call I try to learn React and must pass component and Props to child

I learn Reactjs and javascript and wanted to call this withFetching Component but don't understand how to set the arguments correctly. I understand overall logic but still learning the details
Here is the switch where I call the withFetching
render() {
const theFile = encodeURI(`./images/${fileData}`);
switch (mediaType) {
case 'xlsx': {
const newProps = { ...this.props, responseType: 'arraybuffer' };
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
<{withFetching(XlsxViewer, newProps, fileType="xlsx", filePath={theFile} )}/>
</div>
</div>
);
}
.........
I try like this also:(making WithFetching camel-case even it's a function)
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
<WithFetching XlsxViewer={XlsxViewer} newProps={newProps} />
</div>
</div>
);
But the WithFetching constructor never firers!
I try like this:
case 'xlsx': {
const newProps = { ...this.props, responseType: 'arraybuffer', fileType: 'xlsx', filePath: { theFile } };
// return withFetching(XlsxViewer, newProps);
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
{WithFetching(XlsxViewer, newProps)};
</div>
</div>
);
}
But still the WithFetching constructor never firers!
Error: (yea I know the way I use brackets are my mistake it's hard to learn)
And this is the withFetching that is in its own file called fetch-wrapper.jsx. The WrappedComponent argument is the above XlsxViewer that is the final "On-screen" Component!
import React, { Component } from 'react';
import Error from './error';
import Loading from './loading';
function withFetching(WrappedComponent, props) {
return class FetchComponent extends Component {
constructor(props) {
// eslint-disable-line no-shadow
super(props);
this.state = {};
this.xhr = this.createRequest(props.filePath);
}
componentDidMount() {
try {
this.fetch();
} catch (e) {
if (this.props.onError) {
this.props.onError(e);
}
this.setState({ error: 'fetch error' });
}
}
componentWillUnmount() {
this.abort();
}
createRequest(path) {
let xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
// XHR for Chrome/Firefox/Opera/Safari.
xhr.open('GET', path, true);
// } else if (typeof XDomainRequest !== 'undefined') {
// // XDomainRequest for IE.
// xhr = new XDomainRequest();
// xhr.open('GET', path);
} else {
// CORS not supported.
xhr = null;
return null;
}
if (props.responseType) {
xhr.responseType = props.responseType;
}
xhr.onload = () => {
if (xhr.status >= 400) {
this.setState({ error: `fetch error with status ${xhr.status}` });
return;
}
const resp = props.responseType ? xhr.response : xhr.responseText;
this.setState({ data: resp });
};
return xhr;
}
fetch() {
this.xhr.send();
}
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
render() {
if (!this.xhr) {
return <h1>CORS not supported..</h1>;
}
if (this.state.error) {
return <Error {...this.props} error={this.state.error} />;
}
if (this.state.data) {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
return <Loading />;
}
};
}
export default withFetching;
And this the final XlxsViewer Component that will be visible.
Thanks to Copyright (c) 2017 PlanGrid, Inc.
import React, { Component } from 'react';
import XLSX from 'xlsx';
import CsvViewer from './csv-viewer';
class XlxsViewer extends Component {
constructor(props) {
super(props);
this.state = this.parse();
}
parse() {
const dataArr = new Uint8Array(this.props.data);
const arr = [];
for (let i = 0; i !== dataArr.length; i += 1) {
arr.push(String.fromCharCode(dataArr[i]));
}
const workbook = XLSX.read(arr.join(''), { type: 'binary' });
const names = Object.keys(workbook.Sheets);
const sheets = names.map(name => XLSX.utils.sheet_to_csv(workbook.Sheets[name]));
return { sheets, names, curSheetIndex: 0 };
}
renderSheetNames(names) {
const sheets = names.map((name, index) => (
<input
key={name}
type="button"
value={name}
onClick={() => {
this.setState({ curSheetIndex: index });
}}
/>
));
return <div className="sheet-names">{sheets}</div>;
}
renderSheetData(sheet) {
const csvProps = Object.assign({}, this.props, { data: sheet });
return <CsvViewer {...csvProps} />;
}
render() {
const { sheets, names, curSheetIndex } = this.state;
return (
<div className="spreadsheet-viewer">
{this.renderSheetNames(names)}
{this.renderSheetData(sheets[curSheetIndex || 0])}
</div>
);
}
}
export default XlxsViewer;

The state of a functional component does not change very well

I am creating a domain integrated search site now and I need to animate the TLD part of the design that the designer gave me.
However, when entering the keyword to be searched by the functional component, the state did not change while the class component was changed.
Code
functional component
import * as React from 'react';
import classNames from 'classnames';
import './SearchBarTLD.scss';
const tldList:string[] = [
'.com',
'.co.kr',
'.se',
'.me',
'.xyz',
'.kr',
'.dev',
'.xxx',
'.rich',
'.org',
'.io',
'.shop',
'.ga',
'.gg',
'.net'
];
const SearchBarTLD = () => {
const [ selectIndex, setSelectIndex ] = React.useState(0);
const changeIndex = () => {
if (selectIndex === 14) {
setSelectIndex(0)
} else {
setSelectIndex(selectIndex + 1)
}
}
React.useLayoutEffect(() => {
const interval = setInterval(() => changeIndex(), 1500);
return () => {
clearInterval(interval)
}
})
const renderList = () =>{
return tldList.map((tld:string, index:number) => {
return (
<span
className={
classNames(
"SearchBarTLD__tld", {
"SearchBarTLD__tld--visual": selectIndex === index
}
)
}
key={tld}
>
{tldList[index]}
</span>
)
})
}
return (
<div className="SearchBarTLD">
{renderList()}
</div>
)
}
export default SearchBarTLD;
class components
import * as React from 'react';
import classNames from 'classnames';
import './SearchBarTLD.scss';
export interface SearchBarTLDProps {
}
export interface SearchBarTLDState {
selectIndex: number,
tldList: string[]
}
class SearchBarTLD extends React.Component<SearchBarTLDProps, SearchBarTLDState> {
state: SearchBarTLDState;
intervalId: any;
constructor(props: SearchBarTLDProps) {
super(props);
this.state = {
selectIndex: 0,
tldList: [
'.com',
'.co.kr',
'.se',
'.me',
'.xyz',
'.kr',
'.dev',
'.xxx',
'.rich',
'.org',
'.io',
'.shop',
'.ga',
'.gg',
'.net'
]
};
this.intervalId = 0;
}
changeIndex() {
const { selectIndex } = this.state;
if (selectIndex === 14) {
this.setState({selectIndex: 0});
} else {
this.setState({selectIndex: selectIndex + 1});
}
}
renderList = () =>{
const { selectIndex, tldList } = this.state;
return tldList.map((tld:string, index:number) => {
return (
<span
className={
classNames(
"SearchBarTLD__tld", {
"SearchBarTLD__tld--visual": selectIndex === index
}
)
}
key={tld}
>
{tldList[index]}
</span>
)
})
}
componentDidMount() {
this.intervalId = setInterval(() => this.changeIndex(), 1500);
}
componentWillUnmount() {
clearInterval(this.intervalId)
}
render() {
return (
<div className="SearchBarTLD">
{this.renderList()}
</div>
);
}
}
export default SearchBarTLD;
The results of the functional component and the results of the class component are shown below.
funcational component
https://user-images.githubusercontent.com/28648915/56777865-8c0cf100-680e-11e9-93ad-cb7b59cd54e9.gif
class component
https://user-images.githubusercontent.com/28648915/56777941-e4dc8980-680e-11e9-8a47-be0a14a44ed9.gif
Can you guys tell me why this happens?

adding hammerjs to a react js component properly

I try to add hammer js to my reactjs component and my component looks as it follows
import React from 'react';
import _ from 'underscore';
import Hammer from 'hammerjs';
class Slider extends React.Component {
constructor(props) {
super(props)
this.updatePosition = this.updatePosition.bind(this);
this.next = this.next.bind(this);
this.prev = this.prev.bind(this);
this.state = {
images: [],
slidesLength: null,
currentPosition: 0,
slideTransform: 0,
interval: null
};
}
next() {
console.log('swipe')
const currentPosition = this.updatePosition(this.state.currentPosition - 10);
this.setState({ currentPosition });
}
prev() {
if( this.state.currentPosition !== 0) {
const currentPosition = this.updatePosition(this.state.currentPosition + 10);
this.setState({currentPosition});
}
}
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next());
this.hammer.on('swiperight', this.prev());
}
componentWillUnmount() {
this.hammer.off('swipeleft', this.next())
this.hammer.off('swiperight', this.prev())
}
handleSwipe(){
console.log('swipe')
}
scrollToSlide() {
}
updatePosition(nextPosition) {
const { visibleItems, currentPosition } = this.state;
return nextPosition;
}
render() {
let {slides, columns} = this.props
let {currentPosition} = this.state
let sliderNavigation = null
let slider = _.map(slides, function (slide) {
let Background = slide.featured_image_url.full;
if(slide.status === 'publish')
return <div className="slide" id={slide.id} key={slide.id}><div className="Img" style={{ backgroundImage: `url(${Background})` }} data-src={slide.featured_image_url.full}></div></div>
});
if(slides.length > 1 ) {
sliderNavigation = <ul className="slider__navigation">
<li data-slide="prev" className="" onClick={this.prev}>previous</li>
<li data-slide="next" className="" onClick={this.next}>next</li>
</ul>
}
return <div ref={
(el) => this._slider = el
} className="slider-attached"
data-navigation="true"
data-columns={columns}
data-dimensions="auto"
data-slides={slides.length}>
<div className="slides" style={{ transform: `translate(${currentPosition}%, 0px)`, left : 0 }}> {slider} </div>
{sliderNavigation}
</div>
}
}
export default Slider;
the problem is like on tap none of the components method are fired.
How do I deal in this case with the hammer js events in componentDidMount
Reason is, inside componentDidMount lifecycle method swipeleft and swiperight expect the functions but you are assigning value by calling those methods by using () with function name. Remove () it should work.
Write it like this:
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next); // remove ()
this.hammer.on('swiperight', this.prev); // remove ()
}

Cannot setState() on dynamically generated components

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!

Resources