Retrieve a single element from reducer array - javascript

I have a company reducer that I'd want to contain an array of companies. Since each object is fairly large when user goes on /company/name I only want to fetch that one object from my API and add it to this array, so if user visits the same company page multiple times I won't have to call again the api.
My reducer:
const initialState = {
companies: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_COMPANIES:
return {
...state
};
case GET_COMPANY:
return {
...state,
companies: [action.payload, ...state.companies]
};
default:
return state;
}
};
Is there a way for me to update the state in reducer and just return 1 company? Like shown above this would return all companies in this state, though logically it does not make sense with 'GET_COMPANY'
My action:
export const getCompany = name => dispatch => {
axios.get("/companies/" + name).then(res =>
dispatch({
type: GET_COMPANY,
payload: res.data
})
);
};

You need some 'id' or 'name' or other property of the company object you want to retrieve,
and than you can use find method to do this:
action.payload.find(company=>company.id===id)
According to the MDN:
The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

Related

Redux | Why this store's parameter type changes after second click?

I'm trying to push a new value in the store's state. It works fine the first time I click on the button "Add item", but the second time I got the following error: "state.basket.push is not a function". I configure the action to console log the state and got the following results:
1st click: {...}{basketItems: Array [ "44" ]}
2nd click: Object {basketItems: 0 }
Why the variable type is changing from array to an int?
Here is the code for the rendered component:
function Counter({ basketItems,additem }) {
return (
<div>
<button onClick={additem}>Add item</button>
</div>
);
}
const mapStateToProps = state => ({
basketItems: state.counterReducer.basketItems,
});
const mapDispatchToProps = dispatch => {
return {
additem: ()=>dispatch({type: actionType.ADDITEM, itemName:'Dummy text' }),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
And the reducer looks like this:
import {ADDITEM} from "../actions/types";
const initialState = { basket: [], };
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADDITEM:
console.log(state);
// let newBasket = state.basket.push('44');
return {
...state,
basket: state.basket.push('44')
};
default:
return state;
}
}
I'm copying the state before updating the basket to prevent weird behaviors.
There's two problems here:
state.basket.push() mutates the existing state.basket array, which is not allowed in Redux
It also returns the new size of the array, not an actual array
So, you're not doing a correct immutable update, and you're returning a value that is not an array.
A correct immutable update here would look like:
return {
...state,
basket: state.basket.concat("44")
}
Having said that, you should really be using our official Redux Toolkit package, which will let you drastically simplify your reducer logic and catch mistakes like this.

Compare strings from objects based on value

I'm working on a React/Redux application and I got the following problem:
I got a reducer like this:
export default (state = initialState, action) => {
switch (action.type) {
case action.type === FILTER.LIST_TYPES:
return Object.keys(state);
default:
return state;
}
};
This is the action types object:
export const FILTER = {
LIST_TYPES: "FILTER/LIST_TYPES"
};
export const listFilterTypes = () => ({
type: FILTER.LIST_TYPES
});
Now, when I dispatch this code, I receive false on the equality check like this (because they are both a string, but not the same type):
console.log(action.type === FILTER.LIST_TYPES);
I could change this for example to ==, but ESLINT does not like that and it can have some funky behaviour.. Is there a better approach to compare these object strings?
EDIT:
They are both the same value when I console.log them:
action.type: "FILTER/LIST_TYPES"
FILTER.LIST_TYPES: "FILTER/LIST_TYPES"

Pushing an object on array of objects returns array size

I am building an app with the react-boilerplate where people can make and edit events. This boilerplate uses immutable.js so I want to learn it.
When I save an event to the database, the database returns the event as an object.
console.log(action.payload.event)
would print
{
color:"#000000",
name:"my Event",
_id:"59ea882a58a83d20d400cd21",
}
This object I then want to push to the array of other events. The events get populated when a user logs in.
My reducer looks like this
import { fromJS } from 'immutable';
import { ADD_EVENT_REQUEST_SUCCESS } from './constants';
const initialState = fromJS({
events: null
});
function eventsOverviewReducer(state = initialState, action) {
switch (action.type) {
case ADD_EVENT_REQUEST_SUCCESS:
return state
.update('events', list => list.push( action.payload.event ));
case LOGIN_SUCCESS:
const user = action.payload.user;
return state
.set('events', user.events)
default:
return state;
}
}
However this update statement now sets the events in the store to the size of the array. How do I push my object to the state?
It sounds like your events is not an Immutable.List. When you set events you should create it as a list instead of a regular array:
case LOGIN_SUCCESS:
return state
.set('events', List(user.events))
Then events.push will return the new list correctly.

