Link to the repo: https://github.com/charles7771/ugly-code
At the 'Options' component, I am not writing anything dynamically for a form I'm creating, I am giving each one of the reducer cases a different name. That is not scalable at all and I have no idea how to go about fixing it. Any thoughts?
It is something like this:
case 'SET_SMALLPRICE0': //goes from 0 to over 20
return {
...state,
smallPrice0: action.payload,
}
case 'SET_MEDIUMPRICE0':
return {
...state,
mediumPrice0: action.payload,
}
You could name the action type the exact name of the property to update, or perhaps with a "set_" in front. That would allow you to automatically set that property.
const reducerPriceCarSizeEachService = (state, action) => {
return { ...state, [action.type]: action.payload }
}
or, with a "set_" in front (this could allow you to use different prefixes, such as "push_", "delete_" depending on what you actually want to do)
const reducerPriceCarSizeEachService = (state, action) => {
if (action.type.startsWith('set_')) {
let prop = action.substr(4)
return { ...state, [props]: action.payload }
}
return state
}
Related
I've noticed that in many useReducer examples, the spread operator is used in the reducer like this:
const reducer = (state, action) => {
switch (action.type) {
case 'increment1':
return { ...state, count1: state.count1 + 1 };
case 'decrement1':
return { ...state, count1: state.count1 - 1 };
case 'increment2':
return { ...state, count2: state.count2 + 1 };
case 'decrement2':
return { ...state, count2: state.count2 - 1 };
default:
throw new Error('Unexpected action');
}
};
However in many of my practices, I removed ...state and had no issues at all. I understand that ...state is used to preserve the state of the remaining states, but would a reducer preserve those states already so the ...state is not needed?
Can someone give me some examples where ...state is a must and causes issues when removed with useReducer hook? Thanks in advance!
No, a reducer function alone would not preserve existing state, you should always be in the habit shallow copy existing state. It will help you avoid a class of state update bugs.
A single example I can think of when spreading the existing state may not be necessary is in the case where it isn't an object.
Ex: a "count" state
const reducer = (state = 0, action) => {
// logic to increment/decrement/reset state
};
Ex: a single "status" state
const reducer = (state = "LOADING", action) => {
// logic to update status state
};
Spreading the existing state is a must for any state object with multiple properties since a new state object is returned each time, in order to preserve all the existing state properties that are not being updated.
Edit 1
Can you give an example when NO shallow copying causing state update bugs?
const initialState = {
data: [],
loading: false,
};
const reducer = (state, action) => {
switch(action.type) {
case LOAD_DATA:
return {
...state,
loading: true,
};
case LOAD_DATA_SUCCESS:
return {
...state,
data: action.data,
loading: false
};
case LOAD_DATA_FAILURE:
return {
loading: false,
error: action.error,
};
default:
return state;
}
};
As can been seen in this example, upon a data load failure the reducer neglects to copy the existing state into the new state object.
const [state, dispatch] = useReducer(reducer, initialState);
...
useEffect(() => {
dispatch({ type: LOAD_DATA });
// logic to fetch and have loading failure
}, []);
return (
<>
...
{state.data.map(...) // <-- throws error state.data undefined after failure
...
</>
);
Any selector or UI logic that assumes state.data always exists or is always an array will fail with error. The initial render will work since state.data is an empty array and can be mapped, but upon a loading error state.data is removed from state.
I have a page, which has two sections:
1) List of questions;
2) Top-voted list of questions
These two queries use the same call to the backend API, the only difference is I pass an additional parameter, e.g. showPopular=true in the latter case.
When I try to render data, whatever action retrieves last overwrites data in both sections. How do I distinguish such data?
I am using ReactJS, Redux.
This is to retrieve data:
query={showPopular:true}
this.props.actions.loadQuestions(accountId, query);
I have
const mapStateToProps = (state) => {
return {
error: state.questions.error,
questions: state.questions.questions|| [],
isLoading: state.questions.isLoading
};
}
You should at least separate state/prop/reducer because this two list are totally different, and they obviously will render different DOM.
The mapState should have different props mapping:
const mapStateToProps = (state) => {
return {
error: state.questions.error,
questions: state.questions.questions|| [],
topQuestions: state.questions.topQuestions,
isLoading: state.questions.isLoading
};
}
You didn't post the reducer but it should also separate the two action or by using condition to update different state:
export default (state = initialState, action) => {
switch (action.type) {
case LOAD_QUESTION:
if(action.query.showPopular){
return state.set('topQuestions', action.data);
}else{
return state.set('questions', action.data);
}
default:
return state;
}
};
From a Redux tutorial I've been going through they allow you to add a place multiple times. I changed the reducer to reject duplicates. My question is, (see code), do I have to return the state if no updates are made or is there some other way of indicating no state is changed?
function placeReducer(state = initialState, action) {
switch (action.type) {
case ADD_PLACE:
const existing = state.places.find((item) => item.value == action.payload);
if (existing) {
return {...state};
}
return {
...state,
places: state.places.concat({
key: Math.random(),
value: action.payload
})
};
default:
return state;
}
}
Just return the state, no need to create a new copy.
Hello (sorry for the title, I couldn't come up with anything else)!
I am using Redux to fetch my data:
componentDidMount() {
this.props.fetchData('http://localhost:3001/locks')
}
// Stuff between
const mapStateToProps = state => {
return {
doors: state.doors
}
}
And when I console.log console.log(doors) I see this:
Which is great! This is exacly how I want it to be, but if I navigate to i.e. /userstolock and then back to / (where I was before) I'm getting this error; TypeError: doors.map is not a function on this line:
const doors = this.props.doors.data || []
console.log(doors)
const doorsItems = doors.map(door => <DoorsItem door={door} />) // This line!!
Also my output change:
So I wonder why this problem occur. I'm sorry if I explained badly, but I don't understand this problem therefor I really can't explain it. If anyone need more code, please just let me know!
Thanks a lot.
Edit
I got a comment that suggested me to remove the .data. I tried it, but that gave me no luck. Here's the output from it:
I've been asked to add the reducer and here it is:
const initialState = {
data: []
}
export function doorsHasErrored(state = false, action) {
switch (action.type) {
case 'DOORS_HAS_ERRORED':
return action.hasErrored
default:
return state
}
}
export function doorsIsLoading(state = false, action) {
switch (action.type) {
case 'DOORS_IS_LOADING':
return action.isLoading
default:
return state
}
}
export function doors(state = initialState, action) {
switch (action.type) {
case 'DOORS_FETCH_DATA_SUCCESS':
return action.doors
default:
return state
}
}
Update
Sorry it's difficult to debug without full code, but it looks like your reducers might be overwriting your state.
Typically you'll modify the state object instead of setting it to the action, something like this:
export function doors(state = initialState, action) {
switch (action.type) {
case 'DOORS_FETCH_DATA_SUCCESS':
return {
..state,
doors: action.doors
}
default:
return state
}
}
I think when your other reducers are running, they may be overwriting the entire state. You may have to modify all your reducers to avoid overwriting.
Check out the redux reducer docs for typical reducer usage.
Object.assign({}, state, {doors: action.doors})
would be equivalent to
{
...state,
doors: action.doors
}
--
Also it seems like your initial state might be missing doors?
Maybe something like:
const initialState = {
doors: {
data: []
}
}
I seem to have hit a snag when updating state using redux and react-redux. When I update an individual slice of state, all of the others get removed. I know the answer to this will be simple but I can't figure it out and haven't found anything else online.
So to clarify, here's my reducer:
const initialState = {
selectedLevel: null,
selectedVenue: null,
selectedUnitNumber: null,
selectedUnitName: null,
selectedYear: null
}
export default (state = initialState, action) => {
console.log('reducer: ', action);
switch (action.type){
case 'CHOOSE_LEVEL':
return action.payload;
case 'CHOOSE_VENUE':
return action.payload;
case 'CHOOSE_UNIT':
return action.payload;
case 'SHOW_COURSES':
return action.payload;
}
return state;
}
And my combine reducer:
export default combineReducers({
workshopSelection: WorkshopSelectReducer
});
So my initial state looks like this:
workshopSelection: {
selectedLevel: null,
selectedVenue: null,
selectedUnitNumber: null,
selectedUnitName: null,
selectedYear: null
}
But when I use one of my action creators, for example:
export function chooseVenue(venue){
return {
type: 'CHOOSE_VENUE',
payload: {
selectedVenue: venue
}
}
}
I end up with state looking like this:
workshopSelection: {
selectedVenue: 'London',
}
All of the rest of the state within this object that wasn't affected by this action creator has been completely wiped out. Instead, I just want all other entries to stay as they are with their original values - null in this example, or whatever other value has been assigned to them.
Hope that all makes sense.
Cheers!
You are basically replacing one object (previous state) with another one (your payload, which is also an object).
In terms of standard JS, this would be the equlivalent of what your reducer does:
var action = {
type: 'CHOOSE_VENUE',
payload: {
selectedVenue: venue
}
};
var state = action.payload;
The simplest way to fix this would be using Object spread properties:
export default (state = initialState, action) => {
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
// Watch out, fall-through used here
return {
...state,
...action.payload
};
}
return state;
}
... but since this is still in experimental phase, you have to use some other way to clone previous properties and then override the new ones. A double for ... in loop could be a simple one:
export default (state = initialState, action) => {
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
// Watch out, fall-through used here
const newState = {};
// Note: No key-checks in this example
for (let key in state) {
newState[key] = state[key];
}
for (let key in action.payload) {
newState[key] = action.payload[key];
}
return newState;
}
return state;
}
Keep your payload object as flat on actions creators as shown below...
export function chooseVenue(venue){
return {
type: 'CHOOSE_VENUE',
selectedVenue: venue
}
}
and modify your reducer as below (given example is for updating the venue, do the same for other cases too...)
export default (state = initialState, action) => {
let newState = Object.assign({}, state); // Take copy of the old state
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
newState.selectedVenue = action.selectedVenue; // mutate the newState with payload
break;
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
default :
return newState;
}
return newState; // Returns the newState;
}