How to add copied dictionaries to array in Javascript? - reactjs

I have function named "createdAtRefine" to change items' createdAt from milliseconds to MM/DD below.
const createdAtRefine = (datasum) => {
for (let i = 0; i < datasum.length; ++i) {
datasum[i].createdAt = `${
new Date(parseInt(datasum[i].createdAt)).getMonth() + 1
}/${new Date(parseInt(datasum[i].createdAt)).getDate()}`;
}
return datasum;
};
And I added several dictionary arrays to dataSumArray below.
datasumArray was set [] empty array.
datasumArray.push(
...feedData.seeAdminAllFeeds,
...poemData.seeAdminAllPoems,
...fpLikesData.seeAdminAllLikes,
...fpCommentsData.seeAdminAllComments,
...pedometerData.seeAdminAllPedometers
);
feedData.seeAdminAllFeeds looks like below.
-> [{__typename: 'Feed', id: '3', createdAt: '18382034'}, {__typename: 'Feed', id: '3', createdAt: '18382034'}]
Once all these dictionaries were added to one array I need to change createdAt from milliseconds to 'MM/DD' with above "createdAtRefind" function.
So i run below code.
let createdAtRefind = createdAtRefine(datasumArray);
Then outcome is good, it change to
-> [{__typename: 'Feed', id: '3', createdAt: '10/8'}, {__typename: 'Feed', id: '3', createdAt: '10/8'}]
But Problem is I need to call all this function at first with useEffect like below.
useEffect(() => {
if (
feedData !== undefined &&
feedData !== null &&
poemData !== undefined &&
poemData !== null &&
fpLikesData !== undefined &&
(fpLikesData !== null) & (fpCommentsData !== undefined) &&
fpCommentsData !== null &&
pedometerData !== undefined &&
pedometerData !== null
) {
datasumArray.push(
...feedData.seeAdminAllFeeds,
...poemData.seeAdminAllPoems,
...fpLikesData.seeAdminAllLikes,
...fpCommentsData.seeAdminAllComments,
...pedometerData.seeAdminAllPedometers
);
let createdAtRefind = createdAtRefine(datasumArray);
setDatasum(createdAtRefind);
}
}, [feedData, poemData, fpLikesData, fpCommentsData, pedometerData]);
When I first call this function, it works great.
But if I go to another screen and go back to this screen, **createdAtRefind** becomes all createdAt as '1/1'.
-> [{__typename: 'Feed', id: '3', createdAt: '1/1'}, {__typename: 'Feed', id: '3', createdAt: '1/1'}]
The reason is when I run "createdAtRefine" function, it change the initial data of feedData.seeAdminAllFeeds.
So initial data becomes
-> [{__typename: 'Feed', id: '3', createdAt: '10/8'}, {__typename: 'Feed', id: '3', createdAt: '10/8'}]
And so I run two times createdAtRefind function, so it becomes '1/1'..
I want to run this createdAtRefind function just once when this data comes regardless of screen change.

You're mutating your objects inside the dataSum array. It's best practice in JS, especially in React to avoid mutation. You're getting 1/1 because new Date(parseInt("10/8")).getMonth() + 1 gives you the number 1.
What you can do is use map
const createdAtRefine = (datasum) => {
return datasum.map((data) => ({
...data, // Spread data into new object
createdAt = `${new Date(parseInt(datasum[i].createdAt)).getMonth() + 1}/${new Date(parseInt(datasum[i].createdAt)).getDate()}`
}))
};
Here for each dataSum element I create a new objects with the same keys and values as the original object except for createdAt.
If you're planning on displaying this createdAt date another way to go would be to keep the original datasum array with createdAt in ms and convert only when displaying the date

Related

React: render objects based on value type

