useSelector causes multiple re-renders - react-native

When using react-devtools it tells me that the reason my root component re-renderd was because hooks changed?
when I remove any useSelectors, my root component renders only once, when enabled it renders 6 times.
what are some guesses as to why this is happening?
import {
/// Data State Constants...
SET_USER_DATA,
SET_BADGE_COUNT,
} from "../Actions/gameState";
const initialState = {
/// Data state...
userData: [],
badgeCount: 0,
};
export default function gameState(state = initialState, action) {
const { type, payload } = action || {};
switch (type) {
/////////////////////////
/// Data state Reducers...
/////////////////////////
case SET_USER_DATA: {
return { ...state, userData: payload };
}
case SET_BADGE_COUNT: {
return { ...state, badgeCount: payload };
}
default:
return state;
}
}

Ok, the thing is: the useSelector compare the new value with the old one with a strict ===. You can either call one useSelector per field or implement the shallowEqual from react-redux:
const someState = useSelector(state=>state.myState.someState, shallowEqual)
Here the documentation:
https://react-redux.js.org/next/api/hooks#equality-comparisons-and-updates

Related

What does wrapping a variable in {} mean in a reducer function REACT NATIVE?

I am new to React native and I came across code for a reducer function but I am confused on why "token" is wrapped in brackets. Does it make token into a dynamic thing or something?
Can someone please explain why it is so? Thank you so much!
/** The reducer is in charge of updating the app state based on the dispatched action. **/
//Action Types
export const CREDENTIALED = 'auth/CREDENTIALED';
export const RESET_DATA = 'auth/RESET_DATA';
export const initialState = {
isLoading: true,
token: null,
};
//REDUCER
const authReducer = (state = initialState, action) => {
switch (action.type) {
case CREDENTIALED: {
let {token} = action;
return {...state, isLoading: true, token};
}
case RESET_DATA: {
return {...state, ...initialState};
}
default:
return state;
}
};
export default authReducer;
Not a react native expert by any stretch of the imagination, but it looks like token is just being destructured from action. Roughly the same as:
const dict = {
"alpha": 'a',
"beta": 'b',
}
const {alpha} = dict
console.log(alpha) // you should expect "a" to be printed out

React Redux reducer not returning

I have an application that has to return only future events. I am able to parse through my firebase realtime db and return only future events in my action. It then passes this array of data to my reducer. In my reducer, I am logging the payload. The console log displays the data I want. Now in my component screen, I console log to see what data I should see from the reducer and it is empty.
My action:
export const getAllEvents = () => {
var currentTime = Date.now();
return (dispatch) => {
var ref = firebase.database().ref('Events/');
ref.orderByChild("availableSpots").on("value", function(snapshot) {
snapshot.forEach(child => {
var time = child.val().epochTime;
if (currentTime < time) {
console.log("Events redux: ", child.val());
dispatch({ type: GET_EVENT_LIST, payload: child.val() });
}
})
});
});
}
}
As you can see I am console logging the child.val() which comes out as expected below:
Now I have this hooked up to my reducer via dispatch.
import { GET_EVENT_LIST } from '../actions/types';
const INITIAL_STATE = {
eventList: [],
};
const eventListReducer = (state = INITIAL_STATE, action) => {
switch (action.type){
case GET_EVENT_LIST:
console.log("action count", action.payload);
return { ...state, eventList: [...state.eventList, action.payload] };
default:
console.log("default ");
return state;
}
};
export default eventListReducer;
As you can see the console log of the action.payload is being logged and produces an expected result:
Now back in my component screen:
import { getUserThunk, getAllEvents } from '../actions';
import {connect} from 'react-redux';
class Home extends Component {
componentWillMount() {
console.log("Component will mount");
this.props.getUserThunk();
this.props.getAllEvents();
}
render() {
console.log("usersadf ", this.props.userReducer)
console.log("Props in home ", this.props.eventListReducer)
return (
//some return code here
);
}
export default connect(
state=>({userReducer: state.userReducer, eventListReducer: state.eventListReducer}),
{ getUserThunk, getAllEvents }
)(Home);
Now as you can see I do have different redux action and reducer hooked up and it comes through. However, the one in question returns empty.

Updating a reducer from another reducer in react-native

I want to update the state of one reducer from another reducer in react-native.My code is like this.
This is the action.
export const doLike = payload => {
// Recieving response from the server
let updatedPost = {
Id: 1,
userId: 1,
postId: payload.post._id,
__v: 1,
count: payload.type === 1 ? 10 : 1
};
return {
type: SM_ACTION_LIKE,
post: updatedPost
};
};
This is the smReducer which accepts the action.
const initialState = {
post: {}
};
const smReducer = (state = initialState, action) => {
switch (action.type) {
case SM_ACTION_LIKE:
return {
...state,
post: action.post
};
break;
default:
return state;
}
return state;
};
export default smReducer;
Now I want to change the posts array of mainFeedReducer from here. My mainFeedReducer is this. I want to access the posts array of mainFeedReducer from smReducer.
const initialState = Immutable({
posts: [],
featuredWorkouts: []
});
const mainfeedReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MAIN_FEED:
return {
...state,
posts: action.mainFeedData.posts,
featuredWorkouts: action.mainFeedData.featuredWorkouts
};
break;
default:
return state;
}
};
How can I achieve this?
Redux architecture revolves around a strict unidirectional data flow.
The correct way would be to design your reducers in such a way that they handle more data.
As mentioned in the docs
If a reducer needs to know data from another slice of state, the state tree shape may need to be reorganized so that a single reducer is handling more of the data.
Alternatives
You may consider using redux-thunk, since the inner
function, that recieves two parameters return (dispatch, getState),
has an access to the entire state map.
If you have an instance of your store object, then you can directly access at the states by doing store.getState()

