components/Settings/index.js
class Settings extends React.Component {
constructor(props) {
super(props);
this.state = {
activateOption1: true,
activateOption2: true,
isPending: false,
dataPending: false,
activeData1: false
};
}
componentDidMount(){
const {pet, updatePetStore} = this.props;
this.setState({activeData1: updatePetStore.petstoreToggles.activeData1});
this.setState({activateOption1: updatePetStore.petstoreToggles.activateOption1});
}
componentUpdate(){
const {updatePetStores, dispatch, intid } = this.props;
if(updatePetStore.updatedPetStores == 'pending'){
if(updatePetStore.updatedNonPetCookie == false){
if(updatePetStore.selectedToggle == 'isActiveData1' && !this.state.dataPending){
this.setState({dataPending: true})
} else if(updatePetStore.selectedToggle == 'ActivateOption1' && !this.state.isPending) {
this.setState({isPending: true})
}
}
}
if((updatePetStore.updatedPetCookie && this.state.isPending == true) || (updatePetStore.updatedDataCookie && this.state.dataPending == true)){
if(this.state.isPending) {
dispatch(updatedPetCookie(false));
this.setState({isPending: !this.state.isPending});
this.setState({ActivateOption1: !this.state.ActivateOption1});
} else {
dispatch(updatedDataCookie(false));
this.setState({dataPending: !this.state.dataPending});
this.setState({activeData1: !this.state.activeData1});
}
}
}
render () {
const { petstoreName, pet, updatePetStore } = this.props;
const petstoreToggles = updatePetSTore.petstoreToggles;
var petstoreId = petstoreToggles.petstoreId;
var option2flag;
{petstoreToggles.ActivateOption1=='Yes' || petstoreToggles.ActivateOption1==true ? option2flag = true : option2flag = false};
var petChildrenOptions = null;
var dataChildrenOptions = null;
if(this.state.ActivateOption1) {
petChildrenOptions = (
<div id="options-children">
<Option
title="Main"
modalTitle="Main"
cookieName="ActivateMain"
petstore={petstoreName}
petstoreId={petstoreId}
active={petstoreToggles.ActivateMain}
/>
<Option
title="Option2"
modalTitle="Option2"
cookieName="ActivateOption2"
petstore={petstoreName}
petstoreId={petstoreId}
active={option2flag}
/>
<Option
title="Option3"
modalTitle="Option3"
cookieName="ActivateOption3"
petstore={petstoreName}
petstoreId={petstoreId}
active={true}
/>
<Option
title="Option4"
modalTitle="Option4"
cookieName="ActivateOption4"
petstore={petstoreName}
petstoreId={petstoreId}
active={petstoreToggles.ActivateOption4}
/>
</div>
);
components/Settings/Option/index.js
const cookie = new Cookies();
class Settings extends React.Component {
constructor(props) {
super(props);
this.state = {
activate: false,
activateIsOpen: false,
update: false
};
}
componentWillMount(){
const { cookieName, active } = this.props;
const cookieDomain = getCookieDomain();
this.setState({activate: active});
}
componentDidUpdate() {
const { cookieName, updatePetStore, petstoreId, dispatch } = this.props;
const updatedpetstores = updatePetStore.updatedpetstores;
const cookieDomain = getCookieDomain();
var petstore = null;
if(updatePetStores.updatedCookieFlag && this.state.update){
if(cookieName == 'activeOption1' || cookieName == 'ActivateOption1'){
dispatch(updatedPetCookie(true));
}
for(var i = 0 ; i < updatedPetStores.length; i++) {
{updatedPetStores[i].uuid == petstoresId ? petstore = updatedPetStores[i] : null }
}
if (petstore != null) {
this.state.activate = petstore[cookieName];
if(petstore.accountId == cookie.get('accountId', {domain: cookieDomain})) {
cookie.set('ActivateOption1', petstore.ActivateOption1, {path: '/', domain: cookieDomain});
cookie.set('ActivateOption2', petstore.ActivateOption2, {path: '/', domain: cookieDomain});
cookie.set('ActivateMain', petstore.ActivateMain, {path: '/', domain: cookieDomain});
cookie.set('ActivateOption3', petstore.ActivateOption3, {path: '/', domain: cookieDomain});
}
}
dispatch(setCookieUpdateFlag(false));
this.setState({update: false});
}
}
toggleActivateIsOpen() {
this.setState({activateIsOpen: !this.state.activateIsOpen});
}
yesAction() {
const { dispatch, cookieName, petstoreId, router } = this.props;
if(cookieName != 'ActivateOption1' && cookieName != 'activeData1'){
dispatch(updatedNonPetCookie(true));
} else {
dispatch(selectedToggle(cookieName));
dispatch(updatedNonPetCookie(false));
}
dispatch(setActiveCookie(cookieName, petstoreId));
this.setState({activate: !this.state.activate});
this.setState({update: true});
this.toggleActivateIsOpen();
}
render () {
const { title, modalTitle, petstore, dispatch } = this.props;
const customStyles = {
content : {
marginRight : '-25%',
transform : 'translate(-25%, -25%)'
}
};
return (
<div>
<div className={this.state.activate == true ? "option-active" : "option"} onClick={() => this.toggleActivateIsOpen()}>
{title}
</div>
<Modal
isOpen={this.state.activateIsOpen}
shouldCloseOnOverlayClick={true}
style={customStyles}
contentLabel="Modal"
>
<div className="options-content">
<div>Are you sure you want to {this.state.isActive == true ? 'deactivate' : 'activate'} {modalTitle} for {petstore}?</div>
<div className="options-buttons">
<div className="yes" onClick={() => this.yesAction()}>Yes</div>
<div className="no" onClick={() => this.toggleActivateIsOpen()}>No</div>
</div>
</div>
</Modal>
</div>
)
}
}
function select(state) {
return {
updatePetStore: state.updatePetStore,
router: state.router
}
}
export default connect(select)(Settings);
The code rendered will look like this:
Option 1 [ ] // checkbox
Main [ ]
Option 2 [ ]
Option 3 [ ]
...
I can check the ticks on the checkbox, but whenever I refresh the checks do not save. How do I solve this issue?
I am guessing this is somehow related with cookies, but how?
Also, whenever I look into the developer console I am seeing an error on this page that is saying "Uncaught Invariant Violation". I am not sure if this is related, but it might be worth a mention. If I can provide more info, let me know.
You are not setting the state properly, please set the state like this,
this.setState({...this.state, activeData1: updatePetStore.petstoreToggles.activeData1});
this.setState({...this.state, activateOption1: updatePetStore.petstoreToggles.activateOption1});
you are not mutation the state but overriding it with activeData1: updatePetStore.petstoreToggles.activeData1, we need to send new reference to setState everytime need to change the state, contain initial and mutated data, so that it correctly tell React to render.
Example:
suppose we have initial state as
this.state = {
activateOption1: true,
activateOption2: true,
isPending: false,
dataPending: false,
activeData1: false
};
and now we are setting the state using setState api,
this.setState({activeData1: updatePetStore.petstoreToggles.activeData1});
this.setState({activateOption1: updatePetStore.petstoreToggles.activateOption1});
Now, our state structure becomes like this,
this.state = {
activateOptions1: value //mutated value
}
you loose all rest other properties it initially has.
Related
I'm working on some code that uses a checkbox function (I've stripped off a lot of functionality)
export class MyLovelyCheckBox extends React.Component {
constructor(props) {
super(props);
if (this.props.isRequired) this.props.updateType('default');
}
handleChecked(checkbox) {
if (checkbox.checked) {
this.props.updateType(checkbox.value);
} else {
this.props.updateType('');
}
}
render() {
return <div>
<Checkbox
disabled = {
this.props.isRequired
}
checkboxType = 'default'
handleChecked = {
this.handleChecked.bind(this)
}
value = {
this.props.type
}
/>
</div>;
}
}
The Checkbox function is (again stripped back):
export default function Checkbox(props) {
const handleChange = (event) => {
props.handleChecked(event.target);
};
const { checkboxType } = props;
return (
<div>
<input
disabled={props.disabled}
id="thisCheckBox"
value={checkboxType}
checked={checkboxType === props.value}
type="checkbox"
onChange={handleChange}
aria-checked={checkboxType === props.value} />
<label
id="thisCheckBox-label"
htmlFor="thisCheckBox"
>
<Icon icon={checkboxType === props.value ? 'checkbox' : 'checkbox-outline'} size={20} viewBox={'0 0 20 20'}></Icon>
<span>{props.checkboxLabel}</span>
</label>
</div>
);
}
The problem comes from my tests, as I'm trying to make sure that the handleChecked code is covered by tests. To that end, I've written this test:
it('when clicked, updateTypes is called', () => {
const updateType = jest.fn().mockReturnValue(true);
const mountedComponent = create({ updateType }, false, false);
const checkBox = mountedComponent.find('Checkbox');
checkBox.simulate('change', { target: { checked: true } });
expect(updateType).toHaveBeenLastCalledWith(true);
});
Where the utility method create is:
const create = (params, required = {}, shallowlyRender = true) => {
const props = {
type: 'generic',
updateType: () => true,
};
const allProps = { ...props, ...params };
return shallowlyRender
? shallow(<MyLovelyCheckBox {...allProps} />)
: mount(
<Provider store={store}>
<MyLovelyCheckBox {...allProps}/>
</Provider>,
);
};
Despite my best efforts, I cannot get the handleChecked function to be called by the test.
Instead jest outputs:
expect(jest.fn()).toHaveBeenLastCalledWith(...expected)
Expected: true
Received: ""
Number of calls: 0
Where have I gone wrong on this test?
Front End - Front End
Upon clicking the star, I want to update the state of nested object, with the new rating value of star.
I tried many things but it didnt work as states are immutable.
Nested State
Can some upon please suggest how can I update the value in nested object
onStarClicked = (kTypName, subItemId1, newRating) => {
//console.log(subItemId.split("_"));
let evaluation = subItemId1.split("_")[0];
let subItemId = subItemId1.split("_")[1];
console.log(subItemId);
const r = { ...this.state.ratings };
let kT = r.knowledgeTypes;
let sub = '', kTN = '', kIN = '';
kT.map(knowledgeType => {
//console.log(knowledgeType.knowledgeTypeId);
knowledgeType.knowledgeItems.map(knowledgeItem => {
//console.log(knowledgeItem.knowledgeItemId);
knowledgeItem.subItems.map(knowledgeSubItem => {
//console.log(knowledgeSubItem.subItemId);
if (subItemId === knowledgeSubItem.subItemId) {
kTN = knowledgeType.knowledgeTypeName;
kIN = knowledgeItem.knowledgeItemName;
sub = knowledgeSubItem;
if (evaluation === "self") {
sub.evaluation.self.rating = newRating;
}
else if (evaluation === "evaluator") {
sub.evaluation.evaluator.rating = newRating;
}
//alert(evaluation + subItemId + ' ' + newRating);
//return;
}
})
})
});
this.setState({
...this.state,
ratings: {
...this.state.ratings,
knowledgeTypes: [
...this.state.ratings.knowledgeTypes,
this.state.ratings.knowledgeTypes.filter(kt => kt.knowledgeTypeName !== kTN),
{
...this.state.ratings.knowledgeTypes.knowledgeItems.
filter(ki => ki.knowledgeItemName !== kIN),
knowledgeItems: {
...this.state.ratings.knowledgeTypes.knowledgeItems.subItems.
filter(si => si.subItemId !== subItemId),
sub
}
}]
}
});
}
You basically have to create a new empty array of knowledgeTypes and use the current state to find which item of the state you need to change using Object.keys/map/filter functions.
You'd use the current state in a variable and modify that variable only. You'd likely not mess with the actual state object in any way.
After you have done that, simply append it to the empty array. Then you can setState() the new array to the actual state property.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
financialYear: "2019-20",
quarter: "Q1",
isCurrentQuarter: true,
knowledgeTypes: [
{
knowledgeTypeName: "Technology",
knowledgeItems: [
{
knowledgeItemName: "Java",
subItems: [
{
subItemId: "2",
subItemName: "Collections",
evaluation: {
self: {
ntnet: "Joe",
rating: 1,
isEditable: true
}
}
}
]
}
]
}
]
};
}
handleClick = e => {
const { knowledgeTypes } = this.state;
// transformation
const itemToChange = knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
return item;
}
});
const currItems = itemToChange[0].knowledgeItems[0].subItems;
const subItem = currItems.map(item => {
if (item.subItemId === "2") {
return item;
}
});
const person = subItem[0].evaluation;
person.self.rating = 55; //change
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
newKnowledgeTypes.push(itemToChange);
}
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
};
render() {
return (
<div>
MyComponent
<button onClick={this.handleClick}>Hello</button>
</div>
);
}
}
The sandbox can be found on https://codesandbox.io/s/musing-dew-8r2vk.
Note: It is advisable you do not use nested state objects because state objects are something more lightweight so that they do not have performance considerations.
import React, { Component } from 'react';
import Auxilary from '../../../hoc/Auxilary/auxilary';
import KnowledgeItems from '../KnowledgeItems/KnowledgeItems';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import knowledge from '../../../assests/staticdata.json';
import './QuarterLog.css';
class QuarterLog extends Component {
constructor() {
super();
this.state = {
"financialYear": "",
"quarter": "",
"isCurrentQuarter": "",
"knowledgeTypes": []
}
}
onStarClicked = (kTypName, kItemName, subItemIdName, newRating) => {
let evaluation = subItemIdName.split("_")[0];
let subItemId = subItemIdName.split("_")[1];
const { knowledgeTypes } = this.state;
// transformation
let knowledgeTypeToChange = knowledgeTypes.map(kType => {
if (kType.knowledgeTypeName === kTypName) {
return kType;
}
});
knowledgeTypeToChange = knowledgeTypeToChange.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeTypeToChange[0]);
let knowledgeItemToChange = knowledgeTypeToChange[0].knowledgeItems.map(item => {
if (item.knowledgeItemName === kItemName) {
return item;
}
});
knowledgeItemToChange = knowledgeItemToChange.filter(function (element) {
return element !== undefined;
});
let knowledgeSubItem = knowledgeItemToChange[0].subItems.map(subItem => {
if (subItem.subItemId === subItemId) {
return subItem;
}
});
knowledgeSubItem = knowledgeSubItem.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeSubItem);
let personEvaluations = knowledgeSubItem[0].evaluation;
if (evaluation === "self") {
personEvaluations.self.rating = newRating.toString(); //change
}
else if (evaluation === "evaluator") {
personEvaluations.evaluator.rating = newRating.toString(); //change
}
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === kTypName) {
newKnowledgeTypes.push(knowledgeTypeToChange[0]);
}
else
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
}
componentDidMount() {
// TODO: remove staticdata.js and call REST API and set the response in state
this.setState({
...this.state,
"financialYear": knowledge.financialYear,
"quarter": knowledge.quarter,
"isCurrentQuarter": knowledge.isCurrentQuarter,
"knowledgeTypes": knowledge.knowledgeTypes
})
}
onSubmitRatings = () => {
console.log(this.state);
}
render() {
let data = knowledge; //remove this code, once REST API is implemented
const posts = this.state.knowledgeTypes.map(knowledgeType => {
return (
<Tab key={knowledgeType.knowledgeTypeName} eventKey={knowledgeType.knowledgeTypeName}
title={knowledgeType.knowledgeTypeName}>
<KnowledgeItems
kTypeName={knowledgeType.knowledgeTypeName}
kItems={knowledgeType.knowledgeItems}
ratings={this.state.ratings}
onstarclicked={this.onStarClicked}
/>
</Tab>)
});
return (
<Auxilary>
<div className="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div><h1>Financial Year : {data.financialYear}</h1></div>
<div><h2>Quarter : {data.quarter}</h2></div>
</div>
<div>
<Tabs defaultActiveKey="Domain" id="uncontrolled-tab-example">
{posts}
</Tabs>
</div>
<button onClick={this.onSubmitRatings}> Submit </button>
</Auxilary>
);
}
}
export default QuarterLog;
I found a lot of solutions about this problem but none of them work.
I have a view which renders dynamically components depending on the backend response
/**
* Module dependencies
*/
const React = require('react');
const Head = require('react-declarative-head');
const MY_COMPONENTS = {
text: require('../components/fields/Description'),
initiatives: require('../components/fields/Dropdown'),
vuln: require('../components/fields/Dropdown'),
severities: require('../components/fields/Dropdown'),
};
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class CreateView extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
states: props.states,
error: props.error,
spinner: true,
state: props.state,
prevState: '',
components: [],
};
this.handleChange = this.handleChange.bind(this);
this.getRequiredFields = this.getRequiredFields.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.changeState = this.changeState.bind(this);
this.loadComponents = this.loadComponents.bind(this);
}
componentDidMount() {
this.loadComponents();
}
onChangeHandler(event, value) {
this.setState((prevState) => {
prevState.prevState = prevState.state;
prevState.state = value;
prevState.spinner = true;
return prevState;
}, () => {
this.getRequiredFields();
});
}
getRequiredFields() {
request.get('/transitions/fields', {
params: {
to: this.state.state,
from: this.state.prevState,
},
})
.then((response) => {
const pComponents = this.state.components.map(c => Object.assign({}, c));
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
this.setState({
components: pComponents,
fields: response.data,
spinner: false,
});
})
.catch(err => err);
}
loadComponents() {
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
}
handleChange(field, value) {
this.setState((prevState) => {
prevState[field] = value;
return prevState;
});
}
changeState(field, value) {
this.setState((prevState) => {
prevState[`${field}`] = value;
return prevState;
});
}
render() {
const Components = this.state.components;
return (
<Page name="CI" state={this.props} Components={Components}>
<Script src="vendor.js" />
<Card className="">
<div className="">
<div className="">
<Spinner
show={this.state.spinner}
/>
{Components.map((component, i) => {
const Comp = component.component;
return (<Comp
key={i}
value={this.state[component.field.name]}
field={component.field}
handleChange={this.handleChange}
modal={this.state.modal}
changeState={this.changeState}
/>);
})
}
</div>
</div>
</div>
</Card>
</Page>
);
}
}
module.exports = CreateView;
and the dropdown component
const React = require('react');
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class DrpDwn extends React.Component {
constructor(props) {
super(props);
this.state = {
field: props.field,
values: [],
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('state', this.state.field);
console.log('prevState', prevState.field);
console.log('prevProps', prevProps.field);
console.log('props', this.props.field);
}
render() {
const { show } = this.props.field;
return (show && (
<div className="">
<Dropdown
className=""
onChange={(e, v) => this.props.handleChange(this.props.field.name, v)}
label={this.state.field.name.replace(/^./,
str => str.toUpperCase())}
name={this.state.field.name}
type="form"
value={this.props.value}
width={100}
position
>
{this.state.values.map(value => (<DropdownItem
key={value.id}
value={value.name}
primary={value.name.replace(/^./, str => str.toUpperCase())}
/>))
}
</Dropdown>
</div>
));
}
module.exports = DrpDwn;
The code actually works, it hide or show the components correctly but the thing is that i can't do anything inside componentdidupdate because the prevProps prevState and props are always the same.
I think the problem is that I'm mutating always the same object, but I could not find the way to do it.
What I have to do there is to fill the dropdown item.
Ps: The "real" code works, i adapt it in order to post it here.
React state is supposed to be immutable. Since you're mutating state, you break the ability to tell whether the state has changed. In particular, i think this is the main spot causing your problem:
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
}; return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
You mutate the previous states to changes its components property. Instead, create a new state:
this.setState(prevState => {
const components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return { components }
}
You have an additional place where you're mutating state. I don't know if it's causing your particular problem, but it's worth mentioning anyway:
const pComponents = [].concat(this.state.components);
// const pComponents = [...this.state.components];
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
You do at make a copy of state.components, but this will only be a shallow copy. The array is a new array, but the objects inside the array are the old objects. So when you set ob.field.required, you are mutating the old state as well as the new.
If you want to change properties in the objects, you need to copy those objects at every level you're making a change. The spread syntax is usually the most succinct way to do this:
let pComponents = this.state.components.map(c => {
return {
...c,
field: {
...c.field,
required: 0,
show: false
}
}
});
response.data.forEach(r => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
// Here it's ok to mutate, but only because i already did the copying in the code above
ob.field.required = r.required;
ob.field.show = true;
}
})
import React, {
Component
} from "react";
class App extends Component {
state = {
RSVP: [{
name: ["IronMan"],
isConfirmed: false
}]
};
handleChecked = () => {
this.setState({
isConfirmed: true
}););
};
render() {
return ( < div > {
this.state.RSVP && this.state.RSVP.length != 0 ? this.state.RSVP.map(({
name,
isConfirmed
}) => name && name.length != 0 ? ( <
div key = {
name
} > {
name
} < input type = "checkbox"
checked = {
isConfirmed
}
onChange = {
this.handleChecked
}
/></div > ) : null) : null
} < /div>);}}
export default App;
you can change state of object inside an array you can use the following source code:
handleChange = (index) => {
let tmp = [...this.state.RSVP];
tmp[index] = {...tmp[index],isConfirmed: true};
this.setState({RSVP: tmp})
}
I implemented the componentShouldUpdate in the code below to try and speed up performance. This goal was accomplished, but now the comments don't render. Browser console shows that everything is being received, though. There's also a div that renders the number of comments and that is being updated as well.
class ProposalDetail extends React.Component {
constructor(props) {
super(props);
this.state = {
sortedComments: []
};
}
componentDidUpdate(prevProps) {
if((!prevProps.proposal || Object.keys(prevProps.proposal).length === 0 ) &&
this.props.proposal && Object.keys(this.props.proposal).length > 0 &&
this.props.proposal.status === 4 ){
prevProps.onFetchProposalVoteStatus(prevProps.token);
}
this.handleUpdateOfComments(prevProps, this.props);
}
shouldComponentUpdate(nextProps, nextState) {
console.log('thisProps', this.props.comments)
console.log('nextProps', nextProps.comments)
if (this.props.comments === nextProps.comments) {
return true
}
else {
return false
}
}
componentDidMount() {
this.props.onFetchLikedComments(this.props.token);
}
componentWillUnmount() {
this.props.resetLastSubmittedProposal();
}
handleUpdateOfComments = (currentProps, nextProps) => {
let sortedComments;
if(!nextProps.comments || nextProps.comments.length === 0) {
return;
}
// sort option changed
if(currentProps.commentsSortOption !== nextProps.commentsSortOption) {
sortedComments = updateSortedComments(
this.state.sortedComments,
nextProps.commentsSortOption
);
}
// new comment added
if(currentProps.comments.length !== nextProps.comments.length) {
const isEmpty = currentProps.comments.length === 0;
const newComments = isEmpty ?
nextProps.comments :
[nextProps.comments[nextProps.comments.length - 1]]
.concat(this.state.sortedComments);
sortedComments = updateSortedComments(
newComments,
currentProps.commentsSortOption,
nextProps.commentsvotes,
isEmpty
);
}
// usernames aren't fully merged into comments
const commentWithoutAnUsername = comments => comments.filter(c => !c.username)[0];
if (commentWithoutAnUsername(this.state.sortedComments) && !commentWithoutAnUsername(nextProps.comments)) {
sortedComments = updateSortedComments(
nextProps.comments,
currentProps.commentsSortOption,
nextProps.commentsvotes,
false
);
}
// commentsvotes changed
if(nextProps.commentsvotes && !isEqual(currentProps.commentsvotes, nextProps.commentsvotes)) {
const updatedComments = getUpdatedComments(nextProps.commentsvotes, nextProps.comments);
const newComments = mergeNewComments(this.state.sortedComments, updatedComments);
sortedComments = updateSortedComments(
newComments,
currentProps.commentsSortOption,
nextProps.commentsvotes,
false
);
}
// comment gets censored
if(nextProps.censoredComment && !isEqual(currentProps.censoredComment, nextProps.censoredComment)) {
sortedComments = updateSortedComments(
nextProps.comments,
currentProps.commentsSortOption,
nextProps.commentsvotes,
true
);
}
if(sortedComments) {
this.setState({ sortedComments });
console.log('setState', this.state.sortedComments);
}
}
render() {
const {
isLoading,
proposal,
token,
error,
markdownFile,
otherFiles,
onFetchData,
...props
} = this.props;
console.log(this.props);
const comments = this.state.sortedComments;
return (
<div className="content" role="main">
<div className="page proposal-page">
{error ? (
<Message
type="error"
header="Proposal not found"
body={error} />
) : (
<Content {...{
isLoading,
error,
bodyClassName: "single-page comments-page",
onFetchData: () => onFetchData(token),
listings: isLoading ? [] : [
{
allChildren: [{
kind: "t3",
data: {
...proposalToT3(proposal, 0).data,
otherFiles,
selftext: markdownFile ? getTextFromIndexMd(markdownFile) : null,
selftext_html: markdownFile ? getTextFromIndexMd(markdownFile) : null
}
}]
},
{ allChildren: commentsToT1(comments) }
],
...props
}} />
)}
</div>
</div>
);
}
}
export default ProposalDetail;
It’s not componentShouldUpdate but shouldComponentUpdate
shouldComponentUpdate basically decide whether the component requires re rendering or not. This method either returns true or false only. By default this method returns true which means the component needs re rendering always whenever setState happens or props received irrespective of the state and props comparison.
So in your case you are comparing comments incorrectly in shouldComponentUpdate. You need to return true only when current props and previous props are not equal otherwise false but you are checking vice versa.
The below code would work
shouldComponentUpdate(nextProps, nextState) {
console.log('thisProps', this.props.comments)
console.log('nextProps', nextProps.comments)
if (JSON.stringify(this.props.comments) !== JSON.stringify(nextProps.comments)) {
return true
}
else {
return false
}
}
Try this:
JSON.stringify(this.props.comments) === JSON.stringify(nextProps.comments)
This dirty hack could help.
The problem is that you cannot compare two arrays in such a way you do.