Redux combineReducers, with child properties at same level as child reducers - javascript

I would like to have this shape of data:
let shop = Map({
shopInput: '',
shopShouldHideResults: true,
place: null,
position: {},
shopAutocompleteResults: {
predictions: [{ description: '' }]
}
})
let product = Map({
brand: '',
name: '',
description: '',
image: null,
price: null,
id: null,
item: 'MEAL'
})
export const addPage: Map<*, *> = Map({
shop: shop,
product: product,
shopListDisplayed: false,
rerenderKey: false
})
So addPage has a product reducer and a shop reducer and its own reducer just for its own two properties "shopListDisplayed" and "rerenderKey"
My reducers:
export const addPage = (
state: Map<*, *> = applicationState.get('addPage'),
action: AddPageAction
): Map<*, *> => {
switch (action.type) {
case UPDATE_SHOP_LIST_DISPLAYED: {
return state.set('shopListDisplayed', (action.payload: boolean))
}
case UPDATE_RERENDER_KEY: {
return state.set('rerenderKey', (!state.get('rerenderKey'): boolean))
}
default:
return state
}
}
export const product = (
state: Map<*, *> = applicationState
.get('addPage').get('product'),
action: AddPageAction
): Map<*, *> => {
switch (action.type) {
case UPDATE_BRAND: {
return state.set('brand', (action.payload: string))
}
case UPDATE_NAME: {
return state.set('name', (action.payload: string))
}
case UPDATE_ITEM_TYPE: {
return state.set('item', (action.payload: string))
}
case UPDATE_DESCRIPTION: {
return state.set('description', (action.payload: string))
}
case UPDATE_PRODUCT_ID: {
return state.set('id', (action.payload: number))
}
case UPDATE_PRICE: {
return state.set('price', (action.payload: number))
}
case UPDATE_IMAGE: {
return state.set('image', (action.payload: Image | any))
}
case UPDATE_IMAGE_LINK: {
return state.set('imageLink', (action.payload: string))
}
default:
return state
}
}
export const shop = (
state: Map<*, *> = applicationState.get('addPage').get('shop'),
action: Object
): any => {
switch (action.type) {
case UPDATE_SHOP_INPUT: {
return state.set('shopInput', action.payload.shopInput)
}
case UPDATE_SHOP_PLACE: {
return state.set('place', action.payload.place)
}
case UPDATE_SHOP_SHOULD_HIDE_RESULTS: {
return state.set('shopShouldHideResults', action.payload)
}
case GET_SHOP_AUTOCOMPLETE_RESULTS_REJECTED: {
console.log(
'there was an issue getting your autocomplete results: ',
action.payload
)
return state
}
case GET_SHOP_AUTOCOMPLETE_RESULTS_FULFILLED: {
return state.set('shopAutocompleteResults', action.payload)
}
case GET_SHOP_PLACE_DETAILS_FULFILLED: {
return state.set('place', action.payload.result)
}
case GET_SHOP_PLACE_DETAILS_REJECTED: {
console.log('there was an issue getting place details: ', action.payload)
return state
}
default:
return state
}
}
How do I use combineReducers in this case?
Currently I have:
const rootReducer = combineReducers({
addPage: combineReducers({shop, product}),
alertModal,
categories,
distanceSlider,
editProduct,
map,
menu,
searchPage: combineReducers({location}),
searchPageFields: searchPageFields,
searchResults,
searchResultsPresenter
I don't think my combineReducers takes into account shopListDisplayed: false,rerenderKey: false. I have not explicitely added in addPage reducer into combineReducers which deals with those two root properties of the addPage.
Where do I put that into combineReducers?
I get this error:
Unexpected properties "shopListDisplayed", "rerenderKey" found in the
previous state received by the reducer. Expected to find one of the
known reducer property names instead: "shop", "product". Unexpected
properties will be ignored.

Related

prevstate and nextstate are the same in redux

I tried to create todo list app with react and redux but when I use "CheckTodoItem" action and log with logger, my prevstate and nextstate are same.
TodoList Action :
const AddTodoItem = ( todo ) =>{
return{
type: "ADD_TODO_ITEM",
payload: todo
}
}
const CheckTodoItem = ( todo ) =>{
return{
type: "CHECK_TODO_ITEM",
payload: todo
}
}
const DeleteTodoItem = ( todo ) =>{
return{
type: "DELETE_TODO_ITEM",
payload: todo
}
}
export { AddTodoItem, CheckTodoItem, DeleteTodoItem }
TodoLIst Reducer:
const initialState = {
todoList: [
{ todo: "workout" , isCheck: false},
{ todo: "home work" , isCheck: false},
{ todo: "pay bils" , isCheck: false},
{ todo: "visit ali" , isCheck: false},
{ todo: "Buying household items" , isCheck: false},
],
}
const todoListReducer = ( state = initialState , action ) => {
switch ( action.type ) {
case "ADD_TODO_ITEM":
const Todo = {
todo: action.payload,
isCheck: false,
};
state.todoList.push( Todo );
return{
...state
}
case "DELETE_TODO_ITEM":
const newTodoList = state.todoList.filter( item => item.todo !== action.payload)
return {
...state,
todoList: newTodoList,
}
case "CHECK_TODO_ITEM":
const objIndex = state.todoList.findIndex(( obj => obj.todo === action.payload ));
state.todoList[objIndex].isCheck = true
return{
...state,
}
default :
return state
}
}
export default todoListReducer
logger :
enter image description here
I've tried everything I can but I don't know why it's not working properly
I edit your reducer function with map :
case "CHECK_TODO_ITEM":
const todoList = state.todoList.map((todoItem)=>{
if(todoItem.todo===action.payload){
return {
...todoItem,
isCheck:true,
}else{
return todoItem
}
}
});
return{
...state,
todoList
}

ReactJS properly updating state with redux

I started working with reactjs recently and I need to know how to properly update my state.
My actions.js:
export function updateCreateCampaignObject(data) {
return {
type: actions.UPDATE_CREATE_CAMPAIGN,
payload: data,
}
}
export function updateCampaignProducts(data) {
return {
type: actions.UPDATE_CAMPAIGN_PRODUCTS,
payload: data,
}
}
export function updateCampaignTarget(data) {
return {
type: actions.UPDATE_CAMPAIGN_TARGET,
payload: data,
}
My reducer:
const INITIAL_STATE = {
campaign_dates: {
dt_start: '',
dt_end: '',
},
campaign_target: {
target_number: '',
gender: '',
age_level: {
age_start: '',
age_end: '',
},
interest_area: [],
geolocation: {},
},
campaign_products: {
survey: {
name: '',
id_product: '',
quantity: '',
price: '',
}
}
}
export default function createCampaignReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case UPDATE_CREATE_CAMPAIGN:
return { ...state, state: action.payload }
case UPDATE_CAMPAIGN_PRODUCTS:
return { ...state, campaign_products: action.payload }
case UPDATE_CAMPAIGN_TARGET:
return { ...state, campaign_products: action.payload }
default:
return state
}
In this case, I only want to add +1 to quantity in my campaign_products object.
Do I need to create an action just for this?
How can I call this action in my component, something like this?
import { updateCampaignProducts as updateCampaignProductsAction }
from '~/store/modules/createCampaign/actions'
function addQuantity() {
dispatch(updateCampaignProductsAction({
survey: {
quantity: quantity + 1
}
}))
}
Have not tested, But you need to change something like this. (3 steps)
1) Change quantity to number
campaign_products: {
survey: {
name: '',
id_product: '',
quantity: 0,
price: '',
}
}
2) In Reducer, based on action update the state from current state. (in your case not depend on any action payload)
export default function createCampaignReducer(state = INITIAL_STATE, action) { switch (action.type) {
case UPDATE_CAMPAIGN_PRODUCTS:
const current_quantity = state.campaign_products.survey.quantity;
return { ...state, campaign_products: current_quantity + 1}
default:
return state }
3) dispatch action
function addQuantity() {
dispatch(updateCampaignProductsAction())
}
you can do it with or without hooks.
both ways, read the docs about connecting redux to react.
react team wrote npm library to connent them.

