How do I stop duplication of an object in localStorage? - javascript

I've a component call KeywordLocation.js, and It has one prop named location.
this component is a mapped array and on click I want to save the object of location in localStorage. I created here an empty array and pushing the object on every click. For now I'm getting 5 mapped location objects. when I click on any of them, it saves the object but on 2nd click it doesn't stop duplicating the object. How do I stop this duplication?
searchedLocation.map((location, i) => {
return (
<KeywordLocation
setShowMap={props.setShowMap}
location={location}
key={i}
getPositionFromManualSearch={props.getPositionFromManualSearch}
/>
);
});
KeywordLocation.js
const Component = ({ location }) => {
let allSearchedLocations = [];
const redirectToMap = async () => {
allSearchedLocations.push(location);
allSearchedLocations = allSearchedLocations.concat(
JSON.parse(localStorage.getItem("recent_location_searched") || "[]")
);
const previousLocation = JSON.parse(
localStorage.getItem("recent_location_searched")
);
console.log(previousLocation);
localStorage.setItem(
"recent_location_searched",
JSON.stringify(allSearchedLocations)
);
};
return (
<div onClick={() => redirectToMap()} className="pt-md cursor-pointer">
<p>{location.structured_formatting.main_text}</p>
<p className="text-xs border-b border-black pb-md ">
{location.description}
</p>
</div>
);
};

Are you entirely sure the duplication is ocurring on local storage?
As long as you use the same key, recent_location_searched, there will be only one value stored on that key. Take a look at the "Storage" tab on your browser's debug console to see what's actually being stored.
All evidence seems to point that the duplication is ocurring at the searchLocations variable, not atlocalStorage.
You might try to add some conditional logic that prevents you from pushing to searchLocations if the location is the same as the one on the last item on the array.

The problem is not related to localStorage but more about the usage of the array structure. You could rely on JavaScripts object to store the unique values. You lose the insertion order but you can create a companion array that keep a reference to the order.
const Test = ({ location }) => {
const redirectToMap = () => {
const locations =
JSON.parse(localStorage.getItem("recent_location_searched")) || {};
locations[location.name] = location;
localStorage.setItem("recent_location_searched", JSON.stringify(locations));
};
return (
<div onClick={() => redirectToMap()} className="pt-md cursor-pointer">
<p>{location.name}</p>
</div>
);
};
export default function App() {
const data =
JSON.parse(localStorage.getItem("recent_location_searched")) || {};
return (
<div>
<div className="App">
{[
{ name: "location1" },
{ name: "location3" },
{ name: "location2" }
].map((location) => (
<Test key={location.name} location={location} />
))}
</div>
<ul>
{Object.values(data).map((location) => (
<li key={location.name}>Saved {location.name}</li>
))}
</ul>
</div>
);
}

Related

ReactJS - Can i save specific rendered mapped data, to a variable?

