How can I update my items in redux state? - react-native

I have state that looks following:
const initialState = {
employee: '',
companyNumber: '',
insuranceCompany: '',
workHealthcare: '',
actionGuide: '',
}
And I have screen where I want to update/edit these values.
And the updated values are shown here in this screen.
this is my action file:
const UPDATE_EMPLOYEE_DETAILS = 'UPDATE_EMPLOYEE_DETAILS'
const UPDATE_COMPANYNUMBER_DETAILS = 'UPDATE_COMPANYNUMBER_DETAILS'
const UPDATE_INSURANCECOMPANY_DETAILS = 'UPDATE_INSURANCECOMPANY_DETAILS'
const UPDATE_WORKHEALTHCARE_DETAILS = 'UPDATE_WORKHEALTHCARE_DETAILS'
const UPDATE_ACTIONGUIDE_DETAILS = 'UPDATE_ACTIONGUIDE_DETAILS'
export const employeeEditAction = (text) => ({
type: UPDATE_EMPLOYEE_DETAILS,
payload: { employee: text },
})
export const companyNumberEditAction = (text) => ({
type: UPDATE_COMPANYNUMBER_DETAILS,
payload: { companyNumber: text },
})
export const insuranceCompanyEditAction = (text) => ({
type: UPDATE_INSURANCECOMPANY_DETAILS,
payload: { insuranceCompany: text },
})
export const workHealthcareEditAction = (text) => ({
type: UPDATE_WORKHEALTHCARE_DETAILS,
payload: { workHealthcare: text },
})
export const actionGuideEditAction = (text) => ({
type: UPDATE_ACTIONGUIDE_DETAILS,
payload: { actionGuide: text },
})
and this is the reducer file.
const UPDATE_WORKPLACE_DETAILS = 'UPDATE_WORKPLACE_DETAILS'
const initialState = {
employee: '',
companyNumber: '',
insuranceCompany: '',
workHealthcare: '',
actionGuide: '',
}
const workplaceValueReducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_WORKPLACE_DETAILS:
return {
...state,
employee: action.payload.employee,
companyNumber: action.payload.companyNumber,
insuranceCompany: action.payload.insuranceCompany,
workHealthcare: action.payload.workHealthcare,
actionGuide: action.payload.actionGuide
}
default:
return state
}
}
export default workplaceValueReducer
This is the screen and the function that should save the edited values.
const saveWorkPlaceDetails = () => {
if (employee.length > 0) {
// props.editWorkplaceDetails(
// employee,
// companyNumber,
// insuranceCompany,
// workHealthcare,
// info,
// )
props.saveEmployee(employee)
props.saveCompanyNumber(companyNumber)
props.saveInsuranceCompany(insuranceCompany)
props.saveWorkHealthcare(workHealthcare)
props.saveActionGuide(info)
setEmployee('')
setCompanyNumber('')
setInsuranceCompany('')
setWorkHealthcare('')
setInfo('')
workDetailsSavedToast()
} else {
workDetailsErrorToast()
}
}
const mapDispatchToProps = (dispatch) => ({
//editWorkplaceDetails: bindActionCreators(, dispatch),
saveEmployee: (text) => dispatch(employeeEditAction(text)),
saveCompanyNumber: (text) => dispatch(companyNumberEditAction(text)),
saveInsuranceCompany: (text) => dispatch(insuranceCompanyEditAction(text)),
saveWorkHealthcare: (text) => dispatch(workHealthcareEditAction(text)),
saveActionGuide: (text) => dispatch(actionGuideEditAction(text)),
})
export default connect(null, mapDispatchToProps)(EditWorkplaceDetails)
The input fields looks as following:
<Input
style={inputLicenseEdit}
inputContainerStyle={{
borderBottomColor: colors.white,
width: '100%',
}}
placeholder='Tyƶnantaja'
placeholderTextColor={colors.white}
leftIcon={
<Ionicons name='ios-person-outline' size={30} color={colors.white} />
}
onChangeText={(text) => {
setEmployee(text)
}}
/>[![enter image description here][1]][1]
Currently when I try to update the items in the state it doesn't update and triggers the error toast instead.
What do I need to change here?