Redux state is not getting initialized to initial state

I am new to the react-redux. Here, I have a reducer which is like,
const initialState = {
Low: [
{
id: 0,
technologyId: 0,
technology: '',
type: '',
count: '',
allowded: 6,
level: 'EASY'
}
],
Medium: [
{
id: 0,
technologyId: 0,
technology: '',
type: '',
count: '',
allowded: 7,
level: 'MEDIUM'
}
],
High: [
{
id: 0,
technologyId: 0,
technology: '',
type: '',
count: '',
allowded: 7,
level: 'TOUGH'
}
]
}
export default function QuizData(state = initialState, action) {
switch (action.type) {
case QUIZ_DATA:
return {
...state,
[action.data.type]: [...action.data.tobeData],
error: false,
}
case ADD_NEW:
return {
...state,
[action.data.addtype]: action.data.addData,
error: false,
}
case REMOVE_TECH:
return {
...state,
[action.data.removeType]: action.data.newArr,
error: false,
}
case RESET_QUIZ:
return {
...initialState,
error: false,
}
}
Now, Here on click of button I am calling an action that will reset the data to initial state.
this.props.resetQuiz();
which is
export function resetQuiz() {
return {
type: RESET_QUIZ
}
}
where I use it
let newData = { ...this.props.data }; while using it in the component to do some operation.
Now here what happens is after doing some actions the initial state data gets changes with some new values,..
But,on click of the button I want to set it like, initialState.
So, when I tried that time, that initialState is also getting the same values. So, It is not resetting.
I am using it in component like,
data: state.QuizData // In statetoprops.
let criterias = [{
type: 'Low',
noc: 6,
id: 1,
data: this.props.data["Low"]
}, {
type: 'Medium',
noc: 7,
id: 2,
data: this.props.data["Medium"]
},
{
type: 'High',
noc: 7,
id: 3,
data: this.props.data["High"]
}]
While using the action in component like,
createQuestionBankQuiz = () => {
this.props.resetQuiz();
history.push({
pathname: "/quiz-setup/" + `${this.props.jdId}`
});
};
export default connect(mapStateToProps, { fetchListOfQuiz, updateQuestionViewMode, enableJob, resetQuiz })(LandingScreen);
The way I update is
onChange(event, tobeupdated, id, type, noc, data) {
let newData = { ...this.props.data };
let errorState;
let isDuplicate;
let addedRow;
if (newData) {
let data = newData[type].map((object, index) => {
if (object.id === id) {
object[tobeupdated] = event.target.value;
const tobeData = newData[type];
this.props.updateLowLevel({ tobeData, type }).then(() => {
let criteria_filled = this.disableAddbutton({ ...this.props.data }, type);
addedRow = `new${type}RowAdded`;
this.setState({
[addedRow]: criteria_filled ? true : false
})
const tobechecked = newData[type].filter(item => item.id === id);
isDuplicate = this.checkPreviousSelected(newData, type, tobechecked[0].technology, tobechecked[0].type);
if (isDuplicate) {
toastr.error("Duplicate criteria. Please change it.");
object["technology"] = '';
object["type"] = '';
const tobeData = newData[type];
this.props.updateLowLevel({ tobeData, type });
}
});
}
});
errorState = `show${type}Error`;
if (tobeupdated !== "count") {
this.getSelectionQuestionsNumber(type, id, noc);
}
let validateData = this.validate(type, noc);
this.setState({
[errorState]: validateData
})
}
}
What is it that I am doing wrong ?
I think you wrong with action dispatch
export function resetQuiz() {
return dispatch => {
dispatch({
type: RESET_QUIZ
})
}
}
I think better if you try with this in reducer
export default function QuizData(state = initialState, action) {
switch (action.type) {
case RESET_QUIZ:
return Object.assign({}, initialState, {error: false})
default:
return state;
}
}
you just need to return the initial value in reducer, like this for example:
export default function QuizData(state = initialState, action) {
switch (action.type) {
case RESET_QUIZ:
return initialState
default:
return state;
}
}
You forgot to return the state in your reducer for the default action :
export default function QuizData(state = initialState, action) {
switch (action.type) {
case QUIZ_DATA:
return {
...state,
[action.data.type]: [...action.data.tobeData],
error: false,
}
case ADD_NEW:
return {
...state,
[action.data.addtype]: action.data.addData,
error: false,
}
case REMOVE_TECH:
return {
...state,
[action.data.removeType]: action.data.newArr,
error: false,
}
case RESET_QUIZ:
return {
...initialState,
error: false,
}
return state; // you forgot this
}
redux issues some kind of init action when the store is initialized, so you need to return the given state in your reducers for others actions than the ones you defined yourself.
May be you are not doing deep copy in other actions.
Can you add the code of reducer for other actions?
Edit:
Quick solution for your problems is to change your reducer like below:
export default function QuizData(state = JSON.parse(JSON.stringify(initialState)), action) {
....
}
Note: JSON.parse(JSON.stringify(initialState)) works for your case but not for all the cases, and not a good solution. You have to write deep copy logic for that.
Correct/Proper solution:
You have to modify your component logic to not modify store data directly and update it through other actions.

Edit a property in an array of objects in React based on an ID

I have an array of objects created in the new "Context API" like this ...
const reducer = (state, action) => {
switch (action.type) {
case "DELETE_CONTACT":
return {
...state,
contacts: state.contacts.filter(contact => {
return contact.id !== action.payload;
})
};
default:
return state;
}
};
export class Provider extends Component {
state = {
contacts: [
{
id: 1,
name: "John Doe",
email: "jhon.doe#site.com",
phone: "01027007024",
show: false
},
{
id: 2,
name: "Adam Smith",
email: "adam.smith#site.com",
phone: "01027007024",
show: false
},
{
id: 3,
name: "Mohammed Salah",
email: "mohammed.salah#site.com",
phone: "01027007024",
show: false
}
],
dispatch: action => {
this.setState(state => reducer(state, action));
}
};
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
I want to create an action in the "reducer" that allows me to edit each contact's "show" property based on its id that I will pass to the action as a payload, how can I do that?
To avoid array mutation and retain the element position while editing contact you can do this:
case "EDIT_CONTACT":
const { id, show } = action.payload;
const contact = { ...state.contacts.find(c => c.id === id), show };
return {
...state,
contacts: state.contacts.map(c => {return (c.id !== id) ? c : contact;})
};
You can find the contact, avoid mutation by using spread, set new value of show :
case "EDIT_CONTACT":
const { id, show } = action.payload; // Assume id and show are in action.payload
const contact = { ...state.contacts.find(c => c.id === id), show };
return {
...state,
contacts: [...state.contacts.filter(c => c.id !== id), contact]
};
If order matters:
const { id, show } = action.payload;
const contact = { ...state.contacts.find(c => c.id === id), show };
const index = state.contacts.findIndex(c => c.id === id);
return {
...state,
contacts = [ ...state.contacts.slice(0, index), contact, ...state.contacts.slice(index + 1)];
}

Adding multiple objects to the store in redux

I've written an API call that returns all of a user's elements in an array.
I've written part of a reducer (it doesn't fully work yet, hence the question marks below) that looks like this:
export default function(state = defaultState, action) {
switch (action.type) {
case 'receiveElement':
return {
...state,
elementsMap: {
...state.elementsMap,
[action.element.id]: action.element,
},
visibleElements: [...state.visibleElements, action.element.id],
};
case 'receiveAllElements':
return {
...state,
elementsMap: {
...state.elementsMap,
**???**
},
visibleElements: [...state.visibleElements, ...action.elements.map((element, index) =>
`id-${element.id}`
)],
};
default:
return state;
}
}
defaultState and elementsMap looks like the following:
const defaultState = {
elementsMap: {
'id-1': {id: 'id-1', shape: 'circle', title: 'Run for City Council'},
'id-2': {id: 'id-2', shape: 'circle', title: 'NYU Law School'},
'id-3': {id: 'id-3', shape: 'circle', title: 'Start Company'},
},
visibleElements: ['id-1', 'id-2', 'id-3'],
};
I'm struggling to add the additional N elements coming back from the API call to the elementsMap and would love some help here, I think I've gotten the adding to visibleElements piece down.
Thanks for taking a look
Try this:
case 'receiveAllElements':
var map = elements.reduce((res, i) => { res[i.id] = i; return res; }, {});
return {
...state,
elementsMap: {
...state.elementsMap,
...map
},
visibleElements: [...state.visibleElements, ...action.elements.map((element, index) =>
`id-${element.id}`
)],
};
export default function(state = defaultState, action) {
switch (action.type) {
case 'receiveElement':
return {
...state,
elementsMap: {
...state.elementsMap,
[action.element.id]: action.element,
},
visibleElements: [...state.visibleElements, action.element.id],
};
case 'receiveAllElements':
let _visibleElements = [...state.visibleElements, ...action.elements.map((element, index) => `id-${element.id}`)]
return {
...state,
elementsMap: {
...state.elementsMap,
**???**
},
visibleElements: _visibleElements,
};
default:
return state;
}
}
I wouldn't also do any logic on the output object, I would do it as I showed above. Otherwise, code looks messy. But that's just my opinion

Categories

Resources