Should the properties inside defaultState be null or '' - reactjs

Most of the tutorials online show that the properties inside initialState should be '' (if it is a string), like:
let defaultState = Immutable.fromJS({
viewData: {
id: '',
name: '',
alist: []
}
});
But if you than have a habit of explicitly stating what properties a certain component uses, like:
function mapStateToProps(state) {
return viewData: state.someReducer.get('data');
}
someContainer.propTypes = {
data: ImmutablePropTypes.mapContains({
id: React.PropTypes.string.isRequired,
name: React.PropTypes.string.isRequired,
aList: ImmutablePropTypes.list.isRequired
}),
};
Than the only way i will get "Failed propType" is if any of the properties got removed from the reducer and the isRequired somehow feels redundant since it will always give an empty string which is than passed on to someContainer child components.
However, if i do:
let defaultState = Immutable.fromJS({
data: {
id: null,
name: null,
aList: null
}
});
Than i will get a failed proptype if the reducer didn't populate the state, isnt this wanted? However since i rarely see others set the props to null i feels there is a good reason not to.
Also, going with the null method nesting Maps in state gets confusing since it than even viewData should be null, but then you lose the information with not showing what data it really will get:
let defaultState = Immutable.fromJS({
viewData: { // Shouldnt this be null than as well?
id: null,
name: null,
hierarchies: { // and this
one: null,
two: null
}
}
});

null is a perfectly acceptable form of default state - its an assignment value indicating that this particular property is without value.
There is a bigger question at play, which is totally dependent on your particular application (only you can answer if null makes sense), but to answer your particular question about it "being ok" - yes, it's ok.
I don't like using empty strings as default state, I find it misleading to the application. For example, my consuming components "understand" that a null value means a value has yet to be provided, whereas an empty string implies one has indeed been set, but it is simply empty (lets say, a user without a middlename - the value is ""). This allows the application deal with the current state of the app more appropriately.
Btw - its a very excellent question that isn't often explicitly explored in tutorials (as you mention).

Related

useState strange behaviour

I have set 2 arrays with useState in 2 different files:
const [filterQuery, setFilterQuery] = React.useState({
instructor: [],
material: [],
})
const [query, setQuery] = React.useState({
instructor: [],
material: [],
})
And i have this function that will update query with values from filterQuery:
const applyFilter = (filterQuery) => {
setQuery((prev) => ({
...prev,
instructor: filterQuery.instructor,
material: filterQuery.material,
}));
}
The strange behavior is when i call setFilterQuery from a different function, an example is updating filterQuery when a button is clicked
onClick = (value) => {
setFilterQuery({
...filterQuery,
instructor: value
})
}
What I expected is it will just update filterQuery, but query will magically updated with filterQuery too whereas it has no connection to the function whatsoever.
What could trigger this ? I have tried a few things, declaring a new variable first before using the setQuery :
const filter = filterQuery;
setQuery((prev) => ({
...prev,
instructor: filter.instructor,
material: filter.material,
}));
not working.
I have also tried changing the set format without the prev, using just ...query or without them and still not working.
Thank you for reading, helps appreciated.. Will provide more code info / what else I have tried if asked.
In JavaScript primitive types are passed around as values: meaning that each time a value is assigned, a copy of that value is created.
On the other side objects (including plain objects, array, functions, class instances) are references. If you modify the object, then all variables that reference that object are going to see the change.
Here what basically happens is that you are passing the reference of query to filterQuery, not the value to query that's why when you change either query or filterQuery they both get updated.
You can see more details and examples at this question's answer and difference between values and reference

How to make several fields editable in React and Redux?

I have an object in my redux state
const obj = {
name: 'name',
age: 2,
place: 0,
}
I show these values on the page but I want to make two of them editable so that the object can be updated.
For that I'm basically getting values from two inputs and sending them to my action
export const saveEditedData = data => dispatch => {
dispatch({
type: CHANGE_DATA,
data,
});
}
and then in reducer
case 'CHANGE_DATA':
return {
...state,
obj: {
...state.obj,
name: action.data.name,
age: action.data.age,
}
}
The problem that I'm facing is that if one value is updated and another is not then after this action my second value in empty.
My question is what is a good way to determine which field is changed and update only it?
So far I only came up with putting if else in action to dispatch certain thing. Maybe there is a better way?
You can 1. make each field update with individual actions, changeName, changeAge, etc, or 2. filter the action payload to get rid of the unwanted values before putting them in your obj:
case 'CHANGE_DATA':
return {
...state,
obj: {
...state.obj,
...action.data.filter(el => !!el)
}
}
(the !! notation is converting the array elements to booleans when filtering, not strictly necessary but sanitary)
EDIT: Sorry I misunderstood your data shape, see comments.