I have data mapped in a component like this:
import React from "react";
import { useState } from "react";
import { useEffect } from "react";
import { get } from "lodash";
const Products = ({ data }) => {
return (
data.map((item, index) = > (
<div id={index}>
<img src={item.img} /> <br />
{item.name} <br />
{get(moreData, `[${item.name.toLowerCase()}].info[0]`)}
{get(moreData, `[${item.name.toLowerCase()}].info[1]`)}
{get(moreData, `[${item.name.toLowerCase()}].info[2]`)}
</div>
I want to be able to store this data:
{item.name}
{get(moreData, `[${item.name.toLowerCase()}].info[0]`)}
{get(moreData, `[${item.name.toLowerCase()}].info[1]`)}
{get(moreData, `[${item.name.toLowerCase()}].info[2]`)}
in a string, like string = {item.name},{moreData.item.name.toLowerCase().info[0]},...//etc
However you cannot declare variables inside of a component (as far as i know, still new to this).
I've tried .concat() - after each line and .push() with array instead of string:
{item.name} <br />
{dataString.concat(item.name)}
{dataArr.push(item.name)}
{get(moreData, `[${item.name.toLowerCase()}].info[0]`)}
{get(moreData, `[${item.name.toLowerCase()}].info[1]`)}
{get(moreData, `[${item.name.toLowerCase()}].info[2]`)}
I was going to use DOM, but i've been told it's bad practice to use DOM in react.
I've also tried using state in the same way:
const [dataString, setDataString] = useState("");
...
{item.name}
{setDataString((dataString += item.name))}
But nothing seems to work as intended for me, and i'm out of ideas.
Edit:
I want to be able to copy the 'string/text' to clipboard eventually. So it can be imported to another site. Their required format is Item1, item1-info1, item1-info2, item1-info3, item2, item2-info1, item2-info2, item2-info3...etc
here is an example of how you can use the data object outside of the mapped objects.
also an example how to convert the data to an string in the format required.
because I don't know the structure of the data object of yours I just created an one, take this code and change it to the data structure
const data = [
{
name: "name1",
info1: "info1-1",
info2: "info1-2",
info3: "info1-3",
},
{
name: "name2",
info1: "info2-1",
info2: "info2-2",
info3: "info2-3",
},
{
name: "name3",
info1: "info3-1",
info2: "info3-2",
info3: "info3-3",
},
];
change this function to one that fits your needs.
const getStringOfData = (data) => {
let stringArray = [];
data.map((item) => {
stringArray.push(item.name);
stringArray.push(item.info1);
stringArray.push(item.info2);
stringArray.push(item.info3);
});
let stringResult = stringArray.join(",");
return stringResult;
};
useEffect(() => {
console.log("onMounte with useEffect");
let stringResult = getStringOfData(data)
console.log(stringResult);
}, []);
you can also call this function on onClick function depend in your requirement , the data object is available almost everywhere in your component
Continuing our discussion from the comments, it looks like there are two different things you are trying to do here:
display the UI to the user in a certain way
to be able to copy the information in a specific stringified format
I would advice to split these two behaviors, because they are totally unrelated to each other in terms of logic.
Start by aligning the information in your components how you need it.
const Product = ({ item, index }) => {
// do whatever you need to do with the information
// to display it correctly
const identifier = name.toLowerCase()
const infos = (moreData[identifier] && moreData[identifier].info) || {}
return (
<div>
{ info[0] && <p> info[0] </p>}
{ info[1] && <p> info[1] </p>}
{ info[1] && <p> info[2] </p>}
</div>
)
}
const Products = ({ data }) => {
const onClick = useCallback(
async () => {
// parse data and copy it using a separate
// util method.
const dataToString = /* actual implementation of the formatting to a string representation */
copyTextToClipBoard(dataToString)
},
[data]
)
return (
<div>
<button onClick={onClick} />
<div>
data.map((item, index) => (
<Product item={item} index={index} key={item.id} />
))
</div>
</div>
)
};
Now this would be your UI, the copyToClipboard method, is a bit of a hack, and looks like so:
const copyTextToClipBoard = (text) => {
const element = document.createElement('textarea');
element.style = { display: 'none' }; // eslint-disable-line
element.value = text;
document.body.appendChild(element);
element.select();
document.execCommand('copy');
document.body.removeChild(element);
};

How to map over an array of objects in another file in React?

I am trying to refactor my code and in doing so, I am extracting a single item and putting it into its own component. This MemberItem component has multiple functions state that influence its rendering, however, when I start passing props, the component breaks. I am passing all of the functions, properties and state the the child component needs, but I am still unable to get it to render properly.
// Members.js (Parent Component)
export const Members = () => {
// BELOW ARE THE FUNCTIONS AND STATE THAT INFLUENCE THE CHILD COMPONENT
const [memberName, setMemberName] = useState('')
const [editingMemberName, setEditingMemberName] = useState(
members.map(() => false)
)
// Update member name
const editMemberName = async (_, index) => {
let new_editing_members_state = members.map(() => false)
new_editing_members_state[index] = true
setEditingMemberName(new_editing_members_state)
}
// Cancel editing mode
const cancelEditMemberName = async (_, index) => {
let new_editing_members_state = members.map(() => false)
new_editing_members_state[index] = false
setEditingMemberName(new_editing_members_state)
}
// UPDATE name in database
const updateMemberName = async (index, id) => {
let new_editing_members_state = members.map(() => false)
new_editing_members_state[index] = false
setEditingMemberName(new_editing_members_state)
}
// BELOW, LOOPS OVER EACH ITEM
const memberItems = members.map((member, index) => {
return (
<MemberItem
member={member}
index={index}
editingMemberName={editingMemberName[index]}
editMemberName={editMemberName}
handleChangeName={handleChangeName}
updateMemberName={updateMemberName}
cancelEditMemberName={cancelEditMemberName}
destroyMember={destroyMember}
/>
)
})
return (
// RENDER THE LIST OF ITEMS
{memberItems}
)
}
// Member.js (Child Component)
export const MemberItem = (
member,
index,
editingMemberName,
editMemberName,
handleChangeName,
updateMemberName,
cancelEditMemberName,
destroyMember
) => {
return (
<div
key={member.id}
>
<div>
{editingMemberName[index] ? (
<input
type="text"
placeholder="Johnny Appleseed"
onChange={handleChangeName}
/>
) : (
<>
<div>
{member.name.substring(0, 1).toUpperCase()}
</div>
<h3>{member.name}</h3>
</>
)}
</div>
<div>
{editingMemberName[index] ? (
<button
onClick={() => updateMemberName(index, member.id)}
>
<CgCheckO size=".75em" />
</button>
) : (
<button
onClick={() => editMemberName(member.id, index)}
>
<FiTool size=".75em" />
</button>
)}
<button>
{editingMemberName[index] ? (
<GiCancel
onClick={() => cancelEditMemberName(member.id, index)}
size=".75em"
/>
) : (
<RiDeleteBinLine
onClick={() => destroyMember(member.id)}
size=".75em"
/>
)}
</button>
</div>
</div>
)
}
Currently, I am getting an error of TypeError: editingMemberName is undefined and a warning of Each child in a list should have a unique "key" prop, but if you see, I do pass in an id into the key property.
In React, props are passed down to function components as a single object.
Your component function assumes props are passed down as separate arguments and not in a single object.
Fixed component definition (note the brackets around the argument list):
MemberItem = ({
member,
index,
editingMemberName,
editMemberName,
handleChangeName,
updateMemberName,
cancelEditMemberName,
destroyMember
}) => { ... }
This method of unpacking properties is called object destructuring.

Using Objects in React

I am recieving an object from a websocket,
const client = new
W3CWebSocket("ws://ABCD:9080/user");
I want to access values from the object and display it on the browser.
const [object, setObject] = useState("");
client.onopen = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const newObj = JSON.parse(e.data);
Next I want to set new state with the object I recieved.
setObject(newObj.data);
Next, I want to map through that object and access the values inside it:
return (
<div className="App">
<Navbar />
{Object.keys(object).map((objKey, index) => (
<div key={index}>
<p> {objKey} : {object[objKey]}</p>
</div>
))}
<DataTable object = { object } />
</div>
);
How do I map through the object and display the values I need on the browser.
I think I'm missing something since nothing is getting displayed n my browser.
You have used Object.keys(setObject) where, setObject is a function, and will return an empty array [].
Use Object.keys(object) instead
return (
<div className="App">
<Navbar />
{Object.keys(object).map((objKey, index) => (
<div key={index}>
<p> {objKey} : {object[objKey]}</p>
</div>
))}
<DataTable object = { object } />
</div>
);
You are passing setObject instead of object in Object.keys()

how to update FieldArray elements with redux store variable

I am using redux-form with a FieldArray.By default 1 element will be there in array and it is populated from JSON. I can add upto 3
elements in FieldArray component.
In below code, 'elementList'
property is coming from JSON and also I have store variables named
as'elements' and 'elementList'. I initialize these store variable with elementList
from JSON at first and then keep updating store variables when 'Add
Element' is clicked on. I can see store variables are updating
properly but on screen Field array elements are not updating.It may be because name property 'elementList' in FieldArray may refer to
JSON element.
Is it possible, if I can refer to store variables 'elementList' or 'elements' in name property
of 'FieldArray'. Please advice.
Main page
<div>
<FieldArray name="elementList" component={Elements}
props={this.props}/>
<button type="button" className="btn btn-primary"
onClick={event => this.addElement(elementDTO)}>Add Element
</button>
<br/>
</div>
addElement(elementDTO){
if(this.props.elements && this.props.elements!=undefined && this.props.elements.length >= 3){
return;
}
this.props.addReqElement(this.props.elements);
}
Field Array page
const elements= ({props, meta: {error, submitFailed}}) => {
const {fields} = props;
return (
{fields.map((element, index) => (
<div>
//Field definitions
</div>
))}
Thank you
Update:
Adding method from Redux Action and Reducer
export function addReqElement(childList) {
let state = store.getState()
let newChild=
state.requestReducer.DTOobj.requestDoc; //this is an empty element coming from backend with few properties and adding rest of the //proerties as below to create a new child
newChild.prop1 = null
newChild.prop2 = null
childList.push(newChild)
return (dispatch) => {
dispatch(setDoc(childList));
}
}
export function setDoc(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Update 2: I tried to remove push and used spread operator , but it did not work. I have inner array also, that is working as I am using different strategy. I take pull parent array ,it's index and update parent array with the new inner array. It works but parent array I am not getting how should I make it work. I tried to set the main array to the form props and render full page by dispatching an action but it did not work. Any suggestions plz?
From the main array page:
render() {
const {fields, parentArrayFromStore} = this.props;
return (
<div className="col-sm-12">
{fields.map((doc, index) => (
<div key={index}>
<div className="col-sm-12">
<FieldArray name={`${doc}.innerArrayList`} component={CustomInnerArrayComponent}/>
</div>
<div className="row">
<div className="col-sm-4">
<button type="button" className="btn btn-primary"
onClick={event => this.addInnerArray(index, parentArrayFromStore ,fields.get(index).innerArrayList)}>Add Printer
</button>
</div>
</div>
</div>
))}
</div>)
}
In Action class
export function addInnerArray(index, parentArrayFromStore, innerArrayList) {
let newInnerItem= {};
newInnerItem.prop1 = null
newInnerItem.prop2 = null
innerArrayList.push(newInnerItem)
parentArrayFromStore[index].innerArrayList = innerArrayList;
return (dispatch) => {
dispatch(setParentArray(parentArrayFromStore));
}
}
export function setParentArray(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Hi the issue is with the push statement in your function when updating states in the constructor or reducer use concat or spread operator[...]>
I have made a sample over here
please check
onAddItem() {
let list = [...this.state.items, {text:`Check 1`}];
this.setState({items:list});
}
in your case you can do the following
let arr = [...childList, newChild]
then dispatch the arr
dispatch(setDoc(arr));

React returns older state value onClick

I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.
Is there a way to have the current state recognized within that delete function?
https://codesandbox.io/s/twilight-water-jxnup
import React, { useState } from "react";
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => deleteSpan(props.index)}>DELETE</button>
Length: {spans.length}
</div>
);
};
//set initial span w/ useState
const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
//add new span
const addSpan = () => {
let key = Math.random();
setSpans([...spans, <Span key={key} index={key} />]);
};
//delete span
const deleteSpan = index => {
console.log(spans);
console.log(spans.length);
};
//clear all spans
const clearInputs = () => {
setSpans([]);
};
return (
<>
{spans}
<button onClick={() => addSpan()}>add</button>
<button onClick={() => clearInputs()}>clear</button>
</>
);
}
UPDATE - Explaining why you are facing the issue descibed on your question
When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.
This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.
Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.
You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
Length: {spans.length}
</div>
);
};
const [spans, setSpans] = React.useState([]);
return (
<>
{spans.length
? spans.map((span, index) => (
<Span key={span} index={index} span={span} />
))
: null}
<button onClick={() => setSpans([
...spans,
new Date().getTime(),
])}>add</button>
<button onClick={() => setSpans([])}>clear</button>
</>
);
}
I hope this helps you find your way.

Categories

Resources