You should set as many case in your reducer switch that you have actions' consts.
Example for one action :
export const UPDATE_EMPLOYEE_DETAILS = 'UPDATE_EMPLOYEE_DETAILS'
export const employeeEditAction = (employee) => ({
type: UPDATE_EMPLOYEE_DETAILS,
employee,
})
import {UPDATE_EMPLOYEE_DETAILS, /** ... other actions */} from 'action path';
const workplaceValueReducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_EMPLOYEE_DETAILS:
return {
...state,
employee: action.employee
}
// ... other cases
default:
return state
}
}
It looks like you update all your workplace's values at the same time, so you should simply do :
export const UPDATE_WORKPLACE_DETAILS = 'UPDATE_WORKPLACE_DETAILS'
export const updateWorkPlaceDetailsAction = (details) => ({
type: UPDATE_WORKPLACE_DETAILS,
details,
})
import {UPDATE_WORKPLACE_DETAILS} from 'action path';
const workplaceValueReducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_WORKPLACE_DETAILS:
return action.details || state;
default:
return state
}
}
I personnaly set both cases, so one action for the whole object, plus one action foreach object's attributes I want to update
PS : write "js" after your first line backticks block to get code colors

"What if I want to have the cars listed under certain user like this. user: {cars: [{}]}"
(Replied in previous answered)
It depends of what you want to do.
Examples :
// state is your user
const userReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_CAR:
const nextState = {
...state,
cars: state.cars.slice() // copy array, because all children that ara objects and arrays, should be immutable too
};
return nextState;
newtState.cars.push(action.car);
/** can be done like that too, but it consume more performance */
return {
...state,
cars: [...state.cars, action.car]
}
case UPDATE_CAR:
const nextState = {
...state,
cars: state.cars.slice();
};
nextState.cars[action.index] = action.car;
/** OR even : */
nextState.cars[action.index] = {...state.cars[action.index], [action.key]: action.value}
return nextState;
case REMOVE_CAR:
const nextState = {
...state,
cars: state.cars.slice()
};
// do not use splice before copying the array and do not return directly splice
newtState.cars.splice(action.index, 1);
return nextState;
default:
return state
}
}
see documentation for slice and splice

Related

How can I add, edit, remove items as specific user from their details?

