I have a list of objects that are selectable, eg.
let Tasks = [Task A, Task B, Task C, Task D]. I'm using FlatList to render the tasks. Upon clicking a task, user is taken to an "Edit Task" for the selected task. I can use redux to update selected element to the array index or alternatively I can store the selected task key.
case SELECT_TASK:
return {
...state,
selected_habit: action.array_index
};
case SELECT_TASK:
return {
...state,
selected_habit: action.key
};
On the task specific page, the user can edit the task. Then in redux, I could do either of the following:
case EDIT_TASK: {
let tasks = { ...state.tasks};
tasks[state.selected_task] = edited_task; //not showing edit code
return {
...state
tasks: tasks
}
}
//don't use array index, instead use task key
case EDIT_TASK:
return {
...state
tasks: state.tasks.map(task => {
if (task.key === action.key) {
//update task code
return task;
} else return task;
})
}
Within each task, I may have an array of subtasks requiring a flatlist or similar. Accessing these nested arrays using Redux seems messy. It would be seem preferable to hold an object and access specific keys. However, FlatList requires an array.
1) Should I consider storing tasks in redux as an object, and building an array from the object before rendering so I can use FlatList? This would allow be to more easily update specific attributes of tasks (without storing selected states or mapping for each update)?
2) If the current pattern, is there significant performance upside to storing array element instead of object key?
Related
In my use case I have an array of characters, each character has multiple builds, and each build has a weapons string, and artifacts string. I'm making a tool to select portions of each string and assign them to a value, e.g. assigning index 3-49 of weapons to a specific weapon.
const [characterIndices, setCharacterIndices] = useState<
{ builds: { weaponIndices: SE[]; artifactSetIndices: SE[] }[] }[]
>([
...characters.map((char) => {
return {
builds: [
...char.builds.map((_build) => {
return {
weaponIndices: [],
artifactSetIndices: [],
};
}),
],
};
}),
]);
The SE type is as follows:
type SE = { start: number; end: number; code: string };
//start and end are the respective start and end of selected text
//code is the specific artifact or weapon
The weaponIndices and artifactSetIndices basically hold the start and end of selected text in a readonly textarea.
I have a function to add a SE to either weaponIndices or artifactSetIndices:
const addSE = (
type: "weaponIndices" | "artifactSetIndices",
{ start, end, code }: SE,
characterIndex: number,
buildIndex: number
) => {
let chars = characterIndices;
chars[characterIndex].builds[buildIndex][type].push({ start, end, code });
setCharacterIndices((_prev) => chars);
console.log(characterIndices[characterIndex].builds[buildIndex][type]);
};
I think that using a console log after using a set function isn't recommended, but it does show what it's intended to the weaponIndices, or artifactSetIndices after an entry is added.
Passing the addSE function alongside characterIndices to a separate component, and using addSE, does print the respective indices after adding an entry, but the component's rendering isn't updated.
It only shows up when I "soft reload" the page, when updating the files during the create-react-app live reload via npm run start.
In case you are confused about what the data types are, I've made a github repo, at https://github.com/ChrisMGeo/ght-indexer/tree/main/src at src/data.json. That JSON file describes what the character data looks like, including the builds, and each build's weapons and artifacts(called artifact_sets in the JSON)
Looks to me you are not updating the state at all.
Here you are just storing the same object reference that you already have in state into a new variable chars.
let chars = characterIndices;
chars now holds reference to a same object as characterIndices.
Here you are mutating that same object
chars[characterIndex].builds[buildIndex][type].push({ start, end, code });
And here you are updating the state to the same object that is already in the state. Notice that no state update here occurs.
setCharacterIndices((_prev) => chars);
Object you have in state is mutated, but you did not "change" the value of the state, thus no component re-render.
What you could maybe do is create a copy of the object, mutate that and update the state. just change chars assignment like this:
let chars = {...characterIndices};
React often compares values using Object.is() only to a single level of nesting (the tested object and its children).
It will not re-render if the parent is found equal, or if all the children are found equal.
React then considers that nothing has changed.
In your implementation, even the first top-level check will immediately fail, since Object.is(before, after) will return true.
You could use an Immutable objects approach to eliminate this concern when setting a new state (either directly through spreading values or with a support library such as Immer).
For example instead of setting the values within the object...
myObj.key = newChildObj
...you would make a new object, which preserves many of the previous values.
myObj === {...myObj, key: newChildObj}
This means that every changed object tree is actually a different object (with only the bits that haven't changed being preserved).
To read more about this see https://javascript.plainenglish.io/the-effect-of-shallow-equality-in-react-85ae0287960c
I get an error Cannot add property key, object is not extensible using "#apollo/client": "^3.0.2" with ant design.
I can successfully obtain data from the server with my graphql query, however, the problem is that, the data inside the return object is somehow unextendable. This never used to be a problem prior to using apollo client (using apollo boost).
In order to make my data work in a table in ant design, I have to pass in the array data obtained from the data object returned from the server. The part where I get the error is when I attempt to iterate through the items array, and add a key to each object, so that react won't complain in the console of a missing key element for the html.
const items = [...data.items];
console.log("items:", items);
// Add a key for each item to make React stop complaining
// Erorr occurs here!!!
items.forEach(item => {
item.key = item.id;
});
Is there a way we can remove this unnecessary functionality that's hindering our ability in modifying and extending results obtained from the server? Also I tried spreading the array making a deep copy, but that's not working either.
What do I need to do to make sure that I can add an additional key field in each array item, based on the id?
Addendum:
I don't actually need the code that does the items.forEach() stuff. However, if I don't add it, react will complain that the entries in the table are all missing a key value. This is my workaround since tables in ant design get their data from an array of objects, and thus, need to have a key value in there. Since querying data from the server typically doesn't include a key property in an object, this has to be manually added.
Managed to figure it out by deep copying the array data, via lodash.
Use _.cloneDeep() on the returned data's object that is the name of the object/table you are querying for.
import * as _ from "lodash";
import { Table } from "antd";
function Component({ id, query }) {
const { loading, error, data } = useQuery(query, {
variables: { id }
});
if (loading) return <div />;
if (error) return `Error! ${error}`;
const items = _.cloneDeep(data.items);
console.log("items:", items);
// Add a key for each item to make React stop complaining
items.forEach(item => {
item.key = item.id;
});
return (
<Table
dataSource={items}
/>
);
I have an objectdailyData: {}, and when I'm making ajax request, this object contains some data like this:
So, how to put uniqe id (for example using library shortid) to every object in dailyData.daily ?
Just in case, my reducer's code:
case GET_DAILY_DATA: {
return {
...state,
dailyData: action.data,
}
}
You will have to iterate through the whole array and add the unique id to each of the objects in daily.
So, before saving the data to your state you can do something like this:
dailyData.daily.map(data => {...data, uniqueId: id }))
Don't use the index as unique id, just in case you have to append more data to this in the future, it won't remain unique anymore.
You can use the index of each object, I don't know the exact pourpose of doing that but if you going to iterate the array you have access to the index, like that:
this.props.daily.map((item, index) => }
//use the index like an unique ID
})
You need to be careful of how to use it. This way works pretty well in some cases and don't in others.
I am having a hard time with a React.js project. I have a TODO list and there is a function to mark a task as done. I have a button next to each and every task and if I click on it the following function will be called.
crossLine(key){
const currentTaskArray = [...this.state.task]
const deletedItems = currentTaskArray.filter(deletedItem => deletedItem.key == key);
const taskAfterDeleted = currentTaskArray.filter(deletedTask => deletedTask.key !== key);
this.setState({
task: [taskAfterDeleted,deletedItems]
})
};
I first need to strike this item ( just html strikethrough) so that I know it is done. After that, it should be removed from the array and should be moved to the bottom of the list and the rest of the items should be moved accordingly. The following function does not seem to contain anything inside deletedItems.
you can find the full project at https://codesandbox.io/s/jovial-gauss-s12bf.
.filter() returns an array of the remaining elements. Therefore, taskAfterDeleted and deletedItems are both arrays. You'll need to spread them in your new state:
[...taskAfterDeleted, ...deletedItems]
I am struggling to find a good way to organise my state in Redux with React. It is a simple Blog with posts. I am getting an array from the api that is already ordered from oldest to newest. Since the app is a bit more complicated, I'd like to store the state in an object with the uuid as keys, so I can access it easily.
{
uuid_post1: { ...post1 },
uuid_post2: { ...post2 },
...
}
With the format above it is easy for me to sync the state between API and React without refetching data all the time. But I do need to display the output in an ordered form from newest to oldest.
Is there an easy way to solve this and keep the date info in the object? Is there another good way to organise state for this use case?
Thanks.
In your reducer you'll want to index the posts by ID and also save the sorted IDs. This will let you look up the posts efficiently, and also maintain a list of them in the order that you received them (oldest to newest). You can get them in the reverse order using a selector.
switch (action.type) {
case 'POSTS_RECEIVED':
return {
...state,
orderedPostIDs: posts.map(p => p.id),
postsById: posts.reduce((acc, post) => {
acc[post.id] = post;
}, {});
}
}
With this orderedPostIDs is an array of Post IDs and postsById is an object where the keys are Post IDs and the values are the posts.
function getPostByID(state, postId) {
return state.posts.postsById[id];
}
// Should use reselect here because it's returning a new array with every call
// oldest to newest - post are received from API in this order
function getPostsSortedByDateAscending(state) {
return state.posts.orderedPostIDs.map(id => getPostByID(state, id));
}
// Should use reselect here because it's returning a new array with every call
// newest to oldest
function getPostsSortedByDateDescending(state) {
// copy to new array, because Array.reverse mutates the value
return [].concat(getPostsSortedByDateAscending(state)).reverse();
}
Another approach which makes your state simpler is to only store postsById, as Max commented below. To get the sorted posts, you'll use Object.values() and then sort them as needed.
// oldest to newest - need to use reselect here
function getPostsSortedByDateAscending(state) {
return _.sortBy(Object.values(state.posts.postsById), p => p.date)
}
// oldest to newest - need to use reselect here
function getPostsSortedByDateDescending(state) {
// copy to new array, because Array.reverse mutates the value
return [].concat(getPostsSortedByDateAscending(state)).reverse();
}