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.
Related
I have An action That I am trying to use in my component, My FetchPosts() function will work and map to state, but when I use my other Action FetchUserPosts() I can see in the redux dev tools that the action dispatches, but in State nothing is there and the state doesn't change at all.
It could be something with my action but im not sure.
postAction.js
import { FETCH_POSTS, NEW_POSTS, FETCH_POST_FOR_APPROVAL,
FETCH_POSTS_FROM_USER } from './types';
export const fetchPostsFromUser = (id) => dispatch => {
Promise.all([fetch('http://10.6.254.22:5000/posts/1' + id)])
.then(([res1]) => {
return Promise.all([res1.json()])
})
.then(([posts]) => dispatch ({
type: FETCH_POSTS_FROM_USER,
payload: posts
}));
}
Types.js
export const FETCH_POSTS_FROM_USER = 'FETCH_POSTS_FROM_USER';
PostReducer.js
import { FETCH_POSTS, NEW_POSTS, FETCH_POST_FOR_APPROVAL,
FETCH_POSTS_FROM_USER } from '../actions/types';
const initialState = {
items: [],
item: {}
}
export default function (state = initialState, action ){
switch(action.type) {
case FETCH_POSTS:
console.log('currently reducing')
return{
...state,
items: action.payload
}
case NEW_POSTS:
return{
...state,
item: action.payload
}
case FETCH_POST_FOR_APPROVAL:
return{
...state,
items: action.payload
}
case FETCH_POSTS_FROM_USER:
return{
...state,
items: action.payload
}
default:
return state;
}
}
My Component I want the function to work with so i can map the props.
class ProfilePostBody extends Component {
constructor(props){
super(props)
this.state = {
commentBody: null,
postId: null,
giphyUrl: null,
postPicture: null,
userId: null,
userIdto: null,
userIdName: null,
userIdtoName:null,
// postBody: null,
// giphyUrl: null,
// userIdto: null,
// userIdName: null,
// userIdtoName:'Julio',
displayGifPicker: false
}
}
componentDidMount(){
this.props.fetchPostsFromUser();
this.props.fetchPosts();
}
render () {
return (
// <Grid item xl={6}>
<div>
{this.props.posts.map((post, index) =>
<PostBodyTemplate key={index} postId={post.id} onSubmit=
{this.handleSubmit} onChange={e =>
this.handleInputChange(e,post.id,post.userId,post.userIdName)} title=
{post.title} postBody={post.postBody}
giphyUrl = {post.giphyUrl} userWhoPosted={post.userIdName}
commentBody={post.commentBody} userIdtoName={post.userIdtoName}
userIdName={post.userIdName} />
)}
</div>
)
}
}
ProfilePostBody.propTypes = {
fetchPostsFromUser: PropTypes.func.isRequired,
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array.isRequired
}
const mapStateToProps = state =>({
posts: state.posts.items
})
export default connect(mapStateToProps, { fetchPosts, fetchPostsFromUser
})
(ProfilePostBody);
fetchPosts will work, but if i comment it out and try just the fetchPostsFromUser() it will not work.
I'm dispatching an action that looks like this:
{ type: "TOGGLE_FARA", fara: true, id: "5d20d019cf42731c8f706db1" }
The "id" is merely used for identifying the correct user. The action is meant to modify the "enabled" property of my "fara" state. The fara section of my store looks like this:
{
fara: {
enabled: false, // This need to be flipped...
names: []
},
senators: {
enabled: false,
names: []
},
senateCandidates: {
enabled: false,
names: []
}
}
However, I'm not sure how to structure my reducer. I only want to change the "enabled" property. I've tried a few different ways:
export default (state = DEFAULT_STATE, action) => {
switch (action.type) {
case "INITIALIZE_SETTINGS":
return {
fara: action.fara,
senators: action.senators,
senateCandidates: action.senateCandidates,
emails: action.emails
}
case "TOGGLE_FARA":
return {
...state,
// fara['enabled']: action.fara <–– This won't compile...
// 'fara.enabled' : action.fara <–––This just gives me a key name with the string 'fara.enabled'
}
default:
return state;
}
I was going to do something like this, but this modifys the state directly which you aren't supposed to do (my redux tool extension says the states are the same, which is not ideal). Any thoughts?
export default (state = DEFAULT_STATE, action) => {
switch (action.type) {
case "INITIALIZE_SETTINGS":
return {
fara: action.fara,
senators: action.senators,
senateCandidates: action.senateCandidates,
emails: action.emails
}
case "TOGGLE_FARA":
state.fara.enabled = action.fara;
return {
...state
};
default:
return state;
}
This will work:
return {
...state,
fara: { ...state.fara, enabled: action.fara }
}
and apparently is even in the Redux documentation
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.
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.
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