Better syntax to update nested objects on redux

Given a reducer example like the following
_({
expandAbility: (state, a: { which: string }) => ({
...state,
report: state.report && {
waitingForIt: false,
content: state.report.content && {
...state.report.content,
interaction: {
expandAbilities: !state.report.content.interaction.expandAbilities.contains(a.which)
? state.report.content.interaction.expandAbilities.add(a.which)
: state.report.content.interaction.expandAbilities.remove(a.which)
}
}
}
}),
})
(state type given below just for question context purposes)
const initialState = {
report: undefined as
| {
waitingForIt?: boolean
content?: {
supportedFightIds: number[]
deathCut: number
reportInfo: any
deathsFull: any
deathsByPlayer: any
deathsByAbility: any
interaction: {
expandAbilities: Set<string>
}
}
}
| undefined,
error: undefined as Error | undefined
}
Is there any kind of trick or "flavor-of-the-moment" library which would allow me to write a reducer update operation like expandAbility in a shorter way? (besides maybe creating some vars to reference inner paths)
There are lots of immutable update utilities out there, check out some options at https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities and see what would be the best fit for you.
For starters check out Immutability-helper or immer.
So there are two things you could do to help simplify this. The first thing I like to do is move the logic out of the reducer and instead just pass in a value and say set expandAbilities to action. expandAbilities.
The second is actually something we do at work. We use immutableJS and wrote a single reducer that handles all of our state calls because you can give it a path of the parts of state that need to be updated and the value to update it with so we extracted that out and now it is easy to say dispatch(actions.update({path: ['report', 'content', 'interaction', 'expandAbilities'], value: '123' }))
You can even expand this so you can pass in a list of values that need to be updated and even preform validations around the data.

Reducer behavior

