Delayed prop update when using React Hooks and Redux - javascript

I have a React class component where the view depends on a global state called props.filtered_table_data stored in Redux Store.
I use useReducer to apply filters to the state
const init_filter = { OWNER: '', TEAM: '' }
const [filter, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'OWNER':
return { ...state, OWNER: action.option }
case 'TEAM':
return { ...state, TEAM: action.option }
case 'RESET':
return init_filter
default:
return state
}
}, init_filter)
These filters get applied when I select through a dropdown.
<Select
className="select-filter"
type="owner"
onItemSelect={(optionItem) => { handleFilter('OWNER', optionItem); ; filterData()}}
>
</Select>
And filterData() is the function that handles the filtering of the data. It creates a new array, filters the data based on the filter and updates it at the end by doing
props.filterTable(newArray)
filterTable is just a redux action that updates the props.filtered_table_data.
The filtering works well, but there is a delay in the update.
When I select a new filter, the previous filter gets applied to the data.
How can I fix this issue?

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.

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.

Dynamic combine of reducers

I have a dashboard with widgets and they depend on the current user. So we don't know which all widgets are going to load at compile time. That said , the state will also have dynamic fields for these widgets.I have a reducer for each widget. How do i combine all the reducers of required widgets ?
My state may look like :
{ dashboardId: 12,
widgetList:{
w1: {
widgetTitle:'widget 1'
data:[]
},
w2:{
widgetTitle:'widget 2'
data:[]
}
}
}
Now i want to combine Reducers:
combineReducers({
w1:widget1Reducer,
w2:widget2Reducer
})
Now, the issue is we dont know which all widgets are going to load for the current dashboard.
You don't need to combine multiple reducers using the helper. Create a widgetList reducer that will update to specified widget using the appropriate widget reducer:
function widgetList(state = {}, action) {
switch (action.type) {
case WIDGET_A_FOO:
case WIDGET_A_BAR:
return {
...state,
[action.id]: widgetA(state[action.id], action),
}
case WIDGET_B_BAZ:
return {
...state,
[action.id]: widgetB(state[action.id], action),
}
case DELETE_WIDGET:
return _.omit(state, action.id)
default:
return state
}
}
In the above example, action.id holds the widget ID. widgetA and widgetB are the reducers of widget A and widget B.

redux - reducer state is blank

I am trying to replicate something similar to the TodoList example in the the redux docs' basic example. The second reducer receives an array - styleItems = [{... ... }, {... ...}] - and then calls the first function to act on each of the individual objects.
I provide an initialState to the app container via the following, as shown in containers/app.js. However, the state passed to the styleItems reducer seems to be a blank array - each and every time.
However, react renders the UI based on the initial config, and react dev-tools shows the state structure as expected. Is the redux store somehow seeing the same thing as react?
containers/app.js
function starterInfo(state) {
return {
// The ID of this particular object
id: 12345,
// Various keys and theri css values
styleItems: [
{
pk: 31,
order: 1,
label: 'Caption text color',
css_identifier: '.caption-text',
css_attribute: 'color',
css_value: '#FFFFFF'
},
{
pk:23,
order: 2,
label: 'Caption link color',
css_identifier: '.caption-link',
css_attribute: 'color',
css_value: '#FEFEFE'
}
],
// Network state info
currently_fetching: false,
currently_posting: false
}
}
export default connect(starterInfo)(App)
reducers/index.js
// This handles a single styleItem object within the array
function change_css(state = {}, action){
switch (action.type){
case actions.CHANGE_CSS:
if (state.order !== action.order){
return state
}
return {
...state,
css_value
}
default:
return state
}
}
// This handles the styles array in the global state
function styleItems(state = [], action){
switch(action.type){
case actions.CHANGE_CSS:
const foobar = state.map(styleItem =>
change_css(styleItem, action)
)
return foobar
default:
return state
}
}
The short answer is that you're not passing the initial state quite right. The first argument to the connect function for the React Redux bindings is mapStateToProps. The point of this function is to take the state that already exists in your app and map it to props for your component. What you're doing in your starterInfo function is kind of just hard-coding what the state is for your component. Because you're returning a plain object React doesn't really know the difference so it works just fine, but Redux doesn't yet know about your app state.
Instead, what you should do is provide your initial state directly to the reducers, like this:
const intialStyleItemsState = [
{
pk: 31,
order: 1,
label: 'Caption text color',
css_identifier: '.caption-text',
css_attribute: 'color',
css_value: '#FFFFFF'
},
{
pk:23,
order: 2,
label: 'Caption link color',
css_identifier: '.caption-link',
css_attribute: 'color',
css_value: '#FEFEFE'
}
];
function styleItems(state = intialStyleItemsState, action){ ...
And eventually, because you're splitting your reducers up you'll need to combine them back together again with Redux's combineReducers utility, provide that root reducer to your store and go from there.
You can also pass the initial state using the redux function createstore that take as argument createStore(reducer, [initialState]) http://rackt.org/redux/docs/api/createStore.html
Let’s say you have two reducers
change_css(state = {}, action)
function styleItems(state = [], action)
If you use comibneReducer to initialize your state
var reducer = combineReducers({
css: change_css,
items: styleItems
})
Now
var store = createStore(reducer)
console.log(store.getState())
Your store will contain { css: {}, items: [] }
Now if you want to initialize the state you can pass the initial state as the second argument of the createStore function.
createStore(reducer, {css:{some properties},items:[{name:"obj1"},{name:"obj2"},{name:"obj3"}]})
Now you store will contain the initial state. {css:{some properties,items:[{name:"obj1"},{name:"obj2"},{name:"obj3"}]}
You can feed this state from server for example and set it as initial state of your application

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