Search code examples
javascriptreactjslocal-storagereact-grid-layout

onLayoutChange is resetting my data from local storage


whenever my layout changes, it will save the new changes into localstorage and update my layout state

onLayoutChange(layouts) {
    saveToLS("layouts", layouts);
  }

However, the issue occurs When the page is refreshed. layout was updated before it was fetched from local storage, this means it discard all the changes and reset the layout to default. The image below shows what i mean

enter image description here

What should I do to prevent this from happening?? I am following this guide. It is working on the guide but not for me, really appreciate any help

Here is the full code I am using

const originalLayouts = getFromLS("layouts") || {};
const ResponsiveReactGridLayout = WidthProvider(Responsive);

class MinMaxLayout extends React.PureComponent {
  

  static defaultProps = {
    margin:[0,0],
    className:"layout",
    cols: { lg: 1, md: 10 },
    rowHeight: 100
  };

  
  constructor(props) {
    super(props);
  
    this.state = {
      layouts: JSON.parse(JSON.stringify(originalLayouts)),
      inputWidthVal: "",
      inputHeightVal:"",
      items: [0, 1, 2, 3, 4].map(function (i, key, list) {
        return {
          id: uuidV4(),
          i: i.toString(),
          x: i * 2,
          y: 0,
          w: 1,
          h: 1
        };
      }),
      charts: [0].map(function (i, key, list) {
        return {
          id: uuidV4(),
          i: i.toString(),
          x: i,
          y: 0,
          w: 4,
          h: 3
        };
      }),
      newCounter: 0,
      chartCounter:0
    };

    this.onAddItem = this.onAddItem.bind(this);
    this.onChartItem = this.onChartItem.bind(this);
    this.onBreakpointChange = this.onBreakpointChange.bind(this);
  }

  createElement(el) {
    const removeStyle = {
      position: "absolute",
      left: "11px",
      top: 0,
      cursor: "pointer"
    };

    const i = el.i;

    return (
      <div key={el.id} data-grid={el} >
        <span
          className="remove"
          style={removeStyle}
          onClick={this.onRemoveItem.bind(this, i)}
        >
          x
        </span>
      </div>
    );
  }

  // this is the charts that was created when the DOM was render
  createChart(el) {
  
    const removeStyle = {
      position: "absolute",
      left: "11px",
      top: 0,
      cursor: "pointer"
    };
    const i = el.i;

    return (
      
      // <div key={el.id} data-grid={el}  onClick={((e) => this.handleClick(e))}>
      <div key={el.id} data-grid={el} className="graph">
        <Newvsresturnvisitors />
        <span
          className="remove"
          style={removeStyle}
          onClick={this.onRemoveChartItem.bind(this, i)}
        >
          x
        </span>
      </div>
    );
  }
  
  onAddItem() {
    this.setState({
      // Add a new item. It must have a unique key!
      items: this.state.items.concat({
        id: uuidV4(),
        i: "n" + this.state.newCounter,
        x: (this.state.items.length * 2) % (this.state.cols || 12),
        y: Infinity, // puts it at the bottom
        w: 1,
        h: 1
      }),
      // Increment the counter to ensure key is always unique.
      newCounter: this.state.newCounter + 1
    });
  }

  onChartItem() {
    this.setState({
      // Add a new item. It must have a unique key!
      charts: this.state.charts.concat({
        id: uuidV4(),
        i: "n" + this.state.chartCounter,
        x: (this.state.charts.length * 2) % (this.state.cols || 12),
        y: 0, // puts it at the bottom
        w: 4,
        h: 3
      }),
      // Increment the counter to ensure key is always unique.
      chartCounter: this.state.chartCounter + 1
    });
  }

  // We're using the cols coming back from this to calculate where to add new items.
  onBreakpointChange(breakpoint, cols) {
    this.setState({
      breakpoint: breakpoint,
      cols: cols
    });
  }

  onRemoveItem(i) {
    this.setState({ items: _.reject(this.state.items, { i: i }) });
  }

  clearAllItem = ()=>{
    this.setState({ items: [], charts: [] });
  }

  onRemoveChartItem(i){
    this.setState({ charts: _.reject(this.state.charts, { i: i }) });
  }

  inputCanvasDimension = e =>{

    const re = /^[0-9\b]+$/;

    if (e.target.value === '' || re.test(e.target.value)) {
      
      if(e.currentTarget.className === "inputWidth"){
       this.setState({inputWidthVal: e.currentTarget.value});
      }else if(e.currentTarget.className === "inputHeight"){ 
        this.setState({inputHeightVal: e.currentTarget.value});
      }
   }
  }