Filter data according to input from child Component

I have a List of Users which I want to filter during the user Types in letters in a Textfield.
In my Child component which contains the Input field I pass the input Up via props:
onEnter(event){
console.log("ENTER")
// console.log(event.target.value)
this.props.filterEmployee(event.target.value);
}
In my Container Component I take the value
filterEmployee(val){
console.log(val)
// const allUser = this.props.allUser.allUserData;
allUser .forEach(function(user){
if(user.userNameLast.indexOf(val) != -1){
console.log(user) //works
}
});
}
The allUser is an array of data connected from my Redux-store to the Container Component.
This data are also used to display the list of Users initialzied on componentWillMount.
render() {
console.log("administration")
console.log(this.props)
const allUser = this.props.allUser.allUserData;
return (
<div id="employeeAdministration">
<h1>Mitarbeiter Verwaltung</h1>
<EmployeeAdministrationFilterList
filterEmployee={this.filterEmployee.bind(this)}
/>
{/* suchfeld - Name, Department mit checkbox*/}
<ul>
{allUser.length != 0 ? allUser.map(function (item, i) {
console.log(item)
return <li key={i}>
<UserCard
userData={item}
displayPersonalInfo={true}
showRequestDates={false}
showChangePassword={false}
/>
</li>
})
: ""
}
</ul>
</div>
)
}
}
The problem now is, that I don´t know how to tell the <UserCard /> that the data has changed. How can I pass the data from the function to the render() function?
What would be the way to go here?
EDIT:
I have also tried to go the way via the reducer, but so far it didn´t worked.
filterEmployee(val){
console.log(val)
const {dispatch} = this.props;
dispatch(filterAllUser(val));
}
And the Reducer (which is not working)
function allUser(state = {allUserData: []}, action){
switch (action.type){
case 'REQUEST_ALL_USER':
return Object.assign({}, state, {
isFetching: true
});
case 'RECEIVE_ALL_USER':
return Object.assign({}, state, {
isFetching: false,
allUserData: action.items
});
case 'FILTER_ALL_USER':
return Object.assign({}, state, {
allUserData: state.allUserData.filter(user => user.userNameLast !== action.filter )
});
default:
return state
}
}
And here is the Code how the store is connected to the component
EmployeeAdministration.PropTypes = {
allUserData: PropTypes.array
};
const mapStateToProp = state => ({
allUser: state.allUser
});
export default connect(mapStateToProp)(EmployeeAdministration)
When trying this, the result is Console output of state object
This example should be able to demonstrate a basic workflow: JSFiddle.
Basically, Redux has a one-way-dataflow. The data (here is Users in the store) is flowed from the root component to the sub-components.
Whenever you want to change the value of Users inside whichever component, you create an Action and dispatch the action to some corresponding reducer. The reducer updates the store and pass it from top to bottom.
For example, you want to filter all users whose name contains "Jo":
Action creator
Pass the Action creators into the components. An action is a plain object with format like {type: "FILTER_ALL_USERS", query: "Jo"}. Here the passing is line 73:
<Users users={this.props.users} actions={this.props.actions}></Users>
Inside the component Users, we can call this.props.actions.filter() to create an action.
Dispatch the action created
This action is automatically dispatched by redux because we have bindActionCreators in Line 93:
// Map the action creator and dispatcher
const mapDispatchToProps = (dispatch) => {
return {
actions: Redux.bindActionCreators(userActions, dispatch),
dispatch
}
}
Reducer to handle the action
All reducers will be informed about this action, but a particular one will handle it (based on its type), Line 20:
case 'FILTER_ALL_USERS':
return allUsers.filter(user => user.name.toLowerCase().indexOf(action.query.toLowerCase()) >= 0)
Re-render
The reducer will return a brand-new object as the new store, which will be passed by Redux from the root of the component. All render functions in sub-components will be called.

