React : Add an object into an array - reactjs

I'm a beginner with React, and I would like to add an object into an array.
Here is my code :
const initialState =
{
messages: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_MESSAGE':
return {
messages: update(state.messages, {$push: [{text: action.text}]})
};
default:
return state
}
}
And in my component:
<ul>{this.props.chat.messages.map((message) =>{ return <li>{message.text}</Link></li> })
}
And I get the error:
Encountered two children with the same key,[object Object]. Child keys must be unique; when two children share a key, only the first child will be used.
Thank you for your help.

You have to provide unique keys for each list item. Your messages don't have a key/ID and you need to provide a uniquely generated ID or as a last resort, use the index (which should be avoided as long as possible). The code above can be refactored as:
{ this.props.chat.messages.map((message, index) => (<li key={index}>{message.text}</li>) }

By definition, Arrays are immutable data.
Following in the best practices, preferred use the $splice method.

Related

Change state from reducer not from root state using React-Redux when data is inherited

Good afternoon. I am writing an application using react-redux and faced a dilemma. I have already re-thought it several times and can't choose how to organize the project and data structure correctly and conveniently. Design data by inheriting or composing data. I initially went along the path of composition, but I realized that it is inconvenient when there is a one-to-one relationship. I decided to change it to inheritance, because it seemed logical from the point of view of data organization, but there was a big difficulty with reducers, more precisely, I am confused that it turns out to be a single root reducer with a lot of actionTypeskeys . I remember about performance, when elements inherit a data chain from a common ancestor, that this is very bad. And yet I chose this path and I have a question: Please tell me if it is possible to split into several reducers for each level of nesting data. Example
onst initState: IPages = {
idActive: 0,
pages: [
{
id: 1,
title: `Tab #1`,
workspace: {
idActiveDraggableElements: [],
idActiveLines: [],
attributes: {
height: string,
width: string,
viewBox: [0, 0, 5000, 5000]
},
draggableElements: [], // more data
lines: [], // more data
}
},
]
}
Reducer:
export function pagesReducer(
state: IPages = initState,
action: IPageActionTypes
) {
switch (action.type) {
case "ADD_PAGE":
let uniqId = getUniqKeyIdOfArrayList(state.pages);
return {
...state,
pages: state.pages.concat({id:uniqId, title:`Вкладка - ${uniqId}`})
}
case "REMOVE_PAGE": return {
...state,
pages: state.pages.filter(item => item.id !== action.id)
}
case "CHOSE_PAGE": return {
...state,
idActive: action.id
}
case "RENAME_PAGE":
let indexPage = state.pages.findIndex(item => item.id === action.id);
state.pages[indexPage].title = action.title;
return {
...state
}
// ===================
// LONG LIST WHAT BAD...
// It's a bad idea to add editing to the `workspace` field and then `draggableElements`. `lines`
// ... but I understand that this will happen, because I don't know if there is another way.
default:
return state
}
}
Can I edit the `workspace' node without updating the entire application state?
Thanks you for any help.
For data modeling aspect for a 1-to-1 relationship, you can choose either to reference by id or to embed the data. It depends on your query pattern.
In your case which is embedding, you can make use of memoized selectors.
Ideally, since you have an idActive, update your pages data structure to be an object instead of a list.
Like so:
{
pages: {
'1': {
workspace: { ... },
}
}
}
Then for your reducer, think of it as slicing a tree (or nested attribute). Your reducer would then look something like:
function workspaceReducer(state, action) {
// TODO
}
function pagesReducer(state, action) {
switch (action.type) {
case 'UPDATE_WORKSPACE': {
const { id } = action;
const page = Object.assign({}, state.pages[id]);
return {
...state,
pages: {
...state.pages,
[id]: {
...page,
workspace: workspaceReducer(page.workspace, action)
}
}
}
}
}
}
Then to prevent unnecessary re-renders, using memoized selectors,
it would be like:
import { createSelector } from 'reselect';
const pages = state => state.pages;
const activePage = state => state.idActive;
const getActivePage = createSelector(
activePage,
pages,
(id, pages) => pages[id]
);
const getWorkspace = createSelector(
getActivePage,
page => page.workspace
);

Update value in array in reducer

I have a reducer
const initialState = {
elements: [{"flag": false}, {"flag": false}]
};
const checkReducer = function (state = initialState, action) {
switch (action.type) {
case CHANGE_CHECK:
return {...state, //here I want update element by index, that pass in action};
I need to update an existing value in array elements, that I get by the index passed in action.
I can do it like this
state.elements[action.key] = {"flag": !action.flag}
but then I'll change existing state. according to the redux principles, I can't do it.
So I have to use spread operator and change new object. But I don't know how to use it this way.
Tried something like this
...state, elements[action.index]: {"flag": !action.flag}]
but it isn't worked. Is there a way to do what I want?
return {
...state,
elements: state.elements.map((element, index) => {
if (index === action.key) {
return {"flag": !action.flag}
}
return element
})
}
array#map will create a new array, and change only the item whose index match action.key.
If you find this process tedious, you could use libraries that let mutate your state while keeping the reducer returning new state. One of those is immer.

reactjs and redux: How to implement filter text box for a rows of data

I am learning reactj and redux. Somehow i managed to get data from api and display them in table. The table is shown below.
I want to add filter text box below each column header so that when i type text, it shows those values matching the results.
What is the flow using redux. Or any example
I just typed this up, its just for a general idea of how to filter data in your mapstate function. This is called computing derived data https://redux.js.org/docs/recipes/ComputingDerivedData.html and its good to use tools like Reselect for performance benefits. This isnt working code, but its just to give you an idea of how it is usually done so you dont get duplicate state in your reducers.
class Table extends React.Component {
filterData = (event, column) => {
this.props.someFilterAction(event.target.value, column)
}
renderItem = (item) => {
return <someItemDiv searchAction={(e) => this.filterData(e, item.column) } />
}
render(){
<div>
{ this.props.data.map(this.renderItem) }
</div>
}
}
function mapStateToProps(state) {
const searchItem = state.searchData;
// reselect is good for stuff like this
const filteredData = state.tableData.filter(item => item[searchItem.column].text.indexOf(searchItem.input) !== -1 )
// or some kind of search criteria you want
return {
data: filteredData,
searchItem: state.searchData
}
}
const mapDispatchToProps = {
someFilterAction: YourReduxActionCreator
}
export default connect(mapStateToProps, mapDispatchToProps)(Table);
function YourReduxActionCreator(input, column) {
return {
type: FILTER_DATA,
payload: {
column,
input,
}
}
}
function yourReducer() {
switch(state, action) {
case FILTER_DATA:
return action.payload
default:
return state;
}
}
Hey you just need to create 2 selectors.
The first one : getTableData() that get your whole table data from your store
Then getFilteredTableData() which will filter the result of getTableData() using the form values of your field (your keyword and the column).
The selector getFilteredTableData() should be the source of data for your table.
So as soon as your form change your table will be refiltered !

Using object rest to delete nested object

I have a react app with some redux state that looks like:
{
shape1: {
constraints: {
constraint1: {
key: value
},
constraint2: {
key: value
}
}
},
shape2: {
constraints: {
constraint1: {
key: value
},
constraint2: {
key: value
}
}
}
}
I dispatch an action and want to delete one of the constraint objects, ie. constraint1 for shape1. Here is what my reducer looks like for this action, say I'm trying to delete constraint1 from shape1:
case DELETE_CONSTRAINT:
shape = action.payload; // ie. shape1, the parent of the constraint I
// am trying to delete
let {
[shape]: {'constraints':
{'constraint1': deletedItem}
}, ...newState
} = state;
return newState;
This removes the entire shape1 object from the state instead of just the individual constraint1 object. Where I am going wrong/ what is the best approach for doing this? I'd prefer to use object rest in order to be consistent with the rest of my code.
Thanks.
When using the rest syntax in destructuring to get a slice of the object, you'll get everything else on the same "level".
let {
[shape]: {'constraints':
{'constraint1': deletedItem}
}, ...newState
} = state;
In this case newState takes everything else is everything but [shape].
Since your state has multiple nesting levels, you'll have to extract the new constraints using destructuring and rest syntax, and then create a new state.
const state = {
shape1: {
constraints: {
constraint1: {
key: 'value'
},
constraint2: {
key: 'value'
}
}
},
shape2: {
constraints: {
constraint1: {
key: 'value'
},
constraint2: {
key: 'value'
}
}
}
};
const shape = 'shape1';
const constraint = 'constraint1';
// extract constraints
const {
[shape]: {
constraints: {
[constraint]: remove,
...constraints
}
}
} = state;
// create the next state
const newState = {
...state,
[shape]: {
...state[shape], // if shape contains only constraints, you keep skip this
constraints
}
}
console.log(newState);
In short, no not with an object - not with the spread operator.
You can do it other ways without mutating your state though, such as a filter, for example:
return state.filter((element, key) => key !== action.payload);
Consistency sidenote
As a sidenote - there is a vast difference between consistency in approach and style vs consistency of actual code. Don't feel the need to shoe horn something for consistency if it makes more logical sense to do it a different way. If it truely breaks the consistency of the application that other developers are working on, document why it's different.

reducer: adding to array data

if i pull some data from an external source fro the initial state, then want to add additional information like for example 'liked'?
i've tried adding to the products array but its go messy, I'm thinking i should have an additional array for liked items then put the product id in this, the only thing is i need it to reflect in the product that it has been liked and I'm mapping the product data to the item.
whats the best way to go about this ?
const initialState = {
isFetching: false,
products: [],
};
should i add favs: [] ?
how would i reflect the liked state to my product as I'm mapping the products array to the product component? and the liked state is now in the favs?
i tried doing this to add it to the product array but it got really messy (something like this)
case ADD_LIKED:
state.products[action.index]['liked'] = true;
return state;
state.products[action.index]['liked'] = true;
The problem here is that you are mutating the state inside the reducer which is one of the things you should never do inside a reducer.
You'll find that writing functions which don't mutate the data are much easier if you break them down into smaller parts. For instance you can start to split your application up.
function productsReducer(products = [], action) {
// this reducer only deals with the products part of the state.
switch(action) {
case ADD_LIKED:
// deal with the action
default:
return products;
}
}
function app(state = {}, action) {
return {
isFetching: state.isFetching,
products: productsReducer(state.products, action)
}
}
In this case I would definitely want to write a little immutability helper.
function replaceAtIndex(list, index, replacer) {
const replacement = replacer(list[index]);
const itemsBefore = list.slice(0, index),
itemsAfter = list.slice(index + 1);
return [...itemsBefore, replacement, ...itemsAfter];
}
You can complement this with a generic function for changing objects in lists.
function updateInList(list, index, props) {
return replaceAtIndex(list, index, item => {
return { ...props, ...item };
});
}
Then you can rewrite your function in the immutable form
switch(action) {
case ADD_LIKED:
return updateInList(products, action.index, { liked: true });
default:
return products;
}
You could even get fancy by partially applying the function. This allows you to write very expressive code inside your reducers.
const updateProduct = updateInList.bind(this, products, action.index);
switch(action) {
case ADD_LIKED:
return updateProduct({ liked: true });
case REMOVE_LIKED:
return updateProduct({ liked: false });
default:
return products;
}

Resources