use setState to change my arrays of object in state - javascript

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})
}

Related

React not saving ticked checkboxes

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.

Cant Render React Componet, Error In my understanding or code?

So I have built a graph generator and have all the correct data on the props the problem is when I go to render the build component It logs
Object { "$$typeof": Symbol(react.element), type: createElement(), key: "1", ref: null, props: {…}, _owner: null, _store: {…}, … }
Here is the Code I am sure its something silly I am not understanding about the render.
i
mport React, { Component } from 'react'
import * as action from '../../actions'
import { connect } from 'react-redux'
import jsx from 'react-jsx'
import { bindActionCreators } from 'redux'
import {Modal} from './Modal'
#connect(
(flux, props) => {
return {
filters: flux.FilterStore,
ready: flux.FilterStore.ready,
presets: flux.DataStore.preSelectList,
graphs: flux.GraphStore.graphList
}
},
(dispatch, props) => {
dispatch(action.fetchGraphList())
return {
addDataReportGraphDetails: bindActionCreators(action.addDataReportGraphDetails, dispatch)
}
}
)
class RenderGraphPreview extends Component {
constructor(props) {
super(props)
this.state = {
running: {},
show:false,
graph:{}
}
this.data = 0;
}
//Error function for shorthand errors
throwError = (string = "Error", err = null) => {
throw new Error(`${string}:${err}`)
}
//simple print function to print objects and strings
p = (string, variable) => {
typeof variable === `object` ? variable = JSON.stringify(variable) : variable
console.log(`${string}:${variable}`)
}
showModal = e => {
this.state.show = true
}
generateGraph = ({ presets, filters, graphDetails }) => {
var reportProps = {
wasRunning: true,
companyName: filters.company_name,
companyVertical: filters.company_vertical,
graphTitle: jsx.client(`<div>${graphDetails.title}</div>`)(reportProps).props.children
}
this.data++
if (typeof reportProps.graphTitle == "object") {
reportProps.graphTitle = reportProps.graphTitle.join("")
}
if (!this.state.running) {
reportProps.wasRunning = false
this.state.running = true
}
if (graphDetails.graph) {
var Graph = React.createFactory(require(`../graphs/${graphDetails.graph}`).default);
var newGraphProps = {}
var graphPropKeys = Object.keys(graphDetails.props || {})
graphPropKeys.map((graphKey) => {
if (graphDetails.props[graphKey] && graphDetails.props[graphKey].toString().length > 0)
newGraphProps[graphKey] = graphDetails.props[graphKey]
})
if (graphDetails.timeframe) {
newGraphProps[timeframe] = graphDetails[timeframe]
}
if (graphDetails.props.attackIndexFilterPreset) {
let preset;
for (let j = 0, jEnd = presets.length; j < jEnd; j++) {
if (presets[j]._id == graphDetails.props.attackIndexFilterPreset) {
return preset = presets[j]
}
}
if (preset) {
console.log(`In presents`)
newGraphProps = { ...preset, ...newGraphProps }
}
}
}
// console.log(<Graph key={this.state.count++} isDocument={true} reportKey={graphDetails.key} onImageCreated={this.props.addDataReportGraphDetails} {...filters} {...reportProps} {...newGraphProps}/>)
return (
<Graph key={this.data} isDocument={true} reportKey={graphDetails.key} onImageCreated={this.props.addDataReportGraphDetails} {...filters} {...reportProps} {...newGraphProps}/>
)
}
//Verifies we have the correct data to build the graph
startGraphGeneration = async (e,{ props }) => {
e.preventDefault()
let require = this.props.filters && this.props.presets && props
if (!require) {
this.throwError()
}
let graphProps = {
presets: this.props.presets,
filters: this.props.filters,
graphDetails: props,
}
let g = await this.generateGraph(graphProps)
this.setState({
graph: g
});
console.log(g)
}
render() {
var x = this.state.graph
return (
<div>
<button onClick={(e) => this.startGraphGeneration(e,this.props)}>Preview Graph</button>
{this.state.graph ? <x/> : `Doing Noting`}
</div>
)
}
}
export default connect()(RenderGraphPreview)
In your render method you use this.state.graph. You set this variable to the value returned from generateGraph function, which returns rendered node, not a component
. And then you try to render this node as a component(<x/>), which doesn't work. Also in
generateGraph function console.log(g) shows you rendered component. So just return x in you render method instead:
render() {
var x = this.state.graph
return (
<div>
<button onClick={(e) => this.startGraphGeneration(e,this.props)}>Preview Graph</button>
{this.state.graph ? x : `Doing Noting`}
</div>
)
}