I have currently redux set up in a way where people can add their cars and other things and update them but I would need them to show only to that user that is currently logged in.
I created following user reducer that has all the information but not sure if structure is correct and what to change here to make it work as intended.
What I am intending to do here is that it would look something like this so I can filter the data according to who is logged in.
user: [
{ username: '',
insurances: [],
cars: [],
workplace: {
employer: '',
companyNumber: '',
insuranceCompany: '',
workHealthcare: '',
actionGuide: '',
} },
]
userReducer:
const ADD_USER = 'ADD_USER'
const USER_LOGGED_IN = 'USER_LOGGED_IN'
const ADD_NEW_INSURANCE = 'ADD_NEW_INSURANCE'
const DELETE_EXISTING_INSURANCE = 'DELETE_EXISTING_INSURANCE'
const ADD_NEW_CAR = 'ADD_NEW_CAR'
const DELETE_EXISTING_CAR = 'DELETE_EXISTING_CAR'
const UPDATE_WORKPLACE_DETAILS = 'UPDATE_WORKPLACE_DETAILS'
const UPDATE_EMPLOYEE_DETAILS = 'UPDATE_EMPLOYEE_DETAILS'
const UPDATE_COMPANYNUMBER_DETAILS = 'UPDATE_COMPANYNUMBER_DETAILS'
const UPDATE_INSURANCECOMPANY_DETAILS = 'UPDATE_INSURANCECOMPANY_DETAILS'
const UPDATE_WORKHEALTHCARE_DETAILS = 'UPDATE_WORKHEALTHCARE_DETAILS'
const UPDATE_ACTIONGUIDE_DETAILS = 'UPDATE_ACTIONGUIDE_DETAILS'
const initialState = {
users: [
{
username: '',
signedIn: false,
insurances: [],
cars: [],
workplace: {
employer: '',
companyNumber: '',
insuranceCompany: '',
workHealthcare: '',
actionGuide: '',
},
},
],
}
const userReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_USER:
return {
...state,
users: [...state.users, { id: state.users.length, ...action.payload }],
}
case USER_LOGGED_IN:
return {
...state,
signedIn: action.payload,
}
case ADD_NEW_INSURANCE:
return {
...state,
insurances: [
...state.insurances,
{ id: state.insurances.length, ...action.payload },
],
}
case DELETE_EXISTING_INSURANCE:
return {
insurances: [
...state.insurances.filter(
(insurance) => insurance !== action.payload
),
],
}
case ADD_NEW_CAR:
return {
...state,
cars: [...state.cars, { id: state.cars.length, ...action.payload }],
}
case DELETE_EXISTING_CAR:
return {
cars: [...state.cars.filter((car) => car !== action.payload)],
}
case UPDATE_EMPLOYEE_DETAILS:
return {
...state,
employer: action.payload.employer,
}
case UPDATE_WORKPLACE_DETAILS:
return {
...state,
employee: action.payload.employee,
}
case UPDATE_COMPANYNUMBER_DETAILS:
return {
...state,
companyNumber: action.payload.companyNumber,
}
case UPDATE_INSURANCECOMPANY_DETAILS:
return {
...state,
insuranceCompany: action.payload.insuranceCompany,
}
case UPDATE_WORKHEALTHCARE_DETAILS:
return {
...state,
workHealthcare: action.payload.workHealthcare,
}
case UPDATE_ACTIONGUIDE_DETAILS:
return {
...state,
actionGuide: action.payload.actionGuide,
}
default:
return state
}
}
export default userReducer
Here is my action files currently:
const ADD_NEW_CAR = 'ADD_NEW_CAR'
const DELETE_EXISTING_CAR = 'DELETE_EXISTING_CAR'
export const addNewCar = (text) => ({
type: ADD_NEW_CAR,
payload: text
})
export const deleteExistingCar = (car) => ({
type: DELETE_EXISTING_CAR,
payload: car
})
const ADD_NEW_INSURANCE = 'ADD_NEW_INSURANCE'
const DELETE_EXISTING_INSURANCE = 'DELETE_EXISTING_INSURANCE'
export const addInsurance = (text) => ({
type: ADD_NEW_INSURANCE,
payload: text
})
export const deleteExistingInsurance = (insurance) => ({
type: DELETE_EXISTING_INSURANCE,
payload: insurance
})
const UPDATE_EMPLOYEE_DETAILS = 'UPDATE_EMPLOYEE_DETAILS'
const UPDATE_COMPANYNUMBER_DETAILS = 'UPDATE_COMPANYNUMBER_DETAILS'
const UPDATE_INSURANCECOMPANY_DETAILS = 'UPDATE_INSURANCECOMPANY_DETAILS'
const UPDATE_WORKHEALTHCARE_DETAILS = 'UPDATE_WORKHEALTHCARE_DETAILS'
const UPDATE_ACTIONGUIDE_DETAILS = 'UPDATE_ACTIONGUIDE_DETAILS'
export const employeeEditAction = (text) => ({
type: UPDATE_EMPLOYEE_DETAILS,
payload: { employee: text },
})
export const companyNumberEditAction = (text) => ({
type: UPDATE_COMPANYNUMBER_DETAILS,
payload: { companyNumber: text },
})
export const insuranceCompanyEditAction = (text) => ({
type: UPDATE_INSURANCECOMPANY_DETAILS,
payload: { insuranceCompany: text },
})
export const workHealthcareEditAction = (text) => ({
type: UPDATE_WORKHEALTHCARE_DETAILS,
payload: { workHealthcare: text },
})
export const actionGuideEditAction = (text) => ({
type: UPDATE_ACTIONGUIDE_DETAILS,
payload: { actionGuide: text },
})
const USER_LOGGED_IN = 'USER_LOGGED_IN'
export const authAction = (trueFalse) => ({
type: USER_LOGGED_IN,
payload: trueFalse,
});
and this is my auth reducer:
const USER_LOGGED_IN = 'USER_LOGGED_IN'
const initialState = {
signedIn: false,
}
const authsReducer = (state = initialState, action) => {
switch (action.type) {
case USER_LOGGED_IN:
return {
...state,
signedIn: action.payload,
}
default:
return state
}
}
export default authsReducer

Testing AsyncStorage with Redux Thunk