Reducer not writing to Redux store

I have a connect()ed container component where I try to write a list of books to my redux store. The action creator is available as props and state is mapped to props, however the book list never get into the redux store. readingList is still null instead of bookArray (the passed argument of the action creator). Can someone spot the issue here? Included relevant snippets here:
import { setReadingList } from '../actions/index';
componentWillMount() {
this.props.setReadingList(bookArray);
}
function mapStateToProps(state) {
return {
readingList: state.readingList
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ setReadingList : setReadingList }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ReadingList);
/*-----------*/
./actions/index.js
export function setReadingList(readingList) {
return {
type : "SET_READINGLIST",
payload : readingList
};
}
/*-----------*/
./reducers/index.js
import { combineReducers } from 'redux';
import readingList from './reading_list';
export const rootReducer = combineReducers({
readingList
});
export default rootReducer;
/*-----------*/
./reducers/reading_list.js
export default function(state = null, action) {
switch (action.type) {
case "SET_READINGLIST":
return action.payload;
default:
return state;
}
}
Thanks!
Try calling setReadingList in componentDidMount instead of componentWillMount. Would also ensure bookArray is in scope.

How do I add an element to array in reducer of React native redux?

How do I add elements in my array arr[] of redux state in reducer?
I am doing this-
import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {
arr:[]
}
export default function userState(state = initialUserState, action)
{
console.log(arr);
switch (action.type)
{
case ADD_ITEM:
return {
...state,
arr: state.arr.push([action.newItem])
}
default:
return state
}
}
Two different options to add item to an array without mutation
case ADD_ITEM :
return {
...state,
arr: [...state.arr, action.newItem]
}
OR
case ADD_ITEM :
return {
...state,
arr: state.arr.concat(action.newItem)
}
push does not return the array, but the length of it (docs), so what you are doing is replacing the array with its length, losing the only reference to it that you had. Try this:
import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {
arr:[]
}
export default function userState(state = initialUserState, action){
console.log(arr);
switch (action.type){
case ADD_ITEM :
return {
...state,
arr:[...state.arr, action.newItem]
}
default:return state
}
}
If you need to insert into a specific position in the array, you can do this:
case ADD_ITEM :
return {
...state,
arr: [
...state.arr.slice(0, action.pos),
action.newItem,
...state.arr.slice(action.pos),
],
}
Since this question gets a lot of exposure:
If you are looking for the answer to this question, there is a good chance that you are following a very outdated Redux tutorial.
The official recommendation (since 2019) is to use the official Redux Toolkit to write modern Redux code.
Among other things, that will eliminate string action constants and generate action creators for you.
It will also employ methods that allow you to just write mutating logic in your Reducers created by createReducer or createSlice, so there is no need to write immutable code in Reducers in modern Redux in the first place.
Please follow the official Redux tutorials instead of third-party tutorials to always get the most up-to-date information on good Redux practices and will also show you how to use Redux Toolkit in different common scenarios.
For comparison, in modern Redux this would look like
const userSlice = createSlice({
name: "user",
initialState: {
arr:[]
},
reducers: {
// no ACTION_TYPES, this will internally create a type "user/addItem" that you will never use by hand. You will only see it in the devTools
addItem(state, action) {
// you can use mutable logic in createSlice reducers
state.arr.push(action.payload)
}
}
})
// autogenerated action creators
export const { addItem } = slice.actions;
// and export the final reducer
export default slice.reducer;
If you want to combine two arrays, one after another then you can use
//initial state
const initialState = {
array: [],
}
...
case ADD_ARRAY :
return {
...state,
array: [...state.array, ...action.newArr],
}
//if array = [1,2,3,4]
//and newArr = [5,6,7]
//then updated array will be -> [1,2,3,4,5,6,7]
...
This Spread operator (...) iterates array element and store inside the array [ ] or spreading element in the array, what you can simply do using "for loop" or with any other loop.
I have a sample
import * as types from '../../helpers/ActionTypes';
var initialState = {
changedValues: {}
};
const quickEdit = (state = initialState, action) => {
switch (action.type) {
case types.PRODUCT_QUICKEDIT:
{
const item = action.item;
const changedValues = {
...state.changedValues,
[item.id]: item,
};
return {
...state,
loading: true,
changedValues: changedValues,
};
}
default:
{
return state;
}
}
};
export default quickEdit;
The easiest solution to nested arrays is concat():
case ADD_ITEM:
state.array = state.array.concat(action.paylod)
return state
concat() spits out an updated array without mutating the state. Simply set the array to the output of concat() and return the state.
This worked for me
//Form side
const handleSubmit = (e) => {
e.preventDefault();
let Userdata = { ...userdata, id: uuidv4() };
dispatch(setData(Userdata));
};
//Reducer side
const initialState = {
data: [],
};
export const dataReducer = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.SET_DATA:
return { ...state, data: [...state.data, action.payload] };
default:
return state;
}
};