I have an object array with a structure similar to this:
export interface obj {
id: number,
date: string,
source: string,
}
const obj: obj[] | undefined = [
{ id: 1, date: "2021-01-17", source: "data" },
{ id: 2, date: "2021-11-23", source: "data" },
{ id: 3, date: "2020-05-03", source: "draft" },
{ id: 4, date: "2022-09-08", source: "draft" },
{ id: 5, date: "2021-12-04", source: "data" },
{ id: 6, date: "2021-09-08", source: "empty" },
];
const [objectData, setObjectData] = useState<obj[]>();
I'm trying to return and render the first occuranses of each source type, sorted by the nearest date. So in the example above I'd like to return the object with id: 5 for "data", id: 4 for "draft" and id: 6 for "empty".
This is what I got so far:
Sorting object by dates descending in useEffect and store variable in useState
const sortByDate = obj?.slice().sort((a, b) => {
return b.date.valueOf() - a.date.valueOf();
})
And then trying to map out my component like so:
{objectData?.map((m) =>
m.source === "draft" ? (
<Data id={m.id} date={m.date} source={m.source} />
) : m.applicationType === "data" ? (
<Data id={m.id} date={m.date} source={m.source} />
) : m.applicationType === "empty" ? (
<Data id={m.id} date={m.date} source={m.source} />
)
)}
But this will ofcourse render out every object..How can I render out only the one instance of each object source type that i want?
Given your approach, I would
a) Sort the items in the list in a descending manner.
b) Create a list of possible unique source names
c) Render the unqiue source names and get the first item matching the source name in my descending ordered array.
You can get rid of b) if you want to define your list of source names statically. If that is the case, you need to handle if there is no item found with the source name that is selectecd.
const items = [
{ id: 1, date: "2021-01-17", source: "data" },
{ id: 2, date: "2021-11-23", source: "data" },
{ id: 3, date: "2020-05-03", source: "draft" },
{ id: 4, date: "2022-09-08", source: "draft" },
{ id: 5, date: "2021-12-04", source: "data" },
{ id: 6, date: "2021-09-08", source: "empty" },
];
// a) Sort the array with date object (descending)
const sortedArray = items.sort((a, b) => {
return new Date(b.date).valueOf() - new Date(a.date).valueOf();
});
// b) This will let you get all possible sources dynamically and create an array of unique entries
const uniqueSources = [...new Set(sortedArray.map(item => item.source))];
// c) Map over (existing) unqiue entries in the sorted list and get the first one found
uniqueSources.map((source) => {
const firstObject = sortedArray.find((entry) => entry.source === source);
console.log('source:', source, firstObject);
});
I think you probably do (or should) know the possible values of source, and in fact those should be the type for source, ex:
export interface obj {
id: number,
date: string,
source: 'data' | 'draft' | 'empty',
}
Then in your useEffect you have the internal variable sortedByMostRecent which is your array of data sorted by the date value.
With that you can use a constant array (defined at the top of your file, outside the component definition around the interface definition) of the source options:
const availableSources: interface['source'][] = ['data', 'draft', 'empty'];
To find the first object in the sorted array matching each available (possible) source. Something like:
const topSourceItems = [];
for (source of availableSources) {
const topSource = sortedByMostRecent.find(item => item.source == source);
if (topSource) {
topSourceItems.push(topSource);
}
}
topSourceItems would then be stored in state and rendered with a regular iteration in the markup.
Notes:
Your .slice() isn't doing anything. Also calling an array "obj" is probably not the best decision, but I understand this is likely just dummy code for the example.

How to duplicate object in react

Even replicating the object succeeded. However, the id value is duplicated the same. Therefore, if you click the delete button, the deletion will be done together.But I want to get a different ID. How can I fix it here?
const handleduplicate = id => {
const dupDataGroup = [...Groupdata].filter(item => item.id === id);
const newGroup = [dupDataGroup[0], ...Groupdata];
setGroupData(newGroup);
};
The result when I didn't do anything =>
0: {id: 0, title: 'blur', subtitle: 'Img'}
1: {id: 1, title: 'blurs', subtitle: 'Ima'}
2: {id: 2, title: 'Free ', subtitle: 'Ima'}
handleduplicate click result=>
0: {**id: 0**, title: 'blur', subtitle: 'Img'}
1: {**id: 0,** title: 'blur', subtitle: 'Img'}
2: {id: 1, title: 'blurs', subtitle: 'Ima'}
3: {id: 2, title: 'Free ', subtitle: 'Ima'}
I hope that only the ID value will change when duplicated.
React does not automatically update the key index in JSON objects. Also, in this scenario, you're fetching the value and inserting it into the newGroup.
If you really want to update the keys, I'd suggest creating a for loop to update the values of id.
const handleduplicate = id => {
const dupDataGroup = [...Groupdata].filter(item => item.id === id);
const newGroup = [dupDataGroup[0], ...Groupdata];
for (var i = 0; i < newGroup.length; i++)
newGroup[i].id = i;
setGroupData(newGroup);
};
You can duplicate an object by using the spread operator.
const obj = {name: 'ritik', surname: 'banger'};
const objCopy = {...obj};
or use of Object.assign will work for you.
let objcopy = Object.assign({}, obj);
You must note that here we are referencing the original object 'obj' and thus any changes in 'objcopy' will reflect in 'obj', this is known as shallow copy.
If you want to deep copy the object and you wish to have different reference for both then,
Using JQuery library:
import jQ from "jquery";
let objcopy = jQ.extend(true, obj, {});
Using lodash library:
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
[...Groupdata] creates a new list with the (non-duplicated) items in Groupdata. This is important if you'd want to add the item twice to the list, as changing one with change the same object as you already noticed. You will also have to duplicate the item itself.
Note that duplicating the list itself isn't necessary if you're just going to filter on it.
What I also think is happening, given the additional information in the comments, is that setGroupData is used multiple times. If you use Groupdata, followed by setGroupData, then Groupdata is not updated accordingly. So just make sure you use that function once.
const handleduplicate = (groupData, id, newId) => {
const dupDataGroup = groupData.filter(item => item.id === id); // no copying here.
const rest = groupData.filter(item => item.id !== id);
const newGroup = [
{ // the item is duplicated (note: no deep copy)
...dupDataGroup[0],
id: newId // updated with the new identifier
},
...rest
];
return newGroup;
};
const duplicated = handleduplicate(Groupdata, 123, 456);
setGroupData(duplicated);