When I try to test if a Cat is added correctly to the Async Storage I am receiving null. How can I solve this? In the app is working correctly. I am using React Native, React-Native-Async-Storage, Redux Thunk and Redux Persist.
REDUCER
export const INITIAL_STATE: CatsState = {
cats: [],
selectedCat: null
}
const catsReducer = (state = INITIAL_STATE, action: CatsAction): CatsState => {
switch (action.type) {
case CatsActionTypes.SET_CATS:
return { ...state, cats: action.payload }
case CatsActionTypes.SELECT_CAT:
return { ...state, selectedCat: action.payload }
default:
return state
}
}
ACTION
export const addCat = ({ cat }: AddCatData): ThunkAction<void, RootState, null, CatsAction> => {
return async (dispatch: Dispatch<CatsAction>) => {
try {
const response = await AsyncStorage.getItem(STORAGE_KEYS.cats)
const cats: Cat[] = response ? JSON.parse(response) : []
cats.push(cat)
await AsyncStorage.setItem(STORAGE_KEYS.cats, JSON.stringify(cats))
dispatch({ type: CatsActionTypes.SET_CATS, payload: cats })
} catch (error) {
console.log(error)
}
}
}
TEST
beforeEach(async () => await AsyncStorage.clear())
describe('add cat', () => {
it('persist cat correctly into local storage', async () => {
const cat: Cat = {
id: '1',
name: 'Cat',
breed: 'Breed',
age: 1,
gender: 'male',
weight: 5,
size: 'Big',
color: 'Brown',
mood: 'Angry',
description: 'Cat description',
friendly: 3,
liked: true
}
addCat({ cat })
const response = await AsyncStorage.getItem(STORAGE_KEYS.cats)
const cats: Cat[] = JSON.parse(response)
const expectedArray = { cats: [cat] }
expect(cats).toStrictEqual(expectedArray)
})
})
Thank you
Use official documentation react-native-async-storage package: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
UPDATED:
For you case you need do several steps:
#1 Mock your data
const CAT_MOCK = {
id: '1',
name: 'Cat',
}
#2 Mock Storage
const STORAGE_MOCK = {
[STORAGE_KEYS.cats]: [CAT_MOCK]
}
#3 Mock library for your case
jest.mock('#react-native-async-storage', () => ({
getItem: jest.fn((item) => {
return new Promise((resolve, reject) => {
resolve(STORAGE_MOCK[item]);
});
}),
}));

Array in a Redux State, React