React.js : Updating State of Nested Object

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;

React infinite scroll component performance

I have written the following infinite scroll component in React:
import React from 'react'
import { uniqueId, isUndefined, hasVerticalScrollbar, hasHorizontalScrollbar, isInt, throttle } from '../../../js/utils';
export default class BlSimpleInfiniteScroll extends React.Component {
constructor(props) {
super(props)
this.handleScroll = this.handleScroll.bind(this)
this.itemsIdsRefsMap = {}
this.isLoading = false
this.node = React.createRef()
}
componentDidMount() {
const {
initialId
} = this.props
let id
if (initialId) {
if (typeof initialId === "function") {
id = initialId()
}
else {
id = initialId
}
this.scrollToId(id)
}
}
componentDidUpdate(prevProps) {
if (
this.isLoading
&&
prevProps.isInfiniteLoading
&&
!this.props.isInfiniteLoading
) {
const axis = this.axis()
const scrollProperty = this.scrollProperty(axis)
const offsetProperty = this.offsetProperty(axis)
this.scrollTo(scrollProperty, this.node.current[offsetProperty])
this.isLoading = false
}
}
itemsRenderer(items) {
const length = items.length
let i = 0
const renderedItems = []
for (const item of items) {
renderedItems[i] = this.itemRenderer(item.id, i, length)
i++
}
return renderedItems
}
itemRenderer(id, i, length) {
const {
itemRenderer,
isInfiniteLoading,
displayInverse
} = this.props
let renderedItem = itemRenderer(id, i)
if (isInfiniteLoading) {
if (!displayInverse && (i == length - 1)) {
renderedItem = this.standardLoadingComponentWrapperRenderer(id, renderedItem)
}
else if (i == 0) {
renderedItem = this.inverseLoadingComponentWrapperRenderer(id, renderedItem)
}
}
const ref = this.itemsIdsRefsMap[id] || (this.itemsIdsRefsMap[id] = React.createRef())
return (
<div className="bl-simple-infinite-scroll-item"
key={id}
ref={ref}>
{renderedItem}
</div>
)
}
loadingComponentRenderer() {
const {
loadingComponent
} = this.props
return (
<div className="bl-simple-infinite-scroll-loading-component"
key={uniqueId()}>
{loadingComponent}
</div>
)
}
loadingComponentWrapperRenderer(id, children) {
return (
<div className="bl-simple-infinite-scroll-loading-component-wrapper"
key={id}>
{children}
</div>
)
}
standardLoadingComponentWrapperRenderer(id, renderedItem) {
return this.loadingComponentWrapperRenderer(id, [
renderedItem,
this.loadingComponentRenderer()
])
}
inverseLoadingComponentWrapperRenderer(id, renderedItem) {
return this.loadingComponentWrapperRenderer(id, [
this.loadingComponentRenderer(),
renderedItem
])
}
axis() {
return this.props.axis === 'x' ? 'x' : 'y'
}
scrollProperty(axis) {
return axis == 'y' ? 'scrollTop' : 'scrollLeft'
}
offsetProperty(axis) {
return axis == 'y' ? 'offsetHeight' : 'offsetWidth'
}
scrollDimProperty(axis) {
return axis == 'y' ? 'scrollHeight' : 'scrollWidth'
}
hasScrollbarFunction(axis) {
return axis == 'y' ? hasVerticalScrollbar : hasHorizontalScrollbar
}
scrollToStart() {
const axis = this.axis()
this.scrollTo(
this.scrollProperty(axis),
!this.props.displayInverse ?
0
:
this.scrollDimProperty(axis)
)
}
scrollToEnd() {
const axis = this.axis()
this.scrollTo(
this.scrollProperty(axis),
!this.props.displayInverse ?
this.scrollDimProperty(axis)
:
0
)
}
scrollTo(scrollProperty, scrollPositionOrPropertyOfScrollable) {
const scrollableContentNode = this.node.current
if (scrollableContentNode) {
scrollableContentNode[scrollProperty] = isInt(scrollPositionOrPropertyOfScrollable) ?
scrollPositionOrPropertyOfScrollable
:
scrollableContentNode[scrollPositionOrPropertyOfScrollable]
}
}
scrollToId(id) {
if (this.itemsIdsRefsMap[id] && this.itemsIdsRefsMap[id].current) {
this.itemsIdsRefsMap[id].current.scrollIntoView()
}
}
handleScroll() {
const {
isInfiniteLoading,
infiniteLoadBeginEdgeOffset,
displayInverse
} = this.props
if (
this.props.onInfiniteLoad
&&
!isInfiniteLoading
&&
this.node.current
&&
!this.isLoading
) {
const axis = this.axis()
const scrollableContentNode = this.node.current
const scrollProperty = this.scrollProperty(axis)
const offsetProperty = this.offsetProperty(axis)
const scrollDimProperty = this.scrollDimProperty(axis)
const currentScroll = scrollableContentNode[scrollProperty]
const currentDim = scrollableContentNode[offsetProperty]
const scrollDim = scrollableContentNode[scrollDimProperty]
const finalInfiniteLoadBeginEdgeOffset = !isUndefined(infiniteLoadBeginEdgeOffset) ?
infiniteLoadBeginEdgeOffset
:
currentDim / 2
let thresoldWasReached = false
let memorizeLastElementBeforeInfiniteLoad = () => { }
if (!displayInverse) {
thresoldWasReached = currentScroll >= (scrollDim - finalInfiniteLoadBeginEdgeOffset)
}
else {
memorizeLastElementBeforeInfiniteLoad = () => {
// TODO
}
thresoldWasReached = currentScroll <= finalInfiniteLoadBeginEdgeOffset
}
if (thresoldWasReached) {
this.isLoading = true
memorizeLastElementBeforeInfiniteLoad()
this.props.onInfiniteLoad()
}
}
}
render() {
const {
items
} = this.props
return (
<div className="bl-simple-infinite-scroll"
ref={this.node}
onScroll={this.handleScroll}
onMouseOver={this.props.onInfiniteScrollMouseOver}
onMouseOut={this.props.onInfiniteScrollMouseOut}
onMouseEnter={this.props.onInfiniteScrollMouseEnter}
onMouseLeave={this.props.onInfiniteScrollMouseLeave}>
{this.itemsRenderer(items)}
</div>
)
}
}
And I use it like this in a chat app I am writing:
...
<BlSimpleInfiniteScroll items={chat.messages}
ref={this.infiniteScrollComponentRef}
initialId={() => lastOfArray(chat.messages).id}
itemRenderer={(id, i) => this.messageRenderer(id, i, chat.messages)}
loadingComponent={<BlLoadingSpinnerContainer />}
isInfiniteLoading={isChatLoading}
displayInverse
infiniteLoadBeginEdgeOffset={void 0}
infiniteLoadingBeginBottomOffset={void 0}
onInfiniteLoad={() => this.props.onLoadPreviousChatMessages(chat.id)}
onInfiniteScrollMouseEnter={this.handleInfiniteScrollMouseEnter}
onInfiniteScrollMouseLeave={this.handleInfiniteScrollMouseLeave} />
...
The problem is that as soon as I scroll until the thresold and onInfiniteLoad is called, before the loading spinner is showed and after the data has been loaded the scroll freezes and the component becomes unresponsive.
How can I resolve this issue?
When I render the spinner container and after the previous loaded messages, shouldn't React just append the new divs retaining the previously added items in order to maintain the component performant?
If not, what key concepts of React I am missing?
Thank you for your attention!
UPDATE: Here are the additional components:
BlOrderChat represents a chat window and renders BlSimpleInfiniteScroll:
import React from 'react'
import BlOrderChatMessage from './BlOrderChatMessage';
import { isEmpty, uniqueId } from '../../../js/utils';
import { chatSelector } from '../selectors';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';
import BlSimpleInfiniteScroll from '../../core/scroll/BlSimpleInfiniteScroll';
export default class BlOrderChat extends React.Component {
static BL_ORDER_CHAT_MESSAGE_ID_ATTR_PREFIX = 'blOrderChatMessage'
constructor(props) {
super(props)
this.messageRenderer = this.messageRenderer.bind(this)
this.infiniteScrollComponentRef = React.createRef()
}
scrollToBottom() {
this.infiniteScrollComponentRef.current && this.infiniteScrollComponentRef.current.scrollToStart()
}
messageRenderer(messageId, index, messages) {
const {
currentUser, chat
} = this.props
const message = messages[index]
const length = messages.length
const fromUser = chat.users.items[message.from_user_id]
const itemComponentRender = (children) => (
<div key={messageId}>
{children}
</div>
)
const messageIdAttr = `${BlOrderChat.BL_ORDER_CHAT_MESSAGE_ID_ATTR_PREFIX}${message.id}`
const renderMessageComponent = () => (
<BlOrderChatMessage id={messageIdAttr}
key={uniqueId() + message.id}
message={message.message}
sentUnixTs={message.sent_unix_ts}
currentUser={currentUser}
fromUser={fromUser}
usersInvolvedInChatLength={chat.users.order.length} />
)
let children = []
if (index === 0) {
// First message.
children = [
<div key={uniqueId()} className="bl-padding"></div>,
renderMessageComponent()
]
}
else if (index === length - 1) {
// Last message.
children = [
renderMessageComponent(onComponentDidMount),
<div key={uniqueId()} className="bl-padding"></div>
]
}
else {
// Message in the middle.
children = [
renderMessageComponent()
]
}
return itemComponentRender(children)
}
render() {
const {
chat: propsChat, isChatLoading,
currentUser
} = this.props
const chat = chatSelector(propsChat, currentUser)
const chatHasMessages = chat && !isEmpty(chat.messages)
return (
<div className="bl-order-chat">
<div className="bl-order-chat-header">
// ...
</div>
<div className="bl-order-chat-content">
{
(chatHasMessages &&
<div className="bl-order-chat-content-inner">
<div className="bl-order-chat-infinite-scroll">
<BlSimpleInfiniteScroll items={chat.messages}
ref={this.infiniteScrollComponentRef}
initialId={() => lastOfArray(chat.messages).id}
itemRenderer={(id, i) => this.messageRenderer(id, i, chat.messages)}
loadingComponent={<BlLoadingSpinnerContainer />}
isInfiniteLoading={isChatLoading}
displayInverse
infiniteLoadBeginEdgeOffset={void 0}
infiniteLoadingBeginBottomOffset={void 0}
onInfiniteLoad={() => this.props.onLoadPreviousChatMessages(chat.id)}
onInfiniteScrollMouseEnter={this.handleInfiniteScrollMouseEnter}
onInfiniteScrollMouseLeave={this.handleInfiniteScrollMouseLeave} />
</div>
</div>
)
||
(isChatLoading &&
<BlLoadingSpinnerContainer />
)
}
</div>
<div className="bl-order-chat-footer">
// ...
</div>
</div>
)
}
}
BlOrderChatBox, contains BlOrderChat:
import React from 'react'
import BlOrderChat from './BlOrderChat';
import BlAlert from '../../core/alert/BlAlert';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';
export default class BlOrderChatBox extends React.Component {
constructor(props) {
super(props)
this.node = React.createRef()
}
render() {
const {
ordId, currentChat,
isCurrentChatLoading, currentUser,
err
} = this.props
return (
<div className="bl-order-chat-box" ref={this.node}>
<div className="bl-order-chat-box-inner">
{
(err &&
<BlAlert type="error" message={err} />)
||
(currentChat && (
// ...
<div className="bl-order-chat-box-inner-chat-content">
<BlOrderChat ordId={ordId}
chat={currentChat}
isChatLoading={isCurrentChatLoading}
onLoadPreviousChatMessages={this.props.onLoadPreviousChatMessages}
currentUser={currentUser} />
</div>
))
||
<BlLoadingSpinnerContainer />
}
</div>
</div>
)
}
}
And here is the component which renders BlOrderChatBox (it is the topmost stateful component):
import React from 'react'
import { POSTJSON } from '../../../js/ajax';
import config from '../../../config/config';
import { newEmptyArrayAble, arrayToArrayAbleItemsOrder, arrayAbleItemsOrderToArray, mergeArrayAbles, newArrayAble, firstOfArrayAble, isArrayAble } from '../../../js/data_structures/arrayable';
export default class BlOrderChatApp extends React.Component {
static NEW_CHAT_ID = 0
static MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX = 30
constructor(props) {
super(props)
this.currentUser = globals.USER
this.lastHandleSendMessagePromise = Promise.resolve()
this.newMessagesMap = {}
this.typingUsersDebouncedMap = {}
// Imagine this comes from a database.
const chat = {
// ...
}
const initialState = {
chats: newArrayAble(this.newChat(chat)),
currentChatId: null,
shouldSelectUserForNewChat: false,
newChatReceivingUsers: newEmptyArrayAble(),
isChatListLoading: false,
isCurrentChatLoading: false,
popoverIsOpen: false,
popoverHasOpened: false,
err: void 0,
focusSendMessageTextarea: false,
newChatsIdsMap: {},
currentChatAuthActs: {},
BlOrderChatComponent: null,
}
this.state = initialState
this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
}
POST(jsonData, callback) {
let requestJSONData
if (typeof jsonData === "string") {
requestJSONData = {
action: jsonData
}
}
else {
requestJSONData = jsonData
}
return POSTJSON(config.ORDER_CHAT_ENDPOINT_URI, {
...requestJSONData,
order_chat_type: this.props.orderChatType,
}).then(response => response.json()).then(json => {
this.POSTResponseData(json, callback)
})
}
POSTResponseData(data, callback) {
if (data.err) {
this.setState({
err: data.err
})
}
else {
callback && callback(data)
}
}
newChat(chat) {
const newChat = {
id: (chat && chat.id) || BlOrderChatApp.NEW_CHAT_ID,
ord_id: this.props.ordId,
users: (chat && chat.users && (isArrayAble(chat.users) ? chat.users : arrayToArrayAbleItemsOrder(chat.users))) || newEmptyArrayAble(),
messages: (chat && chat.messages && (isArrayAble(chat.messages) ? chat.messages : arrayToArrayAbleItemsOrder(chat.messages))) || newEmptyArrayAble(),
first_message_id: (chat && chat.first_message_id) || null,
typing_users_ids_map: (chat && chat.typing_users_ids_map) || {},
}
return newChat
}
isChatNew(chat) {
return (
chat
&&
(chat.id == BlOrderChatApp.NEW_CHAT_ID || this.state.newChatsIdsMap[chat.id])
)
}
loadPreviousChatMessages(chatId, lowestMessageIdOrNull, makeChatIdCurrent) {
this.POST({
act: 'loadPreviousChatMessages',
chat_id: chatId,
lowest_message_id: lowestMessageIdOrNull,
max_number_of_messages_to_load: BlOrderChatApp.MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX
}, json => {
this.setState(prevState => {
const chat = prevState.chats.items[chatId]
const messages = arrayToArrayAbleItemsOrder(json.messages)
const newChat = {
...chat,
messages: mergeArrayAbles(messages, chat.messages)
}
const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat))
return {
...(makeChatIdCurrent ?
{
currentChatId: chatId,
focusSendMessageTextarea: true,
}
:
{
currentChatId: prevState.currentChatId,
}
),
chats,
isCurrentChatLoading: false,
}
})
})
}
loadPreviousChatMessagesIfNotAllLoaded(chatId) {
let lowestMessageIdOrNull
const chat = this.state.chats.items[chatId]
if (
!this.isChatNew(chat)
&&
(lowestMessageIdOrNull = (chat.messages.order.length && firstOfArrayAble(chat.messages).id) || null)
&&
lowestMessageIdOrNull != chat.first_message_id
) {
this.setState({
isCurrentChatLoading: true
}, () => {
this.loadPreviousChatMessages(chat.id, lowestMessageIdOrNull)
})
}
}
handleLoadPreviousChatMessages(chatId) {
this.loadPreviousChatMessagesIfNotAllLoaded(chatId)
}
// ...
render() {
const currentChat = this.state.chats.items[this.state.currentChatId] || null
const err = this.state.err
return (
<div className="bl-order-chat-app">
<BlOrderChatBox currentUser={this.currentUser}
chats={arrayAbleItemsOrderToArray(this.state.chats)}
currentChat={currentChat}
isCurrentChatLoading={this.state.isCurrentChatLoading}
onLoadPreviousChatMessages={this.handleLoadPreviousChatMessages}
err={err} />
</div>
)
}
}
I tried to remove all the irrelevant code to simplify the reading. Also here is the file which contains the chatSelector function (normalizes the chat array-able object) and the *ArrayAble* functions (an array-able object to me is basically an object which maps objects through their ids in items and has an order property which keeps the sort):
import { isUndefined, unshiftArray, findIndex } from "../utils";
export function chatSelector(chat, currentUser) {
const newChat = { ...chat }
newChat.messages = arrayAbleItemsOrderToArray(chat.messages).sort((a, b) => {
const sortByUnixTs = a.sent_unix_ts - b.sent_unix_ts
if (sortByUnixTs == 0) {
return a.id - b.id
}
return sortByUnixTs
})
newChat.users = arrayAbleItemsOrderToArray(chat.users).filter(user => user.id != currentUser.id)
return newChat
}
/**
* Given an array-able object, returns its array representation using an order property.
* This function acts as a selector function.
*
* The array-able object MUST have the following shape:
*
* {
* items: {},
* order: []
* }
*
* Where "items" is the object containing the elements of the array mapped by values found in "order"
* in order.
*
* #see https://medium.com/javascript-in-plain-english/https-medium-com-javascript-in-plain-english-why-you-should-use-an-object-not-an-array-for-lists-bee4a1fbc8bd
* #see https://medium.com/#antonytuft/maybe-you-would-do-something-like-this-a1ab7f436808
*
* #param {Object} obj An object.
* #param {Object} obj.items The items of the object mapped by keys.
* #param {Array} obj.order The ordered keys.
* #return {Array} The ordered array representation of the given object.
*/
export function arrayAbleItemsOrderToArray(obj) {
const ret = []
for (const key of obj.order) {
if (!isUndefined(obj.items[key])) {
ret[ret.length] = obj.items[key]
}
}
return ret
}
export function arrayToArrayAbleItemsOrder(array, keyProp = "id") {
const obj = newEmptyArrayAble()
for (const elem of array) {
const key = elem[keyProp]
obj.items[key] = elem
obj.order[obj.order.length] = key
}
return obj
}
export function newEmptyArrayAble() {
return {
items: {},
order: []
}
}
export function isEmptyArrayAble(arrayAbleObj) {
return !arrayAbleObj.order.length
}
export function mergeArrayAbles(arrayAbleObj1, arrayAbleObj2, prependObj2 = false) {
const obj = newEmptyArrayAble()
for (const key of arrayAbleObj1.order) {
if (isUndefined(arrayAbleObj1.items[key])) {
continue
}
obj.items[key] = arrayAbleObj1.items[key]
obj.order[obj.order.length] = key
}
for (const key of arrayAbleObj2.order) {
if (isUndefined(arrayAbleObj2.items[key])) {
continue
}
if (!(key in obj.items)) {
if (!prependObj2) {
// Default.
obj.order[obj.order.length] = key
}
else {
unshiftArray(obj.order, key)
}
}
obj.items[key] = arrayAbleObj2.items[key]
}
return obj
}
export function newArrayAble(initialItem = void 0, keyProp = "id") {
const arrayAble = newEmptyArrayAble()
if (initialItem) {
arrayAble.items[initialItem[keyProp]] = initialItem
arrayAble.order[arrayAble.order.length] = initialItem[keyProp]
}
return arrayAble
}
export function lastOfArrayAble(obj) {
return (
(
obj.order.length
&&
obj.items[obj.order[obj.order.length - 1]]
)
||
void 0
)
}
Thank you for your help. If there's something missing which I should have included, please, let me know!
UPDATE: Thanks to Sultan H. it has improved, though the scroll still blocks as soon as I get the reply from the server. See it here: https://streamable.com/3nzu0
Any idea on how to improve this behaviour further?
Thanks!
Here is an attempt to resolve the performance issue, it's not preferrable to do tasks inside the Arrow Function that calculates the new state, in this case, at loadPreviousChatMessages you are calculating stuff in the callback, which may yeild to a load while setting the state on that context.
Preferrable Changes, replace this.setState inside your function with this code, all I've done here is clear the context by moving all the tasks out:
const chat = this.state.chats.items[chatId];
const messages = arrayToArrayAbleItemsOrder(json.messages);
const newChat = {
...chat,
messages: mergeArrayAbles(messages, chat.messages);
}
const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat));
const newState = {
...(
makeChatIdCurrent ?
{
currentChatId: chatId,
focusSendMessageTextarea: true,
}
:
{
currentChatId: this.state.currentChatId,
}
),
chats,
isCurrentChatLoading: false,
};
this.setState(() => newState);
If that doesn't entirely solve the issue, can you tell if there was at least an improvment?