In React, how do I name a field of my form that is part of an array?

I'm building a React 16.13.0 application. In my form, I want to submit data (an address) as part of an array, so I set up my state like so ...
constructor(props) {
super(props);
this.state = {
countries: [],
provinces: [],
errors: [],
newCoop: {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
country: FormContainer.DEFAULT_COUNTRY,
}],
enabled: true,
email: '',
phone: '',
web_site: ''
},
I then created these functions for managing changes to the input fields ...
  handleInput(e) {
    let self=this
    let value = e.target.value;
    let name = e.target.name;
    this.setValue(self.state.newCoop,name,value)
  }
  setValue = (obj,is, value) => {
       if (typeof is == 'string')
         return this.setValue(obj,is.split('.'), value);
       else if (is.length === 1 && value!==undefined) { 
         return this.setState({obj: obj[is[0]] = value});
       } else if (is.length === 0)
         return obj;
       else
         return this.setValue(obj[is[0]],is.slice(1), value);
  }
...
                <Input inputType={'text'}
                   title= {'Street'} 
                   name= {'addresses[0].formatted'}
                   value={this.state.newCoop.addresses[0].formatted} 
                   placeholder = {'Enter address street'}
                   handleChange = {this.handleInput}
                   errors = {this.state.errors} 
                  /> {/* Address street of the cooperative */}
The Input.jsx file looks like the below ...
const Input = (props) => {
    return (  
  <div className="form-group">
      <FormLabel>{props.title}</FormLabel>
      <FormControl
            isInvalid={props.errors && Boolean(props.errors[props.name])}
            type={props.type}
            id={props.name}
            name={props.name}
            value={props.value}
            placeholder={props.placeholder}
            onChange={props.handleChange}
          />
      {props.errors && props.errors[props.name] && (
          <FormControl.Feedback type="invalid">
                 {props.errors[props.name].map((error, index) => (
                     <div key={`field-error-${props.name}-${index}`} className="fieldError">{error}</div>
                 ))} 
          </FormControl.Feedback>
      )}
  </div>
    )
}
export default Input;
However, when I attempt to change the value, I get the below error. I'm not sure what else I need to be doing to name my component such that I can successfully change it's value. I would prefer not to change the data structure in my constructor, but I'm willing to if that's what it takes.
TypeError: Cannot set property 'formatted' of undefined
FormContainer.setValue
src/containers/FormContainer.jsx:127
124 | if (typeof is == 'string')
125 | return this.setValue(obj,is.split('.'), value);
126 | else if (is.length === 1 && value!==undefined) {
> 127 | return this.setState({obj: obj[is[0]] = value});
| ^
128 | } else if (is.length === 0)
129 | return obj;
130 | else
ISSUE:
Cannot set property 'formatted' of undefined
// Reason : because you can't access obj["addresses[0]"]["formatted"]
// Solution : it should look something like obj["addresses"][0]["formatted"]
Because you are splitting up string by ., so a result you are getting
[
"addresses[0]",
"formatted"
]
Now that you have successfully splitted up the string ,
You are trying to get object by name, specifically obj["addresses[0]"], But you can't access the object index like this,
It will give you undefined, so as a result, you are getting the above error. you can check that exact error by running below code snippet,
const obj = {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
}],
};
const names = "addresses[0].formatted".split(".")
console.log("obj['addresses[0]'] ===>" , obj[names[0]])
console.log("obj['addresses[0]']['formatted'] ===>" , obj[names[0]][names[1]])
SOLUTION :
So now question is if not obj["addresses[0]"] this then what, the solution is obj["addresses"]["0"],
So you have 2 options :
First : change this addresses[0].formatted to addresses.0.formatted
Second : you need to split the sting with .split(/[\[\].]+/)
I would prefer second option as this addresses[0].formatted looks real form name, and this is how it should look like, you can check that in below code snippet also.
const obj = {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
}],
};
const names = "addresses[0].formatted".split(/[\[\].]+/)
console.log("obj['addresses'] ==>" , obj[names[0]])
console.log("obj['addresses']['0'] ==>" , obj[names[0]][names[1]])
console.log("obj['addresses']['0']['formatted'] ==>" , obj[names[0]][names[1]][names[2]])
NOTE :
Now, once you solved the issue, real issue come up in the picture, obj: obj[is[0]] = value, here obj is object so this will throw error , and also your setValue function is limited to that functionality only, it should be generic
handleInput = e => {
let name = e.target.name;
let value = e.target.value;
const keys = name.split(/[\[\].]+/);
this.setState(this.updateValue(this.state, keys, value));
};
// I've created a recursive function such that it will create a
// copy of nested object so that it won't mutate state directly
// obj : your state
// name : input name
// value : value that you want to update
updateValue = (obj, name, value, index = 0) => {
if (name.length - 1 > index) {
const isArray = Array.isArray(obj[name[index]]);
obj[name[index]] = this.updateValue(
isArray ? [...obj[name[index]]] : { ...obj[name[index]] },
name,
value,
index + 1
);
} else {
obj = { ...obj, [name[index]]: value };
}
return obj;
};
WORKING DEMO :
Your code is quite confusing, that's part of your problem to begin with, the other problem with your code is that it is not good practice to have nested objects in react's state. You can learn more by reading this answer in this other question.
Here is an example of what you could do with your code to set the state, however, notice that this is a bad way of solving the issue:
handleInput(e) {
let value = e.target.value;
this.setState(prevState =>{
...prevState,
newCoop: {
...prevState.newCoop
addresses: [
{
...prevState.newCoop[0].addresses
formatted: value
}
]
}
})
}