I'm stuck and I can't find out how to store a dictionary in Redux State.
const productinuse = (state = [], action) => {
switch (action.type) {
case 'productinuse':
return [...state, action.payload];
default:
return state;
}
};
export default productinuse;
animModal(product) {
console.log(product);
() =>
store.dispatch({
type: 'productinuse',
payload: product,
}),
store.dispatch({type: 'toggleproductmodal'});
}
Example of the dictionary that I'm trying to store on it :
{"UNIT": "Kg", "images": ["https://example.com/image.jpg"], "module_id": "1", "product_id": "6", "product_img": "example.com/image2.js", "product_name": "Ananas", "product_prix": "8.5", "product_text": "1 Kg Ananas."}
I always get the default empty array as a value.
EDIT
const initialState = {
productinuse: [],
};
const productinuse = (state = initialState, action) => {
console.log(action.payload + 'DEBUGGINS');
switch (action.type) {
case 'productinuse':
return Object.assign({}, state, {
productinuse: [...state, action.payload],
});
default:
return state;
}
};
export default productinuse;
**EDIT 2 **
<TouchableOpacity
key={product.product_id}
style={{width: '50%', position: 'relative'}}
onPress={() =>
this.setState(
{
product: product,
},
(product) =>
store.dispatch({
type: 'productinuse',
payload: product,
}),
() => store.dispatch({type: 'toggleproductmodal'})}>
Now When I perform the action I'm getting a TypeError: Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a Symbol.iterator method.
try this:
state:
const initialState = {
productinuse: []
}
reducer:
const productinuse = (state = initialState, action) => {
switch (action.type) {
case 'productinuse':
return Object.assign({}, state, {
productinuse: [...state.productinuse, action.payload],
});
default:
return state;
}
};
export default productinuse;

React native mapDispatchToProps not working

I can't get my mapDispatchToProps to work properly.
I export a combineReducers:
export default combineReducers({
auth: AuthReducer,
tenants: TenantsReducer
});
The tenants reducer:
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TENANTS_DATA:
return { ...state, error: false, data: action.payload };
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
default:
return state;
}
};
Then I have getTenantByID method in my action
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '',
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
Finally, I tried to use it in my component.
import { connect } from 'react-redux';
import { getTenantByID } from '../actions';
...
componentDidMount() {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props);
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
return {
error: tenants.error,
tenantData: tenants.tenantData
};
};
const mapDispatchToProps = () => {
return {
getTenantByID
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TenantDetails);
In my componentDidMount, the console.log(this.props) is returning a empty object for tenantData. What am I doing wrong?
Initial state is showing as the component already mounted, which is empty object {}
this.props.getTenantByID(tenantId);
this action triggers actually, but the data is not available in componentDidMount lifecycle.
try putting log in render like this
componentDidMount(){
this.props.getTenantByID(2);
}
render() {
console.log(this.props.tenantData); // 1st render => {}, 2nd render=> desired data
return (
<div/>
);
}
use componentDidUpdate to check if value is changed,
componentDidUpdate(prevProps){
if(prevProps.tenantData !== this.props.tenantData){ console.log(prevProps.tenantData, this.props.tenantData) }
}
remember to receive the dispatch parameter in your mapDispatchToProps method
const mapDispatchToProps = (dispatch) => {
return {
getTenantByID: (tenantID ) => {
dispatch(getTenantByID({tenantID }));
};
};
};
call for
this.props.getTenantByID({ tenantID: 10 })

Reducers with same action affect multiple screens

I have two reducers which share some actions. Issue is when i dispatch action on one screen it triggers action on the other screen. Boths screen are in a TabNavigator therefore I can easily see that if I change a field on 1 screen the same field is changed on the other screen as well.
Reducer 1
import * as Actions from '../actions/Types';
const initialState = {
email: '',
password: ''
};
const signInReducer = (state = initialState, action) => {
switch(action.type) {
case Actions.CHANGE_EMAIL_INPUT:
return Object.assign({}, state,
{ email: action.email }
);
case Actions.CHANGE_PASSWORD_INPUT:
return Object.assign({}, state,
{ password: action.password }
);
default:
return state;
}
}
export default signInReducer;
Reducer 2
import * as Actions from '../actions/Types';
const initialState = {
firstName: '',
lastName: '',
email: '',
password: '',
repeatPassword: ''
};
const signUpReducer = (state = initialState, action) => {
switch(action.type) {
case Actions.CHANGE_FIRST_NAME_INPUT:
return Object.assign({}, state,
{ firstName: action.firstName }
);
case Actions.CHANGE_LAST_NAME_INPUT:
return Object.assign({}, state,
{ lastName: action.lastName }
);
case Actions.CHANGE_EMAIL_INPUT:
return Object.assign({}, state,
{ email: action.email }
);
case Actions.CHANGE_PASSWORD_INPUT:
return Object.assign({}, state,
{ password: action.password }
);
case Actions.CHANGE_REPEAT_PASSWORD_INPUT:
return Object.assign({}, state,
{ repeatPassword: action.password }
);
default:
return state;
}
}
export default signUpReducer;
Store
import { createStore, combineReducers } from 'redux';
import signInReducer from '../reducers/SignIn';
import signUpReducer from '../reducers/SignUp';
import profileReducer from '../reducers/Profile';
const rootReducer = combineReducers({
signIn: signInReducer,
signUp: signUpReducer,
profile: profileReducer
});
const configureStore = () => {
return createStore(rootReducer);
}
export default configureStore;
As you can see there are some common actions like CHANGE_EMAIL_INPUT & CHANGE_PASSWORD_INPUT and I dont want them to be triggered together. One way I can figure out is to change the name of actions and make then more specific to screen but this doesn't sound good. Another could be to wrap reducers so that we know what is being called but not getting an idea on the wrapper.
Any suggestions.
You should not reuse the same action name between 2 reducers, to avoid unintended effects, use different names.
For example
Actions.SIGNUP_ CHANGE_EMAIL_INPUT
and
Actions.SIGNIN_ CHANGE_EMAIL_INPUT
Otherwise, you can merge your 2 reducers, adding a state to know from which screen this change emerged.
well, i solved it by creating a wrapper for the reducers.
Store
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const isInitializationCall = state === undefined;
const shouldRunWrappedReducer = reducerName(action) || isInitializationCall;
return shouldRunWrappedReducer ? reducerFunction(state, action) : state;
}
}
const rootReducer = combineReducers({
// signIn: signInReducer,
// signUp: signUpReducer,
// profile: profileReducer
signIn: createNamedWrapperReducer(signInReducer, action => action.name === 'signIn'),
signUp: createNamedWrapperReducer(signUpReducer, action => action.name === 'signUp'),
profile: createNamedWrapperReducer(profileReducer, action => action.name === 'profile'),
});
Screen
onChangeEmail: (email) => { dispatch({name: 'signIn', type: Actions.CHANGE_EMAIL_INPUT, email: email}) },