0

I am new to React.js and I was creating a notes takign website using React.js and Bootstrap. I was saving the notes data in local storage for simplicity but I will upgrade this app to save the notes data in Firebase. However, when I was trying to load the notes from local storage and showing them in the a div element of id 'notes' it gives error that: TypeError: Cannot set property 'innerHTML' of null

I want to run the load notes function after all the html or every thing else is loaded. When I run this function using button click listener after all the HTML is laoded it works.

The code is:

import React from 'react';

import './Home.css';

function Home() {
    const loadNotes = () => {
        let notes = localStorage.getItem('notes');
        let titles = localStorage.getItem('titles');

        let notesObj;
        let titlesObj;

        if (notes == null) {
            notesObj = [];
        }
        else {
            notesObj = JSON.parse(notes);
        }

        if (titles == null) {
            titlesObj = [];
        }
        else {
            titlesObj = JSON.parse(titles);
        }

        let html = '';
        notesObj.forEach(function (element, index) {
            html += `<div class="noteCard my-2 mx-2 card" style="width: 18rem;" id = ${index}>
                        <div class="card-body">
                            <h5 class="card-title">Note</h5>
                            <p class="card-text">${element}</p>
                            <button id = ${index} onclick = 'deleteNote(this.id)' class="btn btn-primary"><i class="fa fa-trash" style="margin-right: 10px;"></i>Delete Note</button>
                        </div>
                    </div>`
        });

        titlesObj.forEach(function (er, i) {
            html = html.replace('<h5 class="card-title">Note</h5>', `<h5 class="card-title">${er}</h5>`);
        });
    
        let notesElm = document.getElementById('notes');

        if (notesObj.length != 0) {
            notesElm.innerHTML = html;
        }

        else {
            notesElm.innerHTML = `<h4>Nothing to show here.</h4>`;
        }

        console.log('Notes shown.')
    }

    const addNote = () => {
        let addTxt = document.getElementById('addTxt');
        let notes = localStorage.getItem('notes');
        
        let addTitle = document.getElementById('addTitle');
        let titles = localStorage.getItem('titles');

        let notesObj;
        let titlesObj;

        if (notes == null) {
            notesObj = [];
        }
        else {
            notesObj = JSON.parse(notes);
        }

        if (titles == null) {
            titlesObj = [];
        }
        else {
            titlesObj = JSON.parse(titles);
        }

        notesObj.push(addTxt.value);
        titlesObj.push(addTitle.value);

        localStorage.setItem('notes', JSON.stringify(notesObj));
        localStorage.setItem('titles', JSON.stringify(titlesObj));

        addTxt.value = '';
        addTitle.value = '';

        loadNotes();
        
        console.log("Note added.")
    }
    return (
        <div className="home">
            <style type="text/css">
                {`
                    .btn {
                        margin-right: 10px;t
                    }

                    .home__mainTitle {
                        margin-top: 60px;
                    }
                `}
            </style>

            <div class="container my-3">
                <h1 class='home__mainTitle'>Welcome to Magic Notes</h1>
                <div class="card">
                    <div class="card-body" id = 'editor'>
                        <h5 class="card-title">Add title</h5>
                        <div class="form-group">
                            <input className='home__formInput' type="text" class="form-control" id="addTitle" rows="3" placeholder="Title"></input>
                        </div>
                        <h5 class="card-title">Add notes</h5>
                        <div class="form-group">
                            <textarea class="form-control" id="addTxt" rows="3" placeholder="Notes"></textarea>
                        </div>
                        <button class="btn btn-primary" onClick={ addNote } id='addBtn'><i class="fa fa-plus-square"></i>Add Note</button>
                        <button class="btn btn-primary" id='clearAllBtn'><i class="fa fa-eraser"></i>Clear All</button>
                    </div>
                </div>
                <h1 className='home__notesTitle'>Your Notes</h1>
                <hr/>
                <div id="notes" class="row container-fluid">

                    {/* <!-- <div class="noteCard my-2 mx-2 card" style="width: 18rem;">
                        <div class="card-body">
                            <h5 class="card-title">Note 1</h5>
                            <p class="card-text"></p>
                            <a href="#" class="btn btn-primary">Delete Note</a>
                        </div>
                    </div> --> */}

                    { loadNotes() }

                </div>
            </div>
        </div>
    );
}