  setCanvasDimension = () =>{
    var canvas = document.getElementById("DetailLocationContainer");

    if((this.state.inputWidthVal < 1000) || (this.state.inputHeightVal < 3000)){
      alert("Width should not be less than 1000 and height should not be less than 3000");
    }else {
      canvas.style.width= this.state.inputWidthVal   + "px";
      canvas.style.height= this.state.inputHeightVal + "px";
    }
  }

  saveToLS(key, value) {
    var secondObject = Object.entries(value)[1];

    if (global.localStorage) {

      global.localStorage.setItem(
        "rgl-8",
        JSON.stringify({
          [key]: value
        })
      );
    }
  }

  onLayoutChange(layout, layouts) {
    this.saveToLS("layouts", layouts);
  }

  onDrop = (layout, layoutItem, _event) => {
   
   if(_event.dataTransfer.mozSourceNode.className === "textwidget"){
    
    this.setState({
      items: this.state.items.concat({
        id: uuidV4(),
        i: "n" + this.state.newCounter,
        x: layoutItem.x,
        y: layoutItem.y,
        w: 1,
        h: 1
      }),
      newCounter: this.state.newCounter + 1
    });

   }else if(_event.dataTransfer.mozSourceNode.className === "chart"){
    
    this.setState({
      // Add a new item. It must have a unique key!
      charts: this.state.charts.concat({
        id: uuidV4(),
        i: "n" + this.state.chartCounter,
        x: (this.state.charts.length * 2) % (this.state.cols || 12),
        y: Infinity, // puts it at the bottom
        w: 5,
        h: 6
      }),
      // Increment the counter to ensure key is always unique.
      chartCounter: this.state.chartCounter + 1
    });
   }
  };

  reset = () =>{
    // window.location.reload(true);
    this.setState({ layouts: {} });
  }


  render() {
    return (
    <div className="container" id="container">

      <div className="btn_container">
        <div>
          <span>Width </span>
          <input className="inputWidth" value={this.state.inputWidthVal} onChange={this.inputCanvasDimension}/>
          &nbsp;&nbsp;
          <span> Height </span>
          <input className="inputHeight" value={this.state.inputHeightVal} onChange={this.inputCanvasDimension}/>
          &nbsp;&nbsp;
          <button onClick={this.setCanvasDimension}>Apply</button>
          <button onClick={this.reset}><CachedIcon style={{fontSize: '14px'}}/></button>
          <button onClick={openFullscreen}><FullscreenSharpIcon style={{fontSize: '14px'}}/></button>
        </div>
      </div>

       <button id="createItemBtn" onClick={this.onAddItem}>Add Item</button>
       <button id="createChartBtn" onClick={this.onChartItem}>Add Chart</button>
       <button onClick={this.clearAllItem}>Clear All</button>
       
        <div className="widgetcontainer">
          <WidgetThumbnail
            className="chart"
          /> 
          &nbsp;&nbsp;
          <WidgetThumbnail 
            className="textwidget"
          />
        </div>

      <div className='DetailLocationContainer' id="DetailLocationContainer" >
        
        <ResponsiveReactGridLayout 
          cols={{ lg: 1, md: 10, sm: 6, xs: 4, xxs: 2 }}
          rowHeight={100}
          onDrop={this.onDrop}
          isDroppable={true}
          isBounded={true}
          layouts={this.state.layouts}
          onLayoutChange={(layout, layouts) =>
            this.onLayoutChange(layout, layouts)
          }
          onBreakpointChange={this.onBreakpointChange}
          {...this.props}
        >
          {_.map(this.state.items, (el) => this.createElement(el))}
          {_.map(this.state.charts, (el) => this.createChart(el))}
          
        </ResponsiveReactGridLayout>
      </div>
    </div>

    );
  }
}


export default MinMaxLayout;

const rootElement = document.getElementById("root");
ReactDOM.render(<MinMaxLayout />, rootElement);


This is my getFrinLS

export function getFromLS(key) {
    let md = {};
    if (global.localStorage) {
        try {
            md = JSON.parse(global.localStorage.getItem("rgl-8")) || {};
        } catch (e) {
            /*Ignore*/
        }

    // console.log("return value ", md[key]);
    return md[key];
  }
}



Solution

  • Looks like incremental keys are needed to apply updated layout. Change the key value from el.id to i

    createElement(el) {
        const removeStyle = {
          position: "absolute",
          left: "11px",
          top: 0,
          cursor: "pointer"
        };
    
        const i = el.i;
    
        return (
          <div key={i} data-grid={el} >
            <span
              className="remove"
              style={removeStyle}
              onClick={this.onRemoveItem.bind(this, i)}
            >
              x
            </span>
          </div>
        );
      }