reactjs components communicating

I have created two separate components and a parent component. I am trying to see how I can connect them so that I can have the dropdown for the children vanish when their checkbox is unchecked. I think I may have created this so the 2 components can't communicate, but I wanted to see if there was a way to get them to. Been trying different ways, but cannot seem to figure it out.
This is the parent component. It builds sections from some data and renders a checkbox treeview with the first (parent) checkbox having a dropdown. When the third option is selected in this dropdown, it renders in a dropdown for each child checkbox. I am trying to see if I can have the child dropdowns vanish when the checkbox is unchecked, but I can't seem to get the 2 components to communicate.
export default class CheckboxGroup extends PureComponent {
static propTypes = {
data: PropTypes.any.isRequired,
onChange: PropTypes.func.isRequired,
counter: PropTypes.number,
};
mapParents = (counter, child) => (
<li key={child.get('name')} className='field'>
<SegmentHeader style={segmentStyle} title={child.get('label')} icon={child.get('icon')}>
<div className='fields' style={zeroMargin}>
<div className='four wide field'>
<TreeCheckbox
label={`Grant ${child.get('label')} Permissions`}
counter={counter}
onChange={this.props.onChange}
/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</div>
<div className='twelve wide field'>
<GrantDropdown label={child.get('label')} childItems={child.get('items')}/>
</div>
</div>
</SegmentHeader>
</li>
)
mapDataArr = (counter) => (child) => (
(counter === 0 || counter === 1000) ?
this.mapParents(counter, child)
:
<li key={child.get('name')}>
<TreeCheckbox label={child.get('label')} onChange={this.props.onChange}/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</li>
)
buildTree = (dataArr, counter) => (
<ul key={counter} style={listStyle}>
{dataArr.map(this.mapDataArr(counter))}
</ul>
)
render() {
return (
<div className='tree-view'>
{this.buildTree(this.props.data, this.props.counter)}
</div>
);
}
}
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const pointer = { cursor: 'pointer' };
class TreeCheckbox extends PureComponent {
static propTypes = {
onChange: PropTypes.func,
label: PropTypes.string,
currentPerson: PropTypes.any,
};
componentDidMount() {
if (this.props.currentPerson.get('permissions').includes(this.props.label)) {
this.checkInput.checked = true;
this.changeInput(this.checkInput);
}
}
getLiParents = (el, parentSelector) => {
if (!parentSelector) parentSelector = document; // eslint-disable-line
const parents = [];
let parent = el.parentNode;
let o;
while (parent !== parentSelector) {
o = parent;
if (parent.tagName === 'LI') parents.push(o);
parent = o.parentNode;
}
return parents;
}
traverseDOMUpwards = (startingEl, steps) => {
let elem = startingEl;
for (let i = 0; i < steps; i++) {
elem = elem.parentNode;
}
return elem;
}
markIt = (nodeElem, checkIt, indeter) => {
const node = nodeElem;
const up = this.traverseDOMUpwards(node, 1);
node.checked = checkIt;
node.indeterminate = indeter;
this.props.onChange(up.children[1].innerText, checkIt);
}
changeInput = (event) => {
const e = event === this.checkInput ? event : event.target;
const selector = 'input[type="checkbox"]';
const querySelector = (el) => el.querySelectorAll(selector);
const container = this.traverseDOMUpwards(e, 2);
const markAllChildren = querySelector(container.parentNode);
const checked = e.tagName === 'LABEL' ? !markAllChildren[0].checked : e.checked;
const siblingsCheck = (element) => {
let onesNotRight = false;
const sibling = [].slice.call(element.parentNode.children);
sibling.filter(child => child !== element).forEach(elem => {
if (querySelector(elem)[0].checked !== querySelector(element)[0].checked) {
onesNotRight = true;
}
});
return !onesNotRight;
};
const checkRelatives = (ele) => {
let el = ele;
if (el.tagName === 'DIV') el = el.parentNode;
if (el.tagName !== 'LI') return;
const parentContainer = this.traverseDOMUpwards(el, 2);
if (siblingsCheck(el) && checked) {
this.markIt(querySelector(parentContainer)[0], true, false);
checkRelatives(parentContainer);
} else if (siblingsCheck(el) && !checked) {
const parent = this.traverseDOMUpwards(el, 2);
const indeter = parent.querySelectorAll(`${selector}:checked`).length > 0;
this.markIt(querySelector(parent)[0], false, indeter);
checkRelatives(parent);
} else {
for (const child of this.getLiParents(el)) {
this.markIt(querySelector(child)[0], false, true);
}
}
};
for (const children of markAllChildren) {
this.markIt(children, checked, false);
}
checkRelatives(container);
};
getRef = (input) => { this.checkInput = input; }
render() {
const { label } = this.props;
return (
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput} style={pointer}>
{label}
</label>
</div>
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
});
export default connect(mapStatetoProps)(TreeCheckbox);
class GrantDropdown extends AbstractSettingsComponent {
static propTypes = {
label: PropTypes.string,
currentPerson: PropTypes.any,
counter: PropTypes.number,
permissionOptions: PropTypes.any,
};
state = {
items: new List(),
}
componentDidMount() {
if (this.props.childItems) {
this.getAllChildLabels(this.props.childItems);
}
}
getAllChildLabels = (childItems) => {
let list = new List();
for (const item of childItems) {
list = list.push(item.get('label'));
if (item.get('items')) {
for (const childItem of item.get('items')) {
list = list.push(childItem.get('label'));
}
}
}
this.setState({ items: list });
}
handlePermissionChange = (label) => (e, { value }) => {
this.updatePerson(['locationsPermissionsMap', label], value);
}
mapItems = (val, i) => { // eslint-disable-line
const locationVal = this.props.currentPerson.getIn(['locationsPermissionsMap', val]);
return (
<div className={locationVal === 2 ? 'two fields' : 'field'} style={zeroMarginBottom} key={i}>
<OptionSelector
options={this.firstThreePermissionOpt()}
defaultValue={locationVal || 0}
onChange={this.handlePermissionChange(val)}
/>
{locationVal === 2 &&
<div className='field' style={zeroMarginBottom}>
<LocationMultiSelect name={val} {...this.props}/>
</div>
}
</div>
);
}
render() {
const { label, currentPerson } = this.props;
if (!currentPerson.get('permissions').includes(label)) {
return null;
}
const locationLabel = currentPerson.getIn(['locationsPermissionsMap', label]);
return (
<div className={ locationLabel === 2 ? 'two fields' : 'field'} style={zeroMarginBottom}>
<div className='field'>
<OptionSelector
options={this.getPermissionOptions()}
defaultValue={currentPerson.getIn(['locationsPermissionsMap', label]) || 0}
onChange={this.handlePermissionChange(label)}
/>
{locationLabel === 3 && this.state.items.map(this.mapItems)}
</div>
{locationLabel === 2 &&
<div className='field'>
<LocationMultiSelect name={label} {...this.props}/>
</div>
}
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
locations: state.get('locations'),
});
export default connect(mapStatetoProps)(GrantDropdown);
What you can do is set couple of props to send to child component to re-render them.
Example
export default class CheckBoxComponent extends React.Component {
changeInput() {
this.props.onCheckedChanged();
}
render() {
return(
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput.bind(this)} style={pointer}>
{label}
</label>
</div>
</div>
)
}
}
export default class DropDownComponent extends React.Component {
renderSelect() {
// here render your select and options
}
render() {
return(
<div>
{this.props.checkboxChecked === false ? this.renderSelect : null}
</div>
)
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxChecked: false
};
}
onCheckedChanged() {
this.setState({ checkboxChecked: !this.state.checkboxChecked });
}
render() {
return(
<div>
<CheckBoxComponent onCheckedChanged={this.onCheckedChanged.bind(this)} />
<DropDownComponent checkboxChecked={this.state.checkboxChecked} />
</div>
)
}
}
You can set the <input/> name attribute into a corresponding property in your state so the handler can get the name of the list / input via the event parameter and set the state respectively.
Then you can conditionally render the Dropdown according to the state.
Here is a small example of such behavior:
const lists = [
[
{ value: "0", text: "im 0" },
{ value: "1", text: "im 1" },
{ value: "2", text: "im 2" }
],
[
{ value: "a", text: "im a" },
{ value: "b", text: "im b" },
{ value: "c", text: "im c" }
]
];
const DropDown = ({ options }) => {
return (
<select>
{options.map(opt => <option value={opt.value}>{opt.text}</option>)}
</select>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showList0: false,
showList1: true
};
this.toggleCheck = this.toggleCheck.bind(this);
}
toggleCheck(e) {
const listName = e.target.name;
this.setState({ [listName]: !this.state[listName] });
}
render() {
return (
<div>
{lists.map((o, i) => {
const listName = `showList${i}`;
const shouldShow = this.state[listName];
return (
<div>
<input
type="checkbox"
name={listName}
checked={shouldShow}
onChange={this.toggleCheck}
/>
{shouldShow && <DropDown options={o} />}
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories

Resources