export default Home;

Thanks and sorry from any bad mistakes in code or description of problem.

1
  • we should avoid accessing the DOM directly in React, because ReactJS plays on Virtual DOM so it's not a good and efficient practise to access DOM by DOM API's. so use the ref prop for getting access to the element and changing its behaviour. Commented Feb 27, 2021 at 7:10

2 Answers 2

1

By trying to access the DOM and set innerHTML directly, you're sort of fighting against some of the general principals of React.

In this specific case, it's failing because the div doesn't actually exist in the DOM when you first try to mutate it.

Take a look at this very partial refactor:

import React, { useEffect, useState } from 'react';
import './Home.css';

function Home() {
    const [notes, setNotes] = useState([]);
    const [titles, setTitles] = useState([]);

    useEffect(() => {
      setNotes(JSON.parse(localStorage.getItem('notes')) ?? []);
      setTitles(JSON.parse(localStorage.getItem('titles')) ?? []);
    },[]);

    const addNote = () => {
        let addTxt = document.getElementById('addTxt');
        
        let addTitle = document.getElementById('addTitle');

        let newTitles = [...titles, addTitle.value];
        let newNotes = [...notes, addTxt.value];

        localStorage.setItem('notes', JSON.stringify(newNotes));
        localStorage.setItem('titles', JSON.stringify(newTitles));

        setNotes(newNotes)
        setTitles(newTitles)

        
        addTxt.value = '';
        addTitle.value = '';

        console.log("Note added.")
    }
    return (
        <div className="home">
            <style type="text/css">
                {`
                    .btn {
                        margin-right: 10px;t
                    }

                    .home__mainTitle {
                        margin-top: 60px;
                    }
                `}
            </style>

            <div class="container my-3">
                <h1 class='home__mainTitle'>Welcome to Magic Notes</h1>
                <div class="card">
                    <div class="card-body" id = 'editor'>
                        <h5 class="card-title">Add title</h5>
                        <div class="form-group">
                            <input className='home__formInput' type="text" class="form-control" id="addTitle" rows="3" placeholder="Title"></input>
                        </div>
                        <h5 class="card-title">Add notes</h5>
                        <div class="form-group">
                            <textarea class="form-control" id="addTxt" rows="3" placeholder="Notes"></textarea>
                        </div>
                        <button class="btn btn-primary" onClick={ addNote } id='addBtn'><i class="fa fa-plus-square"></i>Add Note</button>
                        <button class="btn btn-primary" id='clearAllBtn'><i class="fa fa-eraser"></i>Clear All</button>
                    </div>
                </div>
                <h1 className='home__notesTitle'>Your Notes</h1>
                <hr/>
                <div id="notes" class="row container-fluid">

                    {notes.map((note,index) => {

                    return <div class="noteCard my-2 mx-2 card" style={{width: '18rem'}} id={index}>
                        <div class="card-body">
                            <h5 class="card-title">{titles[index]}</h5>
                            <p class="card-text">{note}</p>
                            <button id={index} onclick='deleteNote(this.id)' 
                               class="btn btn-primary"><i class="fa fa-trash" style={{marginRight: "10px;"}}></i>Delete Note</button>
                        </div>
                    </div>

                    })}

                    {notes.length === 0 ? <h4>Nothing to show here.</h4> : null}

                </div>
            </div>
        </div>
    );
}

export default Home;

Note how I'm using useState to store the notes and titles. Documentation: https://reactjs.org/docs/hooks-state.html

The useEffect is called when the component is mounted and loads the data from localStorage. https://reactjs.org/docs/hooks-effect.html

Then, in the body of the render, instead of calling loadNotes and trying to mutate a DOM that doesn't exist yet, I just map the notes and titles into the rendered content.

Note that this is not a complete refactor yet. For example, you may want to add listeners to your text area to keep track of the content automatically rather than pulling the content with document.getElementById. Also, delete hasn't been implemented yet, the test for localStorage content in useEffect is pretty minimal, etc. But, it's enough to get you started.

Sign up to request clarification or add additional context in comments.

1 Comment

Very very helpful answer. I knew about useEffect and useState but I do not know about their uses or implementation in real code and I know that delete note is not implemented yet but I it will be simple I think. (I have idea for it) Thanks very much! 😊
0

