I've already asked that question but my explanation was pretty bad, so I decided to ask again with a better explanation and with actual code (I'll ask moderators to delete one of the posts). So let's consider the problem.
Following snippet represents rendering notes from array. However, during the adding note part, I mutate a state. So the question is: how can I add a new note in notes array without mutating? In other words, I want to remove replaceNotes and remain the same functionality. I know that it's possible to add notes without array at all, but I do need to update array with notes in due to the future reference. The ting is, in my original application I've got lists with notes, and while I switch between lists, I should get rendered notes that relies to the list I switch on. That's why I should keep the reference to notes array.
At the same time I'm wondering, would it be okay, if I just store notes in localStorage and then take notes from that data? Is it a good practice in functional programming?
const button = document.getElementById('button');
const notesContainer = document.querySelector('.notes');
const pipe = (f, g) => (...args) => f(g(...args));
let notes = [];
const createNote = (...fns) => fns.reduceRight(pipe);
const handleEvent = () =>
createNote(gatherContent, renderContent, replaceNotes)(notes);
function gatherContent(notes) {
const name = prompt('How do you want to name a note?');
return [...notes, { name }];
}
function renderContent(notes) {
function render(note) {
const noteEl = document.createElement('div');
noteEl.innerHTML = `<p>${note.name}</p>`;
notesContainer.append(noteEl);
}
notesContainer.innerHTML = '';
notes.map(render);
return notes;
}
const replaceNotes = newNotes => (notes = newNotes);
button.addEventListener('click', handleEvent);
<button id="button">Click me!</button>
<section class="notes"></section>
Here is how to create a simple task list app without mutating anything except for the DOM.
const button = document.getElementById("button");
const section = document.getElementById("notes");
const template = document.getElementById("template");
template.parentNode.removeChild(template);
const render = notes => {
button.onclick = event => {
const name = prompt("How do you want to name a note?");
render([...notes, { name }]);
};
while (section.lastChild) {
section.removeChild(section.lastChild);
}
for (const note of notes) {
const node = template.cloneNode(true);
node.firstChild.firstChild.nodeValue = note.name;
section.appendChild(node);
}
};
render([]);
<button id="button">Click me!</button>
<section id="notes"></section>
<div id="template"><p>name</p></div>
For a detailed explanation, read my previous answer. https://stackoverflow.com/a/58642199/783743
You can use this pattern with localStorage too.
const button = document.getElementById("button");
const section = document.getElementById("notes");
const template = document.getElementById("template");
template.parentNode.removeChild(template);
const render = notes => {
localStorage.setItem("notes", notes); // set notes
button.onclick = event => {
const name = prompt("How do you want to name a note?");
render([...notes, { name }]);
};
while (section.lastChild) {
section.removeChild(section.lastChild);
}
for (const note of notes) {
const node = template.cloneNode(true);
node.firstChild.firstChild.nodeValue = note.name;
section.appendChild(node);
}
};
render(localStorage.getItem("notes") || []); // get notes
Note that localStorage should only be used to save state that you want to use across sessions. It's not recommended to use localStorage as your application store. That would result in both bad performance and bad code structure.
Related
I have 2 separate div elements and I have a function that defines each element and is supposed to only act on the element. so when i call typeField(name,element1) it should type into only element1 and similarly then I call typeField(address,element2) it should type into only element2.. but currently if you check my codepen implementation it's mixing the characters and I am not sure why.
Also after this I need to figure a way out where I can get the name to type in first and then after that's done I need to start the typing of the address. My assumption is I need to promisify the functions and then call them as async/await. Is that the right approach? or am I overthinking this?
So my 2 questions are
What am I doing wrong in my code that's mixing up the contents in elements innerHTML?
Once that's resolved how can I make the first element type first and on completion start the next one
Any help in resolving this would be appreciated.
This is the link to my Codepen
https://codepen.io/alimbolar/pen/Rwjoqbm?editors=1111
const typewriter1 = document.getElementById('typewriter1');
const typewriter2 = document.getElementById('typewriter2');
const name = 'Alim Bolar';
const address = "Some part of the world";
const currentLocation = "Bali, Indonesia";
const profile = "Frontend Development, Backend Development, WordPress Development"
let content = "";
let i = 0;
function typeField(x, element) {
if (i < x.length) {
alphabet = x.charAt(i);
content = content + alphabet;
element.innerHTML = content;
i++;
setTimeout(function() {
typeField(x, element)
}, 500);
}
}
typeField(name, typewriter1);
typeField(address, typewriter2);
<header>
<div>NAME : <span id="typewriter1"></span></div>
<div>ADDRESS : <span id="typewriter2"></span></div>
</header>
You need to
not have i or content be global, because then it'll be shared by all calls of typeField
have typeField assign to a persistent Promise outside that further calls are chained off of
Consider awaiting a Promise inside a loop, it'll look a lot cleaner than recursion.
const typewriter1 = document.getElementById('typewriter1');
const typewriter2 = document.getElementById('typewriter2');
const name = 'Alim Bolar';
const address = "Some part of the world";
const currentLocation = "Bali, Indonesia";
const profile = "Frontend Development, Backend Development, WordPress Development"
let prom = Promise.resolve();
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function typeField(textToInsert, element) {
prom = prom.then(async () => {
for (const char of textToInsert) {
element.textContent += char;
await delay(500);
}
});
}
typeField(name, typewriter1);
typeField(address, typewriter2);
<header>
<div>NAME : <span id="typewriter1"></span></div>
<div>ADDRESS : <span id="typewriter2"></span></div>
</header>
I have a function to render comments, in which each comment is stored as an object in an array. Comments can have reply comments, in which they have the exact same html and data to render, just their styling is different (via a CSS modifier class).
How can I make this function recursive? The renderReplies(comment.replies) calls a function that is the exact same as renderComments function, just without the mentioned function call renderReplies (as a reply to a reply is the exact same also in styling terms).
const renderComments = (comments) => {
commentsElement.innerHTML = '';
comments.forEach(comment => {
commentsElement.innerHTML += html;
// data-id attribute
const liElements = commentsElement.querySelectorAll('.comment');
const liElement = liElements.item(liElements.length - 1);
liElement.setAttribute('data-id', comment.id);
// author
liElement.querySelector('.comment__author').innerHTML = comment.user.username;
// avatar src & alt attributes
const avatar = liElement.querySelector('.comment__avatar');
avatar.setAttribute('src', comment.user.image.png);
avatar.setAttribute('alt', comment.user.username);
// time since posted
// content
const p = liElement.querySelector('.comment__text');
p.appendChild(document.createTextNode(comment.content));
// score
liElement.querySelector('.comment__score b').innerHTML = comment.score;
// replies
renderReplies(comment.replies);
});
};
Check whether there's a replies property. If it exists, call the function recursively. Since replies won't have replies of their own, you'll stop there.
const renderComments = (comments) => {
commentsElement.innerHTML = '';
comments.forEach(comment => {
commentsElement.innerHTML += html;
// data-id attribute
const liElements = commentsElement.querySelectorAll('.comment');
const liElement = liElements.item(liElements.length - 1);
liElement.setAttribute('data-id', comment.id);
// author
liElement.querySelector('.comment__author').innerHTML = comment.user.username;
// avatar src & alt attributes
const avatar = liElement.querySelector('.comment__avatar');
avatar.setAttribute('src', comment.user.image.png);
avatar.setAttribute('alt', comment.user.username);
// time since posted
// content
const p = liElement.querySelector('.comment__text');
p.appendChild(document.createTextNode(comment.content));
// score
liElement.querySelector('.comment__score b').innerHTML = comment.score;
// replies
if (comment.hasOwnProperty("replies")) {
renderComments(comment.replies);
}
});
};
I am using draft js and there I do have a situation that needs to put a new li element from another source of data.
I tried using the https://github.com/facebook/draft-js/issues/493 approach that case I am able to add data but that also not retaining the undo/redo stack.
Is there a way I can maintain undo/redo stack?
function appendBlocksFromHtml(editorState, htmlString) {
const newBlockMap = htmlToDraft(htmlString);
const contentState = editorState.getCurrentContent();
const blockMap = contentState.getBlocksAsArray();
newBlockMap.contentBlocks = blockMap.concat(newBlockMap.contentBlocks);
const newContentState = ContentState.createFromBlockArray(newBlockMap, contentState.getEntityMap());
return EditorState.moveSelectionToEnd(EditorState.createWithContent(newContentState));
}
appendBlocksFromHtml what currently does is create new EditorState and returns.
We can retain old state while using EditorState.push
function appendBlocksFromHtml(editorState, htmlString) {
const newBlockMap = convertFromHTML(htmlString);
const contentState = editorState.getCurrentContent();
const blockMap = contentState.getBlocksAsArray();
newBlockMap.contentBlocks = blockMap.concat(newBlockMap.contentBlocks);
const newContentState = ContentState.createFromBlockArray(
newBlockMap,
contentState.getEntityMap(),
);
return EditorState.moveSelectionToEnd(
EditorState.push(editorState, newContentState, 'change-block-data'),
);
}
I've been learning JavaScript for about a month now, and right now I’m working on a little project (Shopping Cart) to dive into storage events. I’ve successfully managed to store a few keys from “tab1” into the local storage and retrieve them from “tab2”. However, I came across a particular “problem”, which I’ve been struggling with for the last couple of days.
On “tab1” I have five keys that are being stored in localStorage. on “tab2” I need to complete an action (create an HTML element) every time the localStorage changes. The problem is that the action is being triggered once for every key that’s changed. In other words, I keep getting 5 duplicate HTML elements.
I’ve spent many hours searching for answers on forums, YouTube videos, blogs and of course here. So far no luck. I’ve also been reading the documentation on localStorage, but since I’m new at this, it’s not very clear for me.
I hope you can help find a solution or understand why I keep getting these duplicate actions.
This is an example of the code I have so far:
let itemList = document.getElementById("itemList");
let summaryItem = document.getElementById("summaryItem_01");
let summaryImage = document.getElementById("itemImage_01");
let summaryName = document.getElementById("item_Name_01");
let summaryModel = document.getElementById("itemModel_01");
let summaryQuantity = document.getElementById("detailQuantityDisplay_01");
let summaryPrice = document.getElementById("itemPriceAmount_01");
//Gets localStorage info on page load and feeds summaryItem fields.
window.addEventListener("load", () => {
let itemImage = localStorage.getItem("modalItemImage");
let itemName = localStorage.getItem("modalItemName");
let itemModel = localStorage.getItem("modalItemModel");
let itemQuantity = localStorage.getItem("modalItemQuantity");
let itemPrice = localStorage.getItem("modalItemUnitPrice");
//Prints localStorage info to summaryItem element.
summaryImage.setAttribute("src", itemImage);
summaryName.innerText = itemName;
summaryModel.innerText = itemModel;
summaryQuantity.value = itemQuantity;
summaryPrice.innerText = itemPrice;
});
//This is where I'm getting the duplicate action
window.addEventListener("storage", () => {
let a = document.createElement("article");
itemList.appendChild(a);
});
UPDATE:
After MauriceNino's suggestion, I ended up with this code and it worked perfectly:
//Tab1
let modalItem = {
modalItemImage: displayModalImage.innerHTML.slice(10, -2),
modalItemName: displayModalName.innerText,
modalItemModel: displayModalModel.innerText,
modalItemQuantity: displayModalQty.value,
modalItemUnitPrice: displayModalPrice.innerText,
modalItemTotal: displayModalTotal.innerText,
};
localStorage.setItem("modalItem", JSON.stringify(modalItem));
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//Tab2
//Gets localStorage info from modal
let data = JSON.parse(localStorage.getItem("modalItem"));
let dataArray = Object.values(data);
let itemImage = dataArray[0];
let itemName = dataArray[1];
let itemModel = dataArray[2];
let itemQuantity = dataArray[3];
let itemPrice = dataArray[4];
//Prints localStorage info to summaryItem element.
summaryImage.setAttribute("src", itemImage);
summaryName.innerText = itemName;
summaryModel.innerText = itemModel;
summaryQuantity.value = itemQuantity;
summaryPrice.innerText = itemPrice;
//Creates new summaryItem when there's a chnage on localStorage.
window.addEventListener("storage", () => {
let a = document.createElement("article");
itemList.appendChild(a);
});
You could just save it as one element like so:
//Gets localStorage info on page load and feeds summaryItem fields.
window.addEventListener("load", () => {
// Get all the data in a single statement
let { itemImage, itemName, itemModel, itemQuantity, itemPrice }
= localStorage.getItem("modalItem");
//Prints localStorage info to summaryItem element.
summaryImage.setAttribute("src", itemImage);
// ...
});
//Will be called only once now
window.addEventListener("storage", () => {
let a = document.createElement("article");
itemList.appendChild(a);
});
// This is where you are saving your localStorage settings
localStorage.setItem("modalItem", {
itemImage: 'image',
itemName: 'name',
itemModel: 'model',
itemQuantity: 2,
itemPrice: 3
});
Let's see the next situation:
If we create an user, we have to create a new client, a new user, and a new, inital project for the user.
db = {
users: {},
clients: {},
projects: {}
};
const usersRef = firebase.database().ref("/users");
const clientsRef = firebase.database().ref("/clients");
const projectsRef = firebase.database().ref("/projects");
To keep the code clean, and separated, we can create three functions:
const newUserToDb = name => {
const newUser = usersRef.push();
newUser.set({name});
};
const newClientToDb = name => {
const newClient = clientsRef.push();
newClient.set({name});
};
const newProjectToDb = name => {
const newProject = projectsRef.push();
newProject.set({name});
};
const createUserToDb = (userName, clientName, projectName) => {
newUserToDb(userName);
newClientToDb(clientName);
newProjectToDb(projectName);
};
To make all the changes in one place, but make the code less separated:
const createUserToDb = (userName, clientName, projectName) => {
const userId = usersRef.push().key;
const clientId = clientsRef.push().key;
const projectId = projectsRef.push().key;
const updates = {};
updates[`/users/${userId}`] = userName;
updates[`/clients/${clientId}`] = clientName;
updates[`/projects/${projectId}`] = projectName;
firebase.database().ref().update(updates);
};
Is there any important difference between the two solutions above? Which is more efficient?
The important difference to the above approach is atomicity. In the first scenario, the individual collection or documents update will succeed or fail without affecting other updates. In the second scenario, all the updates will succeed else none will.
I don't think efficiency is the right term to be used for comparing the above scenarios, its more of the business/use case which will define which one you need to use
The first way seems more separated and explicit which would probably be easier for other developers to understand.