I have multiple values stored in my redux initial state, and I have one action handler StepOneFn that is taking in all the values my component has given it to change. I want to update the initial state to change only some of the values. I'm not sure how to go about doing it.
let initial_state = {
house_name:"",
address:"",
city:"",
state:"",
zip:0,
img:"",
mortgage:0,
rent:0
}
const step_one = "step_one"
export default function reducer(state = initial_state,action){
switch(action.type){
default:
return state;
case step_one:
return {...state,...action.payload}
}
}
export function StepOneFn(name,address,city,state,zip){
return{
type:step_one,
payload:{
name,
address,
city,
state,
zip
}
}
}
If you want to change only some values you can do like this
case step_one:
return {...state, address:action.payload.address, city:action.payload.city}
}
Now it only changes city and address as per payload others are unchanged.
Related
I just started with redux and react-redux. I am observing a very weird behavior and not able to wrap my head around it.
I am trying something like this.
const fetchedFolders = useSelector(state=>{
console.log("redux state = ",state);
return state.fetchedFolders;
});
const updateFetchedFolders = useDispatch();
I have callback function that receives a new set of values and will update the state in store.
let appendFoldersToList=(newFolders)=>{
console.log(typeof(newFolders))
if(typeof(newFolders) === undefined)
console.log("go to error");
else{
updateFetchedFolders(setFetchedFolders([...fetchedFolders,...newFolders]));
}
}
this works perfectly and re-renders the list with new value
but if I replace the line
updateFetchedFolders(setFetchedFolders([...fetchedFolders,...newFolders]));
with
updateFetchedFolders(setFetchedFolders([...newFolders]));
it does not re-render and it still shows the old list. but in console, I can see data is updated.
I am not able to understand why it re-renders in first case and not in second case.
This is how my reducers look:-
export const reducer = (state=initialState, action)=>{
switch(action.type){
case 'SET_FOLDERS': return {
...state,
fetchedFolders:[...action.payload]
}
}
}
this is my action creator:-
export const setFetchedFolders = (payload)=>{
return {
type:'SET_FOLDERS',
payload:payload
}
}
this is my initial state:-
const initialState = {
fetchedFolders:[],
}
I don't think I am not mutating the state.
my array looks something like this::-
[
{name:cats, id:SOME_ID},
{name:dogs, id:SOME_ID}
]
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.
I have setup redux with a reducer that updates an array of time slots for a given date. Every time I change the date, the reducer updates the state (I have console logged the state change which happens as expected in my mapStateToProps function), however, the redux connect function doesn't detect that the array of time slots has been updated. Here is my reducer:
import { SHOW_ERROR, HIDE_ERROR, SHOW_SUCCESS, HIDE_SUCCESS, UPDATE_TIME_SLOTS } from '../actions/types';
const INITIAL_STATE = { error: null, success: null, timeSlots: null };
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_ERROR:
return { ...state, error: action.payload };
case HIDE_ERROR:
return { ...state, error: action.payload };
case SHOW_SUCCESS:
return { ...state, success: action.payload };
case HIDE_SUCCESS:
return { ...state, success: action.payload };
case UPDATE_TIME_SLOTS:
return { ...state, timeSlots: action.payload };
default:
break;
}
return state;
}
The time slots is always an array like so:
["08:30", "09:15", "10:00", "10:45", "11:30", "12:15", "13:00", "13:45", "14:30", "15:15", "16:00", "16:45"]
Here is my mapStateToProps function which then uses redux connect to send these props to my BookingForm (this is where my console.log detects the change in state correctly:
function mapStateToProps(state) {
console.log(state.public.timeSlots);
return {
timeSlots: state.public.timeSlots
};
}
export default connect(mapStateToProps, { create, getAvailableSlots })(form(BookingForm));
Every time I click on a date in my form the state is updated with the new array, however, my connect function doesn't detect this update and therefore the BookingForm component doesn't re-render. I am assuming this is to do with the array not immutably being updated, however, I haven't found a solution to designing it so that it is updated immutably.
Any help?
You are probably sending the same array instance, and the connect function is checking for reference changes only! btw: same for objects.
Using the same array will not get detected by the connect, you have to create a new array every time you want to update the state.
How about you let the reducer deal with that?
So, once the user clicks a date on your form, send an action to record that, say "DATE_CLICKED" action, and in the reducer, add the date to a new array along with all the previous values.
Like this:
return [...state.dates, action.date]
I can't seem to reset the default state; how do I do that? I tried this but all it does is add state to state and calls it undefined.
const initialState = {
name: null,
coins: 0,
image: null,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case types.ADD_GROUP_COINS:
return {
...state,
coins: state.coins + action.coins
};
case types.DELETE_GROUP:
return {
state: undefined
};
default:
return state;
}
}
To reset the state to the initialState, simply return initialState, like this:
case types.DELETE_GROUP:
return initialState;
Remember that in the reducer, the object that you return is the new state. You don't need to wrap it in another object, like this:
return {
state: ...
}
That will give you a property called state in your state, which isn't at all what you need.
From the documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
It's easy to get confused about this, so be careful! Take a look at your default case if you're still not quite sure: you're returning the old state variable as the new state, not { state: state } which you are essentially doing with the current DELETE_GROUP handler.
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.