I'm learning to use useReducer. I wrote in react input which, after clicking the button, adds an element to the table and displays on the page, I also want to add a logic that will not allow adding two the same elements with the same content to the table, but unfortunately it does not work, please help. console.log works, but adds an item to the array anyway
const tab = []
const App = () => {
const [state, dispatch] = useReducer(
(state, action) => {
switch (action.type) {
case 'ADD':
for (const n of state) {
if (n.name === action.course.name) {
console.log('repeated text')
return
}
}
return [...state, action.course]
}
}, tab)
Hard to test this without your specific data, but this should work:
const [state, dispatch] = useReducer(
(state, action) => {
switch (action.type) {
case 'ADD':
if(state.find(i => i.name === action.course.name))
return state
else
return [...state, action.course]
}
}, tab)
Related
I have two 'select' inputs (html) that work togueter to display info, this is the state:
const [foodValues, setFoodValues] = useState({ type: 'Everything', category: 'Everything' })
Everything works just fine, I'm using a classic handleChange() on both inputs:
const handleChange = e => {
const value = e.target.value
const name = e.target.name
dispatch({type: ACTIONS.CHANGE, payload: {e: value, name: name}})
}
As you can see, I call the dispatch to use the useReducer:
const OperationsReducer = (foodReducer, action) => {
const { foodValues, setFoodValues } = useContext(AllContext)
switch (action.type) {
case ACTIONS.CHANGE:
setFoodValues({
...foodValues,
[action.payload.name]: action.payload.e
})
break;
}
}
And it works just fine! But I'm getting this warning in the console:
warning Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. You can only call Hooks at the top level of your React function. For more information, see
So, my question is: is there a way to use this useReducer without that warning? I know I could just use setFoodValues outside of it, but I'm curious (I really like using useReducer...). I already read the React docs on this topic, but I saw some people doing some really weird (and cool) to stuff to work around this.
const initialState = { type: 'Everything', category: 'Everything' };
function reducer(state, action) {
switch (action.type) {
case ACTIONS.CHANGE:
return {
...state,
[action.payload.name]: action.payload.e
};
default:
return state;
}
}
function Component() {
const [state, dispatch] = useReducer(reducer, initialState);
const handleChange = e => {
const value = e.target.value
const name = e.target.name
dispatch({type: ACTIONS.CHANGE, payload: {e: value, name: name}})
}
...
}
I have 2 questions on managing the state for my app.
Part 1: I can't update and set the entire state with my reducer, when I fetch the data from the server, but can change a part of that state:
state.main_meals = action.payload - works
state = action.payload - doesn't work
Part 2: I have heard that you shouldn't be using pure "setter" functions in redux, but how else am I supposed to set my state?
here is my reducer:
setDiaryState: (state, action) => {
state = action.payload;
},
and my component:
const diary = useAppSelector((state) => state.diary);
const dispatch = useAppDispatch();
const [user, loading, error] = useAuthState(auth);
useEffect(() => {
{
user &&
db
.collection("users")
.doc(`${auth.currentUser?.uid}`)
.collection("diaryState")
.doc("currentState")
.onSnapshot((doc) => {
const data = doc.data();
dispatch(setDiaryState(data));
});
}
}, [user]);
Your reducer must always return a immutable state here,
state = action.payload;
You are clearly mutating it which won't trigger a re-render,this can be fixed by simply add a return statement.
This would replace your entire state by action.payload.
setDiaryState: (state, action) => {
return {...action.payload};
},
if you wish to modify a subset of your state you can use:
setDiaryState: (state, action) => {
return {...state, main_meals:action.payload};
},
This way you ensure that your state is always immutable.
I have two reducers, one with the name of Month and the other with Loading
export const Month = (state = '', action) =>
action.type === 'selectedMonth' ? (state = action.payload) : '';
Second one,
export const isLoading = (state = false, action) => {
switch (action.type) {
case 'setLoading':
return !state;
case 'removeLoading':
return false;
default:
return state;
}
};
I'm using dispatch inside the useEffect multiple times for the different actions. At first call i-e dispatch(actions.setMonths()) works fine but when I recall the dispatch for the different action i-e dispatch(actions.setLoading()) the store gets updated but the state of the month is set to the initial state.
dispatcher calls,
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
dispatch(actions.setLoading());
const res = await getData('users');
dispatch(actions.removeLoading());
};
//any code
dispatch(actions.selectedMonth("argument"));
fetchData();
}, [months, dispatch]);
More Details,
Your Month reducer returns '' if the type of the action is not selectedMonth. So when the action is setLoading or removeLoading, the month gets setback to an empty string
change your Month reducer to
export const Month = (state = '', action) =>
action.type === 'selectedMonth' ? action.payload : state;
I am using Redux for state management, but I encountered a problem. My issue is I like to set state only if state is different. Let me clarify my problem through my code.
// MyComponent.jsx
const [query, setQuery] = useState('');
useEffect(() => {
if(query.length) {
let {search, cancel} = searchContent(query);
search.then(res =>
setSearchResult(res.data)
).catch(e => {
if(axios.isCancel(e)){
return;
}
})
return () => cancel();
}else{
setSearchResult(null);
}
}, [query, setSearchResult])
Above is my component that is supposed to set search state.
// action.js
export const SET_SEARCH_RESULT = 'SET_SEARCH_RESULT';
export const setSearchResult = (val) => ({
type: SET_SEARCH_RESULT,
searchResult: val,
});
//reducer.js
import { SET_SEARCH_RESULT } from './article.action';
const INITIAL_STATE = {
searchResult: null
}
const articleReducer = (state=INITIAL_STATE, action) => {
switch (action.type) {
case SET_SEARCH_RESULT:
return {
...state,
searchResult: action.searchResult
}
default:
return state
}
}
I am able to set state using redux and it works fine. However, my problem is even though initial state is null, when useEffect function runs initially my state sets to null.
My question is how can I use redux so that only it runs if state is different.
Thanks in advance.
I would like to update Redux State on the basis of one action, as follows:
export const editAction = (itemType, itemContent, id) => (dispatch) => {
return axios.put(`${url}/${itemType}/${id}`, {
itemType,
...itemContent,
})
.then(({ data }) => {
console.log(data);
dispatch({
type: EDIT_SUCCESS,
itemType,
data,
id,
});
})
};
I omitted catch block to shorten source code.
What return should I use to update Redux State in reducer after EDIT_SUCCESS action type?
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_SUCCESS:
return {
...state,
[action.itemType]: [...action.data],
};
case ADD_SUCCESS:
return {
...state,
[action.itemType]: [...state[action.itemType], action.data],
};
case EDIT_SUCCESS:
return {
...state,
// ???,
};
case DELETE_SUCCESS:
return {
...state,
[action.itemType]: [...state[action.itemType].filter(item => item._id !== action.id)],
};
}
};
It is quite clear to me how to implement FETCH, ADD and DELETE because I done it (as you can see) but I have no idea how to implement return for EDIT_SUCCESS.
But importantly, after run editAction() (submit button in Formik), editing object is updated (by axios) in the database correctly but State in Redux DevTools as well as view in a browser remains the same. Only when I refresh page, I see the difference.
If you have edited an element, you should look for it and update it.
Something like:
const newState = {...state};
const indexOfElementToUpdate = newState[action.itemType].findIndex(item => item._id === action.id);
newState[action.itemType][indexOfElementToUpdate] = action.data;
return newState;