I am trying to save recent searches in local storage.
Reducer.js
const initialState = {
venues: [],
searches: [],
};
const venueReducer = (state = initialState, action) => {
switch (action.type) {
case RECENT_SEARCHES:
return {
...state,
searches: [
...state,
{ near: action.payload.location, query: action.payload.place },
],
};
}
};
Action.js
export const fetchVenues = (place, location) => dispatch => {
dispatch({ type: FETCH_VENUE_REQUESTED })
dispatch({ type: RECENT_SEARCHES, payload: { place, location } })
};
Right now I am getting the user input and saving it to the searches array in reducer but it's not keeping the previous state. Only shows the current user input.
When you spread the searches, you need to do:
[ ...state.searches,
Instead of just:
[ ...state,
As that will put state.venues (Array) and state.searches (Array) in your state.searches
Related
I am working on a application which uses redux for state management. There, at a some condition, I want to update the state.
My initial state and reducer function looks like this:
import { createSlice } from '#reduxjs/toolkit';
const filterDataTemplate = {
programId: '',
year: '',
};
const initialState = {
//some other state
filterData: { ...filterDataTemplate },
};
const slice = createSlice({
name: 'editFilterSlice',
initialState: initialState,
reducers: {
updateFilterProgramId: (state, action) => {
state.filterData.programId = action.payload;
},
updateFilterYear: (state, action) => {
state.filterData.year = action.payload;
},
},
});
export const {
updateFilterYear,
updateFilterProgramId,
} = slice.actions;
export default slice.reducer;
So filter details containg year and programId is obtained with the help of this code:
const filterDetails = useAppSelector(
(state) => state.locationsFilter.filterData
);
Let's say I have filter data initially:
filterDetails: {year:2021, programId: "Ameria"}
And i want to have my new filter data to be
filterDetails: {year: "", programId: "Ameria"}
So for this what I am doing:
const handleDelete = (e) => {
e.preventDefault();
if (//some condition) {
console.log("delete is called");
dispatch(updateFilterYear(''));
} else {
dispatch(updateFilterProgramId(''));
}
}
handleDelete function is getting called properly when I am clicking a button because I am getting value inside console.
But after running this code my filter data is not updating. I am not sure what I am doing wrong.
Please help with this.
Action.payload is of object type. So You should reference action.payload.year.
I hope this example will be of any use
setTodoDate: {
reducer: (state, action: PayloadAction<TodoDate>) => {
state.currentDate = action!.payload.date;
},
prepare: (value) => ({
payload: { ...value },
}),
}
I think the issue is because you are trying to mutate your state directly. This is bad practice, and Redux state (and more generally react) is intended to be immutable. Reducers should return a copy of the state, along with the updated values. Documentation linked below.
Redux Documentation
Try writing your reducers like the following
updateFilterYear: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
year: action.payload
}
},
updateFilterProgramId: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
programId: action.payload
}
},
I am building a multi step form and I am using react-hook-form and redux-toolkit.
Everything works, but on some pages I have multiple input elements (ex. goal and height).
My current code:
const rootSlice = createSlice({
name: "root",
initialState: {
goal: "",
height: "",
age: "",
username: "",
},
reducers: {
chooseGoal: (state, action) => {
state.goal = action.payload;
},
chooseActivityLevel: (state, action) => {
state.activity_level = action.payload;
},
chooseYourAge: (state, action) => {
state.age = action.payload;
},
chooseYourUserName: (state, action) => {
state.username = action.payload;
},
},
});
const goal = useSelector((state) => state.goal);
const { register, handleSubmit } = useForm({
defaultValues: { goal, activity_level },
});
const onSubmit = (data) => {
dispatch(chooseGoal(data.goal));
dispatch(chooseActivityLevel(data.activity_level));
};
I was wondering if I can hook multiple form values to one reducer something like this:
chooseGoalAndHeight: (state, action) => {
state.goal && state.height = action.payload;
},
// And dispatch it like this:
dispatch(chooseGoalAndHeight(data.goal, data.height));
Also is it okay to initialize a form element that will have a number input with empty string?
Good question. This is one of the three main problems in the redux world. See Redux Problems
You can solve that problem clearly and beautifully, using redux-cool lib which has Global and local Actions capability.
Here is how I would do it where I changed the initialState to be an array of the objects you want to add from the form instead of just an object as you probably want to store multiple instances of the form data. Then I combined all the reducers into a single reducer and added in a prepare callback which prepares the action.payload to ensure it is the same format as the object of the initialState. You can read more about Preparing Action Payloads here.
const rootSlice = createSlice({
name: "root",
initialState: [
{
goal: "",
height: "",
age: "",
username: "",
},
],
reducers: {
submitForm: {
reducer(state, action) {
state.push(action.payload);
},
prepare(goal, activity_level, age, username) {
return {
payload: {
goal,
activity_level,
age,
username,
},
};
},
},
},
});
Then you would use the reducer like so just passing in properties of the form as per the initialState objects structure.
dispatch(submitForm(data.goal, data.activity_level, data.age, data.username));
You can try this method. I recently implemented it.
const initialState = {
data: {},
};
const rootSlice = createSlice({
name: 'currentUserDetails',
initialState,
reducers: {
addUserInfo: {
reducer(state, action) {
state.data = {
...state.data,
...action.payload
};
},
},
},
});
export const {
addUserInfo
} = currentUserDetails.actions;
export default rootSlice.reducer;
// Inside your Component
const data = {
goal,
activity_level
};
//dispatch
dispatch(addUserInfo(data));
If I fetch this array of restos with redux:
[{
res_id: Int,
res_name: String,
res_category: String,
res_category_id: Int,
city_id: Int
}]
My action looks something like this:
export const getrestos = () => {
const resData = await response.json();
dispatch({
type: GET_RESTOS,
payload: resData
});
};
};
export const setFilters = filterSettings => {
console.log(filterSettings);
return { type: SET_FILTERS, filters: filterSettings };
};
And this is my reducer:
import { GET_RESTOS, SET_FILTERS } from '../actions/restos';
const initialState = {
restoList: [],
filteredRestos: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_RESTOS:
return {
restoList: action.payload
}
case SET_FILTERS:
const appliedFilters = action.filters;
const updatedFilteredRestos = state.restoList.filter(resto => {
if (appliedFilters.cityID || resto.city_id) {
resto => resto.city_id.indexOf(cityID) >= 0
return { ...state, filteredRestos: updatedFilteredRestos };
}
});
return { ...state, filteredRestos: updatedFilteredRestos };
default:
return state;
}
};
I have touchable categorys in a page, and when i touch one i want to fetch the corresponding restos for that category and show them in a flatlist. Apart from that i want to have a search bar that when I type I want to show restos by res_name and/or by res_category.
Ive tried to create selectors, but I dont understand how, i dont need an specific approach, but the most clean or efficient as possible.
Thanks in advance if anyone can give me a hint or solution!
EDIT
The problem is im getting undefined in updatedFilteredRestos.
Your reducers should be clean, dumb and all they do should be returning objects. This makes your components more testable and errors easier to catch. In my opinion, this is a perfect use-case for reselect. Here's a medium article: https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c But the true beauty of reselect is that it will memoize for you, i.e. if your states don't change, it uses a cached version of the data.
Anyway, you should clean up your restoReducer to something to this effect.
import { GET_RESTOS, SET_FILTERS } = "../actions/restos";
const initialState = {
restoList: [],
filteredRestos: []
};
const restoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_RESTOS:
return { ...state, restoList: action.payload };
case SET_FILTERS:
return { ...state, filteredRestos: action.payload };
default:
return state;
}
}
Then write your filtered resto selector:
// ../selectors/restos
import { createSelector } from "reselect";
// First, get your redux states
const getRestos = (state) => state.restos.restoList;
const getFilteredRestos = (state) => state.restos.filteredRestos;
// Next, create selectors
export const getFilteredRestoList = createSelector(
[getRestos, getFilteredRestos],
(restoList, filteredRestos) => {
// need to check for non-empty filters
// if it is, simply return the unfiltered `restoList`
if(!Array.isArray(filteredRestos) || !filteredRestos.length)
return restoList || [];
// If you do have valid filters, return filtered logic
return restoList.filter(r => filteredRestos.some(f => f.cityID === r.city_id));
);
Then, use this selector in your components:
// ../components/my-app
import { getFilteredRestoList } from "../selectors/restos";
// hook it up to your `mapStateToProps` as you would a normal state
// except this time, it's a special selector
const mapStateToProps = (state, ownProps) => {
restoList: state.restos.restoList,
filteredRestos: state.restos.filteredRestos,
filteredRestoList: getFilteredRestoList(state) //<-- this is your selector
}
Then inside your component, just reference it: this.props.filteredRestoList.
I use Redux in my project for first time. I have multiple reducers and and actions. When the first action is dispatched, state is changed. It looks okey. After dispatching second action, state is changed again but the previous changes are removed. I mean, when 'FETCH_COMPANY_INFORMATIONS' is dispatched companyName is changed and companyDesc set to initial value. Then 'FETCH_INITIAL_MEMBER' is dispatched and companyName is removed but companyDesc is still there and member payloads are also changed. What is my mistake? Thanks.
I tried many ways to solve this but still continue. I check this on Redux DevTools.
memberReducer
const initialState = {
username: '',
companyId: '',
isAdmin: '',
photo: '',
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_INITIAL_MEMBER:
return {
...state,
username: action.payload.username,
companyId: action.payload.companyId,
isAdmin: action.payload.isAdmin,
};
default:
return state;
}
};
companyReducer
const initialState = {
companyName: 'companyName',
companyDesc: 'companyDesc',
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_COMPANY_INFORMATIONS:
return {
...state,
companyName: action.payload.companyName,
};
default:
return state;
}
};
memberAction
const fetchInıtıalMember = async muuid => {
axios
.get(`/api/member/${muuid}`)
.then(response => {
const username = response.data.mname;
const isAdmin = response.data.misAdmin;
const companyId = response.data.cid;
store.dispatch({
type: FETCH_INITIAL_MEMBER,
payload: {
username,
isAdmin,
companyId,
},
});
})
.catch(error => {});
};
companyAction
const fetchCompanyInformations = () => {
store.dispatch({
type: FETCH_COMPANY_INFORMATIONS,
payload: { companyName: 'dispacthedCompanyName' },
});
};
Edit:
The code above is correct. My mistake is about importing the constants. This Redux implementation works well. I was storing all action type constant in a types.js file. I import this type constants in the another files wrongly. After changing it my problem is solved.
Right now I am mapping over an array with an endpoint to my API. From there I am taking every link and calling a get request on each thing I map over. My issue is that I am not able to save everything into my redux state. I have tried using concat and push to take everything and put it all in one array in my redux state.
MomentContent.js:
componentDidMount () {
this.props.photos.map(photo => {
this.props.fetchPhoto(this.props.token, photo)}
)
}
index.js (actions):
export const fetchPhoto = (token, photo) => dispatch => {
console.log('right token')
console.log(token);
fetch(photo, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Token ${token}`,
}
})
.then(res => res.json())
.then(parsedRes => {
console.log('photo data')
console.log(parsedRes)
dispatch(getPhoto(parsedRes))
})
}
export const getPhoto = (photo) => {
console.log('RES')
console.log(photo)
return {
type: GET_PHOTO,
photo: photo
}
}
When I use concat (reducer):
import {
GET_PHOTO
} from '../actions';
const initialState = {
photo: []
}
const photoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_PHOTO:
return {
...state,
photo: initialState.photo.concat([action.photo])
}
default:
return state;
}
}
export default photoReducer
When I use push (reducer):
import {
GET_PHOTO
} from '../actions';
const initialState = {
photo: []
}
const photoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_PHOTO:
return {
...state,
photo: initialState.photo.push([action.photo])
}
default:
return state;
}
}
export default photoReducer
UPDATE (another issue):
I was able to get it to work with :
return {
...state,
photo: [...state.photo, action.photo]
}
The issue now is that every time I refresh the same data is pushed again, so everything multiplies. Is there a way to fix this?
You need to merge your updatedState and not initialState to the reducer in order to update
Either using concat:
return {
...state,
photo: state.photo.concat([action.photo])
}
or using spread operator
return {
...state,
photo: [...state.photo, action.photo]
}
the push does not work correctly in redux, the ideal is to use the spread operator to concatenate the arrays
return {
... state,
photo: [... initialState.photo, action.photo]
}
If action.photo is an array, no need to wrap it with additional [].
If you want the newly fetched photo array to combined with existing photo array in the Redux state, use state.photo.push instead of initialState.photo.push.
case GET_PHOTO:
return {
...state,
photo: state.photo.push(action.photo)
}
Javascript push method on array return you the new size of the array and hence it won't work correctly.
what you need is to use concat or spread-syntax
case GET_PHOTO:
return {
...state,
photo: initialState.photo.concat([action.photo])
}
or
case GET_PHOTO:
return {
...state,
photo: [...initialState.photo, action.photo]
}