I faced a problem with React Redux when I need for one page render multiple lists of items from the same API. For example, I have endpoint /items with GET param /items?city=Berlin. I need to render 5 cities lists of items on the frontpage.
Currently I have 1 action to handle API call.
Here is my reducer.js:
...
const initialState = fromJS({
...
items: [],
...
})
export default function reducer (state = initialState, action) {
...
switch (action.type) {
case fetchItemsByCityRoutine.SUCCESS:
return state.update('items', (items) => items.push(...action.payload.data))
...
}
}
saga.js:
function * fetchItemsByCity ({ payload: city }) {
try {
yield put(fetchItemsByCityRoutine.request())
const response = yield call(apiClient.fetchItemsByCity, city)
response.city = city
yield put(fetchItemsByCityRoutine.success(response))
} catch (error) {
yield put(fetchItemsByCityRoutine.failure(extractErrorMessage(error)))
} finally {
yield put(fetchItemsByCityRoutine.fulfill())
}
}
function * watchItemsByCitySaga () {
yield takeEvery(fetchItemsByCityRoutine.TRIGGER, fetchItemsByCity)
}
On the Homepage I render list of cities like this:
const renderCityListSections = () => homepageCityLists.map((city) => <ItemList key={city.cityName} {...city} />)
ItemList component:
class ItemList extends Component {
componentDidMount () {
const { cityName } = this.props
this.props.fetchItemsByCityRoutine(cityName)
}
render () {
const { items, title } = this.props
return (
items.length > 0 &&
<Wrapper>
<SlickSlider>
{items.map((item) => <Item key={item.id} {...item} />)}
</SlickSlider>
</Wrapper>
)
}
}
THE PROBLEM is that current solution makes rerender view too many times because every time I fetch new list for some city it adds to items, so items changes and it leads to trigger rerendering view.
I had a thought to create 5 different Actions for every city, but it's not seems to be reasonable solution.
WHAT is the best approach to render multiple lists of cities on one page?
UPDATE:
So I changed reducer to look like this:
const initialState = fromJS({
...
items: {
Miami: [],
Prague: [],
Melbourne: [],
Venice: [],
Barcelona: [],
},
...
})
export default function reducer (state = initialState, action) {
switch (action.type) {
...
case fetchItemsByCityRoutine.SUCCESS:
return state.updateIn(['items', action.payload.city], (list) => (
list.concat(action.payload.data)
))
...
}
}
so every array is immutable, but again it runs into too much rerendering.
Everybody who has such a problem I ended up with this SOLUTION:
In my reducer I decoupled every item for city like this:
const initialState = fromJS({
...
itemsForBerlin: [],
itemsForAnotherCity: [],
...
})
export default function reducer (state = initialState, action) {
switch (action.type) {
...
case fetchItemsByCityRoutine.SUCCESS:
return state.set(`itemsFor${action.payload.city}`, action.payload.data)
...
}
}
Related
i got two values i.e.company and id from navigation.
let id = props.route.params.oved;
console.log("id-->",id);
let company = props.route.params.company;
console.log("company--->",company);
i got two values as a integer like this:--
id-->1
comapny-->465
Description of the image:---
if i am giving input 1 in that textInput and click on the card(lets say first card i.e.465 then i am getting those two values in navigation as in interger that i have mention above.so each time i am getting updated values.
i am getting updated values from navigation.
so i want to store those values in redux.
action.js:--
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (updatedCompany, updatedId) => {
return {
type: CHANGE_SELECTED_COMPANY,
updatedCompany,
updatedId,
};
};
reducer.js:--
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
return {
company: {
company: action.updatedCompany,
id: action.updatedId,
},
};
}
return state;
};
export default changeCompanyReducer;
congigure-store.js:--
import changeCompanyReducer from "./reducers/change-company-reducer";
const rootReducer = combineReducers({changeCompanyReducer});
How can i store the update values getting from navigation in Redux?
could you please write code for redux??
in the component create a function that updates the values
const updateReducer = () => {
dispatch(changeCompany(props.route.params.oved, props.route.params.company))
}
then call the function in react navigation lifecycle event
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
updateReducer()
});
return unsubscribe;
}, [navigation])
its possible that a better solution would be to update the reducer before the navigation happens and not pass the data in the params but rather pull it from redux but this is the answer to the question as asked
I have the following code (I deleted most of it to make it easier to understand - but everything works):
"role" reducer:
// some async thunks
const rolesSlice = createSlice({
name: "role",
initialState,
reducers: { // some reducers here },
extraReducers: {
// a bunch of extraReducers
[deleteRole.fulfilled]: (state, { payload }) => {
state.roles = state.roles.filter((role) => role._id !== payload.id);
state.loading = false;
state.hasErrors = false;
},
},
});
export const rolesSelector = (state) => state.roles;
export default rolesSlice.reducer;
"scene" reducer:
// some async thunks
const scenesSlice = createSlice({
name: "scene",
initialState,
reducers: {},
extraReducers: {
[fetchScenes.fulfilled]: (state, { payload }) => {
state.scenes = payload.map((scene) => scene);
state.loading = false;
state.scenesFetched = true;
}
}
export const scenesSelector = (state) => state.scenes;
export default scenesSlice.reducer;
a component with a button and a handleDelete function:
// a react functional component
function handleDelete(role) {
// some confirmation code
dispatch(deleteRole(role._id));
}
My scene model (and store state) looks like this:
[
{
number: 1,
roles: [role1, role2]
},
{
number: 2,
roles: [role3, role5]
}
]
What I am trying to achieve is, when a role gets deleted, the state.scenes gets updated (map over the scenes and filter out every occurrence of the deleted role).
So my question is, how can I update the state without calling two different actions from my component (which is the recommended way for that?)
Thanks in advance!
You can use the extraReducers property of createSlice to respond to actions which are defined in another slice, or which are defined outside of the slice as they are here.
You want to iterate over every scene and remove the deleted role from the roles array. If you simply replace every single array with its filtered version that's the easiest to write. But it will cause unnecessary re-renders because some of the arrays that you are replacing are unchanged. Instead we can use .findIndex() and .splice(), similar to this example.
extraReducers: {
[fetchScenes.fulfilled]: (state, { payload }) => { ... }
[deleteRole.fulfilled]: (state, { payload }) => {
state.scenes.forEach( scene => {
// find the index of the deleted role id in an array of ids
const i = scene.roles.findIndex( id => id === payload.id );
// if the array contains the deleted role
if ( i !== -1 ) {
// remove one element starting from that position
scene.roles.splice( i, 1 )
}
})
}
}
I have an issue with update new data by using react-redux, Add and remove are working fine, but it will return null object when i want to edit one of the data.
I am not sure what stage cause wrong.
action.users.js
import { v4 as uuid } from 'uuid';
// ADD_USER
export const addUser = ({ username = '', location = '' } = {}) => ({
type: 'ADD_USER',
user: { id: uuid(), username, location },
});
//REMOVE_USER
export const removeUser = ({ id } = {}) => ({ type: 'REMOVE_USER', id });
//EDIT_USER
export const editUser = ({ id, updates } = {}) => ({
type: 'EDIT_USER',
id,
updates,
});
components.EditUserPage.js
import React from 'react';
import { connect } from 'react-redux';
import UserForm from './UserForm';
import { editUser } from '../actions/users';
const EditUserPage = props => {
return (
<div>
<UserForm
user={props.user}
onSubmit={user => {
props.dispatch(editUser(props.user.id, user));
props.history.push('/Users');
}}
/>
</div>
);
};
const mapStateToProps = (state, props) => {
return {
user: state.user.find(user => user.id === props.match.params.id),
};
};
export default connect(mapStateToProps)(EditUserPage);
reducers.users.js
const usersReducerDefaultState = [];
export default (state = usersReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_USER':
return [...state, action.user];
case 'REMOVE_USER':
return state.filter(({ id }) => id !== action.id);
case 'EDIT_USER':
return state.map(user => {
if (user.id === action.id) {
return {
...user,
...action.updates,
};
} else {
return user;
}
});
default:
return state;
}
};
The page should go to ./edit when i click one of the data and the input value will show the currently selected data in the userform component. it seems like going well at this stage, i change the input value and click the create button, the page back to /User, unfortunately, the selected data return null object. please help me. you answer will help me to jump out of this nightmare.
Change
props.dispatch(editUser(props.user.id, user));
to
props.dispatch(editUser({id: props.user.id, updates: user}));
You define the method signature of edit user as
function editUser ({ id, updates } = {}) {…}
That is, a function that takes one (optional) argument. That argument is expected to be an object with an id and updates property. (I don't think you should make the argument optional. It is needed for the rest of the function to work.)
However, you call the function with two arguments, presumable a number and an object. Also, if props.user.id ends up being undefined, then it will be replaced with the default value of {}, and id and updates will be undefined, but no error will occur (which is what you want, because you are passing the wrong type to the function).
Alternatively, you could define the method signature to take to positional arguments and not change the function call:
const editUser = (id, updates) => (…)
or, if your UserForm component includes id in the call to onSubmit:
const editUser = ({ id, ...updates }) => (…) // use destructuring to select the id from the argument
// call it like this:
props.dispatch(editUser({user}));
I am newbie in react/redux and I try to do an app to manage my sentences.
I try to filter my sentences and it does it correct but when i delete some words from the input field, my state remains filtered.
this is the action.
export const filterWord = (e) => {
return {
type: 'FILTER_WORD',
e
}
}
this is the reducer:
case 'FILTER_WORD':
return state.filter((sentence) => sentence.word.includes(action.e.target.value));
Hope someone can give me an advice
The best way to handling filtering is not filter your reducer state based on the filter word but to do that in a selector
If you filter your state in a reducer, the next time you change a filter or remove it, you would not have the original state but a duplicate state
You can instead store the filterword in reducer
export const filterWord = (e) => {
return {
type: 'FILTER_WORD',
word: e.target.value,
}
}
const reducer = (state = { data: [], filterWord: null}) => {
...
case 'FILTER_WORD':
return {...state, filterWord: action.word};
...
}
Now in your mapStateToProps component you can filter the result for the user component
const mapStateToProps = (state) => {
return {
data: state.data.filter((sentence) => sentence.word.includes(state.filterWord));
}
}
I have array in redux. I am showing datas on Flatlist. However, When I edited array data , flatlist not re-render. How can i solve this problem? I checked my redux and is working fine
this.props.notes[this.state.Index]={
color: JSON.stringify(BgColor),
date: this.state.fullDate.toString(),
note: this.state.noteText,
name: this.state.noteName,
type: "note",
noteID:this.props.notes[this.state.Index].noteID
}
this.props.editNotes(this.props.notes);
Flatlist code;
<FlatList
ref={(list) => this.myFlatList = list}
data={this.props.notes}
showsVerticalScrollIndicator={false}
renderItem={({item, index})=>(
)}
removeClippedSubviews={true}
extraData={this.props.notes}
/>
mapStateToProps on same page with Flatlist
const mapStateToProps = (state) => {
const { notes } = state
return { notes }
};
Reducer
const notes = [];
const notesReducer = (state = notes, action) => {
switch (action.type) {
case 'editNotes':
return state = action.payload;
default:
return state
}
};
export default notesReducer;
The reason it's not updating is because you're not returning a new array. The reference is same.
Return the updated state like return [...state,action.payload]
The reason it's not updating the data correctly is because the mutation.
The problematic code is this part.
this.props.notes[this.state.Index]={
color: JSON.stringify(BgColor),
date: this.state.fullDate.toString(),
note: this.state.noteText,
name: this.state.noteName,
type: "note",
noteID:this.props.notes[this.state.Index].noteID
}
this.props.editNotes(this.props.notes);
It should be in this way
const { notes, editNotes } = this.props;
const newNotes = [...notes];
const { index } = this.state;
newNotes[index] = {
//update data
}
editNotes(newNotes);
You can fix the issue in many ways but the wrong part I see in your code is Reducer. As per the standard, your reducer should be a Pure Function and the state should not mutate.
const notes = [];
const notesReducer = (state = notes, action) => {
switch (action.type) {
case 'editNotes':
return {
...state,
...action.payload;
},
default:
return state
}
};
export default notesReducer;
This should resolve your issue.
Suggestion:
Try to create a nested hierarchy in redux like
const initialState = {
notes: [],
};
const notesReducer = (state = initialState, action) => {
switch (action.type) {
case 'editNotes':
return {
...state,
notes: [
...state.notes,
...action.payload.notes,
],
},
default:
return state
}
};
export default notesReducer;