Access data from arr of objects using props passed to children - error

Using props passed down to functional component to access data from an array of objects. Getting the following result when the code is running.
I have tried assigning props.id to a variable and using that in its place but get the same error, why is this happening and how can I work around it/fix it?
Object:
const items = [
{
url: 'sample url 1',
desc: 'Description 1'
},
{
url: 'sample url 2',
desc: 'Description 2'
}
Attempt to log the data:
console.log(items[props.id].url);
Output:
TypeError: Cannot read property 'url' of undefined
Id sounds a bit misleading here. What you are trying to do is to assign the index of an element to the variable props.id. The index can be (as you can read in the comment by Felix) be 0 or 1 since your array has two elements in it.
There are two options here:
Stick with using index
Call it props.index and make sure that props.index < items.length at all times to avoid Type and OutOfBounds errors.
Actually creating an id
const items = [
{
id: 1,
url: 'sample url 1',
desc: 'Description 1'
},
{
id: 2,
url: 'sample url 2',
desc: 'Description 2'
}
and using props.id to filter for the id as follows:
const item = items.find(i => i.id === props.id);
if(!item) {
console.log('this id does not exist!', id);
}
// now you can use the item

Find object in state and update property

I have a problem with a dynamic state I am setting. My first state looks like this:
const [exercises, setExercises] = useState([{
id: 123,
title: "Title here",
category: "someCategory"
}])
A user then selects an item of this state. I create a second state representing the selected object, but adding additional properties to it. For instance I am adding and initializing the properties 'amount' and 'unit'.
const [selectedExercises, setSelectedExercises] = useState([{
id: 123,
title: "Title here",
category: "someCategory",
amount: 0,
unit: ''
}])
I want the user to choose amount and unit from a form. How do I access and change those two properties in the state? Since I don't know the user's selection, I have to find the object within the state first.
I have tried things like (el being called from an input element somewhere):
setSelectedExercises([
...selectedExercises,
(selectedExercises.find(exercise => exercise.title === el.title).amount = 1),
])
How do I find the object in question and update its amount property (for example in an onChange method)?
const [selectedExercises, setSelectedExercises] = useState([{
id: 123,
title: "Title here",
category: "someCategory",
amount: 0,
unit: ''
}]);
// Your handler should look like this and
// you should call handleAmountChange(el.id, 1)
function handleAmountChange(amount, id) {
setSelectedExercises(prev => prev.map(selectedExercise => {
if (selectedExercise.id === id) {
return {
...selectedExercise,
amount
}
}
return selectedExercise;
}));
}
A more generic function to change any property would look like this.
function handleChange(id, property, value) {
setSelectedExercises(prev => prev.map(selectedExercise => {
if (selectedExercise.id === id) {
return {
...selectedExercise,
[property]: value
}
}
return selectedExercise;
}));
}

Resources