As I understand it, when an action is called, all reducers respond. If action exists in the switch case statement of the reducer, it executes. If it doesn't, then the case: default executes which preserves the existing state.
When the action exists in the reducer but the particular property it's trying to update does not exist, it seems to behave OK as there's nothing to update.
For example, I have an action creator that is used to set the visible property of my modals. Each modal has its own Id. My code looks like this:
export default (state = initialState, action) => {
case types.SET_MODAL_IS_VISIBLE:
return Object.assign({}, state,
{ modal22: action.value }
)}
I have the SET_MODAL_IS_VISIBLE in multiple reducers but if modal22 is not defined in a particular reducer, nothing happens and no errors.
Now, I have a scenario that is throwing an error. I have a general purpose date picker component that I built that can be used as a single and independent date picker OR it can be "linked to" another one. The second scenario is useful if I need the user to give me two dates e.g. start and end dates.
I also built a feature where if the date picker is coupled with another one, when the user sets the date in the first date picker, I disable all the dates prior to that date in the second date picker because I don't want the user to unintentionally select an end date that is prior to the start date.
I define my date pickers as below:
const initialState = {
datePickers: {
"startDatePicker": {
activeDate: "8/25/2017",
disabledBefore: "",
linkedTo: "endDatePicker"
},
"endDatePicker": {
activeDate: "",
disabledBefore: "8/25/2017" // This date is set when the user sets the active date in startDatePicker
linkedTo: ""
}
}
}
This scenario is a bit interesting because a state change in one property in my reducer is triggering a state change in another. This is not difficult to do and I have a way of controlling when I do the update.
The action for setting disabled dates looks like below:
...
case types.SET_DISABLED_DATES:
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
Please keep in mind that I can and should be able to set disabledBefore even if the date picker is used as an independent one. So, I need my SET_DISABLED_DATES in every reducer.
The problem I'm running into is that whenever I call SET_DISABLED_DATES, I get errors in reducers where the date picker is used as a single/independent one because the date picker Id for its pair is NOT defined in the reducer.
For example, in projectsReducer I may use the date picker as part of a pair so both startDatePicker and endDatePicker are defined and everything works fine.
But I may be using a single instance date picker in the tasksReducer which also responds to the SET_DISABLED_DATES call but it fails because it cannot find the endDatePicker. In this scenario, the tasksReducer is responding to the call I made to set the disabledDates property of endDatePicker in projectsReducer.
I've posted two questions about this already and the only real solution I'm seeing here is that I need to have a condition in my reducer that looks like this:
...
case types.SET_DISABLED_DATES:
if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
} else {
return state;
}
Admittedly, this looks a bit like a kludge but I couldn't really come up with another solution here.
Again, the problem is that for as long as all reducers respond to SET_DISABLED_DATES, it's guaranteed that a particular date picker will not be there and the Object.assign() will throw an error.
Any suggestions? Is the simple condition in the reducer the way to go here? Is it a kludge?
P.S. I tried this code and it works fine and fixes the problem. On the one hand, I feel this is a bit of an anti-pattern but on the other hand, it just seems like a good idea to make sure the property I want to update in my reducer exists before attempting to update it. I'd appreciate your feedback on this. Thanks.
You are just doing basic validation in the reducer before setting the state. That is perfectly fine. I don't think it will be a good practice to check the store in the action creator to prevent dispatching actions on objects not in the store (how would you do that anyway!).
What I don't understand is, how can a datepicker be linked to another datepicker that isn't in the store? Maybe dispatch a create and teardown action on the component's didMount and willUnmount?
I don't know your full requirements but I think we can make it a lot simpler. I'd do something like this:
The store:
{
datePickers: {
id1: {
value: '',
minValue: '',
maxValue: '',
},
id2: {
value: '',
minValue: '',
maxValue: '',
}
}
}
Now, unless you are making some kind of coupled datepicker components that will always behave in pairs, I believe the cleanest approach would be to set the disabled value in the linked datepicker in the mapDispactchToProps function in your parent component.
That is where you would set ids to the components, and you know exactly which component should be disabled before another.
Something like:
dispatch => ({
setArrivalDate(value) {
dispatch(datePickerActions.setValue(arrivalDateId, value);
dispatch(datePickerActions.setMaxValue(depatureDateId, value);
},
setDepatureDate(value) {
dispatch(datePickerActions.setValue(depatureDateId, value);
dispatch(datePickerActions.setMinValue(arrivalDateId, value);
}
})
This may not be abstract enough, but is clean.
You could do the same thing if you have a paired component, but you'd still need to know which date comes before another. It'd be a hassle to make a generic abstraction around it.
Remove the bold part in your code below
...
case types.SET_DISABLED_DATES:
if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
} else {
return state;
}
Also, a little bit of es6 spread and a helper switchcase function makes this code much more readable.
const newReducer = (state = defaultState, action) => switchcase({
[types.SET_DISABLED_DATES]:
state.datePickers[action.datePickerId] === undefined
? state
: ({ ...state,
datePickers: { ...state.datePickers,
[action.datePickerId]: { ...state.datePickers[action.datePickerId],
disabledBefore: action.value,
},
},
}),
})(state)(action.type);
Using lodash/fp/set, the code becomes
const reducerWithLodash = (state = defaultState, action) =>
switchcase({
[types.SET_DISABLED_DATES]:
state.datePickers[action.datePickerId] === undefined
? state
: set({...state}, `datePickers.${action.datePickerId}.disabledBefore`, action.value)
})(state)(action.type)
I haven't tested the lodash version, so please take that with a grain of salt (Dan Abramov seems to approve)

Loading static constants in React+Redux

I'm using server side rendering for my React-Redux application. And I want at application startup to load some constants, for example list of cities with corresponding IDs:
[
{
name: "London",
id: 1
}
...
]
I think it's better to put this data into store on server side and provide it to client using window.__INITIAL_STATE__ as suggested here http://redux.js.org/docs/recipes/ServerRendering.html
This constants will be read-only, and I want to preload them just for data normalization purposes. For example later I can just retrieve list of users with city IDs, instead of users with city names: {name: "Alex", cities: [1,2]}
The problem is that if I put them into store, then I forced to create reducer for this constants, otherwise I'm getting this error:
Unexpected key "cities" found in preloadedState argument passed to
createStore. Expected to find one of the known reducer keys instead:
"colors". Unexpected keys will be ignored.
So I'm searching for some elegant way to handle this situation.
For now I have 2 ideas how to handle it:
Create empty reducer which always will return default state
export const cities = (state = [], action={ type: null }) => {
return state
}
Send from server initial actions with payloads, and execute them on client at startup:
// server.js
window.INITIAL_ACTIONS = [
{ type: "REQUEST_LOGIN_SUCCESS", user: {userId, userName, userEmail, etc} },
{ type: "REQUEST_CITIES_SUCCESS", [..listOfCities] },
]
And in my client-index.js, dispatch those actions right after creating the store:
//client-index.js
window.INITIAL_ACTIONS.forEach(store.dispatch)
So, is one of my approaches is good? Or may be you know some other, more elegant solution?
Thanks.
We do something similar with a dummy "settings" reducer. i.e.
const rootReducer = combineReducers({
...
settings: (state = {}) => state,
...
});
This gives us a convenient place to store all our app config.
Just make sure you key your initial state in the same manner. i.e.
window.__INITIAL_STATE__ = {
...
settings: { ... },
...
};
Some may object to this practise, but I think it's sound. Though settings may be constant, it is nonetheless state. It conforms to the redux practice of a single state object. (Besides, there may come a future point where the settings state slice will be dynamic and require a "real" reducer.)

Resources