Is this the correct way to delete an item using redux?

I know I'm not supposed to mutate the input and should clone the object to mutate it. I was following the convention used on a redux starter project which used:
ADD_ITEM: (state, action) => ({
...state,
items: [...state.items, action.payload.value],
lastUpdated: action.payload.date
})
for adding an item - I get the use of spread to append the item in the array.
for deleting I used:
DELETE_ITEM: (state, action) => ({
...state,
items: [...state.items.splice(0, action.payload), ...state.items.splice(1)],
lastUpdated: Date.now()
})
but this is mutating the input state object - is this forbidden even though I am returning a new object?
No. Never mutate your state.
Even though you're returning a new object, you're still polluting the old object, which you never want to do. This makes it problematic when doing comparisons between the old and the new state. For instance in shouldComponentUpdate which react-redux uses under the hood. It also makes time travel impossible (i.e. undo and redo).
Instead, use immutable methods. Always use Array#slice and never Array#splice.
I assume from your code that action.payload is the index of the item being removed. A better way would be as follows:
items: [
...state.items.slice(0, action.payload),
...state.items.slice(action.payload + 1)
],
You can use the array filter method to remove a specific element from an array without mutating the original state.
return state.filter(element => element !== action.payload);
In the context of your code, it would look something like this:
DELETE_ITEM: (state, action) => ({
...state,
items: state.items.filter(item => item !== action.payload),
lastUpdated: Date.now()
})
The ES6 Array.prototype.filter method returns a new array with the items that match the criteria. Therefore, in the context of the original question, this would be:
DELETE_ITEM: (state, action) => ({
...state,
items: state.items.filter(item => action.payload !== item),
lastUpdated: Date.now()
})
Another one variation of the immutable "DELETED" reducer for the array with objects:
const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
...state.slice(0, index),
...state.slice(index + 1)
];
return stateTemp;
Deleting an item using redux in different ways.
Method 1: In that case is used createSlice( .. )
const { id } = action.payload; // destruct id
removeCart: (state, action) =>{
let { id } = action.payload;
let arr = state.carts.filter(item => item.id !== parseInt(id))
state.carts = arr;
}
Method 2: In that case is used switch (... ), spread-operator
const { id } = action.payload; // destruct id
case actionTypes.DELETE_CART:
return {
...state,
carts: state.carts.filter((item) => item.id !== payload)
};
For both methods initialized this state:
initialState: {
carts: ProductData, // in productData mocked somedata
}
The golden rule is that we do not return a mutated state, but rather a new state. Depending on the type of your action, you might need to update your state tree in various forms when it hits the reducer.
In this scenario we are trying to remove an item from a state property.
This brings us to the concept of Redux’s immutable update (or data modification) patterns. Immutability is key because we never want to directly change a value in the state tree, but rather always make a copy and return a new value based on the old value.
Here is an example of how to delete a nested object:
// ducks/outfits (Parent)
// types
export const NAME = `#outfitsData`;
export const REMOVE_FILTER = `${NAME}/REMOVE_FILTER`;
// initialization
const initialState = {
isInitiallyLoaded: false,
outfits: ['Outfit.1', 'Outfit.2'],
filters: {
brand: [],
colour: [],
},
error: '',
};
// action creators
export function removeFilter({ field, index }) {
return {
type: REMOVE_FILTER,
field,
index,
};
}
export default function reducer(state = initialState, action = {}) {
sswitch (action.type) {
case REMOVE_FILTER:
return {
...state,
filters: {
...state.filters,
[action.field]: [...state.filters[action.field]]
.filter((x, index) => index !== action.index)
},
};
default:
return state;
}
}
To understand this better, make sure to check out this article: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da

Categories

Resources