This error occurs because loadNotes() is wrapped inside notes div. So, you can check if the notes div is created in first place with if condition.

if (notesElm) {
  if (notesObj.length != 0) {
    notesElm.innerHTML = html;
  }

  else {
      notesElm.innerHTML = `<h4>Nothing to show here.</h4>`;
  }
}

Below is the working code:

import React from 'react';

import './Home.css';

function Home() {
    const loadNotes = () => {
        let notes = localStorage.getItem('notes');
        let titles = localStorage.getItem('titles');

        let notesObj;
        let titlesObj;

        if (notes == null) {
            notesObj = [];
        }
        else {
            notesObj = JSON.parse(notes);
        }

        if (titles == null) {
            titlesObj = [];
        }
        else {
            titlesObj = JSON.parse(titles);
        }

        let html = '';
        notesObj.forEach(function (element, index) {
            html += `<div class="noteCard my-2 mx-2 card" style="width: 18rem;" id = ${index}>
                        <div class="card-body">
                            <h5 class="card-title">Note</h5>
                            <p class="card-text">${element}</p>
                            <button id = ${index} onclick = 'deleteNote(this.id)' class="btn btn-primary"><i class="fa fa-trash" style="margin-right: 10px;"></i>Delete Note</button>
                        </div>
                    </div>`
        });

        titlesObj.forEach(function (er, i) {
            html = html.replace('<h5 class="card-title">Note</h5>', `<h5 class="card-title">${er}</h5>`);
        });
    
        let notesElm = document.getElementById('notes');
        if (notesElm) {
          if (notesObj.length != 0) {
            notesElm.innerHTML = html;
          }

          else {
              notesElm.innerHTML = `<h4>Nothing to show here.</h4>`;
          }
        }
       

        console.log('Notes shown.')
    }

    const addNote = () => {
        let addTxt = document.getElementById('addTxt');
        let notes = localStorage.getItem('notes');
        
        let addTitle = document.getElementById('addTitle');
        let titles = localStorage.getItem('titles');

        let notesObj;
        let titlesObj;

        if (notes == null) {
            notesObj = [];
        }
        else {
            notesObj = JSON.parse(notes);
        }

        if (titles == null) {
            titlesObj = [];
        }
        else {
            titlesObj = JSON.parse(titles);
        }

        notesObj.push(addTxt.value);
        titlesObj.push(addTitle.value);

        localStorage.setItem('notes', JSON.stringify(notesObj));
        localStorage.setItem('titles', JSON.stringify(titlesObj));

        addTxt.value = '';
        addTitle.value = '';

        loadNotes();
        
        console.log("Note added.")
    }
    return (
        <div className="home">
            <style type="text/css">
                {`
                    .btn {
                        margin-right: 10px;t
                    }

                    .home__mainTitle {
                        margin-top: 60px;
                    }
                `}
            </style>

            <div class="container my-3">
                <h1 class='home__mainTitle'>Welcome to Magic Notes</h1>
                <div class="card">
                    <div class="card-body" id = 'editor'>
                        <h5 class="card-title">Add title</h5>
                        <div class="form-group">
                            <input className='home__formInput' type="text" class="form-control" id="addTitle" rows="3" placeholder="Title"></input>
                        </div>
                        <h5 class="card-title">Add notes</h5>
                        <div class="form-group">
                            <textarea class="form-control" id="addTxt" rows="3" placeholder="Notes"></textarea>
                        </div>
                        <button class="btn btn-primary" onClick={ addNote } id='addBtn'><i class="fa fa-plus-square"></i>Add Note</button>
                        <button class="btn btn-primary" id='clearAllBtn'><i class="fa fa-eraser"></i>Clear All</button>
                    </div>
                </div>
                <h1 className='home__notesTitle'>Your Notes</h1>
                <hr/>
                <div id="notes" class="row container-fluid">

                    {/* <!-- <div class="noteCard my-2 mx-2 card" style="width: 18rem;">
                        <div class="card-body">
                            <h5 class="card-title">Note 1</h5>
                            <p class="card-text"></p>
                            <a href="#" class="btn btn-primary">Delete Note</a>
                        </div>
                    </div> --> */}

                    { loadNotes() }

                </div>
            </div>
        </div>
    );
}

export default Home;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.