I have a store with
const initialState = {
users: dummyData,
is_created: false
};
where users is an array. Then a getUsers selector const getUsers = state => state.users;
Inside the component I used the useSelector to get access to my state.
const { users } = useSelector(state => ({
users: getUsers(state),
}));
When I try to loop through users
for (let i = 0; i < users.length; i++) {
//...
}
I get an error Cannot read property 'length' of undefined
useSelector seems to run provided function at arbitrary time so its no guaranteed to have users defined when you want because of the async nature of it.
Couple ways you can tackle this:
First define default for users like:
const { users = [] } = useSelector(state => ({
users: getUsers(state),
}));
That should resolve your issue.
Enhancement to your above code using for loop would be not to rely on for loop instead use .map like below:
users.map(user => { ... });
I found the error from the redux dev tools chart. I am using combineReducers() and I named the reducer after the users the component name ie
export default combineReducers({
users: users.reducers,
});
So the correct selector would be
const getUsers = state => state['users'].users;
Related
I am relatively new to Redux.
I have the following action -
import { createSlice } from "#reduxjs/toolkit";
export const currentListSlice = createSlice({
name: "currentList",
initialState: [1, 2, 3],
reducers: {
setCurrentList: (state, action) => {
return action.payload;
},
},
});
export const { setCurrentList } = currentListSlice.actions;
export default currentListSlice.reducer;
The initialState:[1,2,3] is for development only - The initialState will be empty and will be updated with a function that will use data from firebase.
Next, I'm calling the array and rendering a card from each object, using the numbers in the state to get the rest of the info from the data objects.
const preList = useSelector((state) => state.currentList);
const list = [];
preList.map((item) => list.push(DATA[item]));
list is being used as the data in a Flatlist.
So far so good.
Then I am calling the function to update currentList on redux like this -
useEffect(() => {
dispatch(setCurrentList(fiveRandomBeaches(DATA.length)));
}, [preList]);
I am going into an infinite loop.
To test, I added a button and called the dispatch with onPress event and the function works just fine, so no issues with the function.
dispatch(setCurrentList(fiveRandomBeaches(DATA.length)))
How do I call the dispatch function here without getting the infinite loop?
Thanks
I really don't think the fiveRandomBeaches function is relevant to the issue but here goes
export const fiveRandomBeaches = (dataLength) => {
let beachesList = [];
let uniqueBeachesList;
let rndBeach;
do {
rndMarker = Math.floor(Math.random() * dataLength);
beachesList.push(rndMarker);
uniqueBeachesList = [...new Set(beachesList)];
} while (uniqueBeachesList.length < 5);
return uniqueBeachesList;
};
Maxbe just a point on the syntax, since your problem god solved by someone else:
const list = [];
preList.map((item) => list.push(DATA[item]));
map() function is already returning an array, you can just save time by making use of it. Therefore try this instead of the above code:
const list = preList.map((item) => DATA[item]);
Your useEffect has a dependency on preList and that is causing the infinite loop. setCurrentList will update it and cause the effect to run again ad infinitum.
Removing preList from the dependency array should solve your problem.
I want to render the fullName string from response from the API call that I did in redux-thunk, and put it in useEffect with dispatch (useDispatch() hook). If I return nothing in JSX and if I console.log the state it is actually passes, and I can see the whole data in the console. But if I want to render any part of the data, I get an error:
Uncaught TypeError: Cannot read properties of null (reading 'fullName').
const Profile = (props) => {
let { userId } = useParams();
const dispatch = useDispatch();
const state = useSelector((state) => state);
useEffect(() => {
dispatch(getUserProfile(userId));
}, []);
return <div>
{state.profile.profile.fullName} //error if i want smth to render, two profile because one from redux's initialState, and one from api.
</div>;
};
export default Profile;
THUNK
export const getUserProfile = (userId) => (dispatch) => {
profileAPI.getProfile(userId).then((response) => {
debugger
dispatch(setProfile(response));
});
};
I tried conditional rendering, but it didnt get me the solution. Ive tried a lot ways of conditional rendering, and no one of them was helpful
Can you Please share Profile Reducer Code also because as per my observation Profile is like an object
export const initialState = {
profile:{fullName:""};
}
export const profileReducer =(state = initialState,action={})=>{
..
}
and you want to get only profileReducer Data then get only profileReducer data by state.profileReducer an get the value as const {profile}
const {profile} = useSelector((state) => state.profileReducer);
for testing check if are you able to getting proflie data by
useState(()=>{console.log("profile",profile)},[profile])
I'm working on a project that use Redux toolkit in typescript language.
The problem is that when I use createSelector it returns wrong state object in some of the selectors, not all of them. And it will work, if I use store.getState() instead of returned state. Let me describe more details.
My state includes one reducer which includes two reducers that combined using combineReducers, as shows below.
const entities = combineReducers({
trello: entityTrello,
users: entityUser,
})
export function makeStore() {
return configureStore({
reducer: {
entities: entities,
},
devTools: process.env.NEXT_PUBLIC_PROJECT_STATUS == "develop",
})
}
trello reducer contains boards and their cards.
users reducer contains list of users.
all things go right and after saving data in store I got below state tree
Then I try to get data from state using createSelector.
export const selectLanesByBoardId = () => {
return useAppSelector<BoardData>(selector => createSelector(
(state: AppState) => state.entities.trello.tables.data?.boards,
(state: AppState) => state.entities.trello.tables.data?.lanes,
(state: AppState) => state.entities.trello.tables.data?.cards,
(state: AppState) => state.entities.trello.meta.boardId,
(state: AppState) => state.entities.trello.meta.workspace,
(boards, lists, cards, boardId, workspace) => {
let data: Array<Lane>
// because we have key-value array, first we change it to simple array using "objectValues"
// then find lanes of current board by "boardId" which sent from component (it's ids of lanes, not whole object)
const laneIds = objectValues<TrelloBoard>(boards)?.find(value => value.id == boardId)?.lists
// now with using "laneIds" search for specific lane objects
const lanes = objectValues<TrelloLanes>(lists)?.filter(value => laneIds?.includes(value.id))
// any lane has it's own cards that not implemented yet, so update cards field using "map" on array of lanes
data = lanes.map(lane => ({
...lane,
cards: objectValues<TrelloCard>(cards)
?.filter(value => lane?.cards?.includes(value.id))
.map(value => ({
...value,
draggable: workspace == "free",
laneId: lane.id
}))
}))
return {lanes: data} as BoardData
}
)(selector))
}
It goes right again, and I got correct data.
But in another createSelector the returned state is not the root state. please check below code.
export const selectUsers = () => {
return useAppSelector<Array<User>>(selector => createSelector(
(state: AppState) => {
return state.entities.users.tables.data?.users
},
(users) => {
return users ?? []
}
)(selector))
}
In this part of code I get an error which shows below.
As I said, it will work if I use store.getState() instead of state. Here is the code
export const selectUsers = () => {
return useAppSelector<Array<User>>(selector => createSelector(
(state: AppState) => {
return store.getState().entities.users.tables.data?.users
},
(users) => {
return users ?? []
}
)(selector))
}
Last, my question is why createSelector is not returning the root state in some part of the code, but store.getState() does. Any help, please?
update
As the shared image of the error shows, it says that entities object is undefined, not users. So I figured out that the root state is changing as #GabrielePetrioli noted in a comment, by adding a new console.log in createSelector of users, the state shows below data in the console log.
it's showing a new object instead of my store structure.
But how may the root state entity changes? I even has no access to entities object in my reducers.
below code is one of updating samples of the state.
state.tables.data && (state.tables.data = {
...state.tables.data,
lanes: state.utils.nextStateAfterLastAction?.lanes ?? [],
cards: state.utils.nextStateAfterLastAction?.cards ?? [],
})
as you can see, I just have access to the tables object, not the entities object.
every reducer updates its own state, not the root structure of the state!
I'm writing tests for React using react-testing-library and jest and are having some problems figuring out how to set a preloadedState for my redux store, when another file imports the store.
I Have a function to set up my store like this
store.ts
export const history = createBrowserHistory()
export const makeStore = (initalState?: any) => configureStore({
reducer: createRootReducer(history),
preloadedState: initalState
})
export const store = makeStore()
and another js file like this
utils.ts
import store from 'state/store'
const userIsDefined = () => {
const state = store.getState()
if (state.user === undefined) return false
...
return true
}
I then have a test that looks something like this:
utils.test.tsx (the renderWithProvider is basically a render function that also wraps my component in a Render component, see: https://redux.js.org/recipes/writing-tests#connected-components)
describe("Test", () => {
it("Runs the function when user is defined", async () => {
const store = makeStore({ user: { id_token: '1' } })
const { container } = renderWithProvider(
<SomeComponent></SomeComponent>,
store
);
})
})
And the <SomeComponent> in turn calls the function in utils.ts
SomeComponent.tsx
const SomeComponent = () => {
if (userIsDefined() === false) return (<Forbidden/>)
...
}
Now.. What happens when the test is run seem to be like this.
utils.ts is read and reads the line import store from 'state/store', this creates and saves a store variable where the user has not yet been defined.
the utils.test.tsx is called and runs the code that calls const store = makeStore({ user: { id_token: '1' } }).
The renderWithProvider() renderes SomeComponent which in turn calls the userIsDefined function.
The if (state.user === undefined) returns false because state.user is still undefined, I think that's because utils.ts has imported the store as it were before I called my own makeStore({ user: { id_token: '1' } })?
The answer I want:
I want to make sure that when call makeStore() again it updates the previously imported version of store that is being used in utils.ts. Is there a way to to this without having to use useSelector() and pass the user value from the component to my utils function?
e.g I could do something like this, but I'd rather not since I have a lot more of these files and functions, and rely much on import store from 'state/store':
SomeComponent.tsx
const SomeComponent = () => {
const user = useSelector((state: IState) => state.user)
if (userIsDefined(user) === false) return (<Forbidden/>)
...
}
utils.ts
//import store from 'state/store'
const userIsDefined = (user) => {
//const state = store.getState()
if (user === undefined) return false
...
return true
}
As I said above I'd prefer not to do it this way.
(btw I can't seem to create a fiddle for this as I don't know how to do that for tests and with this use case)
Thank you for any help or a push in the right direction.
This is just uncovering a bug in your application: you are using direct access to store inside a react component, which is something you should never do. Your component will not rerender when that state changes and get out of sync.
If you really want a helper like that, make a custom hook out of it:
import store from 'state/store'
const useUserIsDefined = () => {
const user = useSelector(state => state.user)
if (user === undefined) return false
...
return true
}
That way your helper does not need direct access to store and the component will rerender correctly when that store value changes.
I am trying to follow this code in redux-saga
export const getUser = (state, login) => state.entities.users[login]
export const getRepo = (state, fullName) => state.entities.repos[fullName]
Which is then used in the saga like this:
import { getUser } from '../reducers/selectors'
// load user unless it is cached
function* loadUser(login, requiredFields) {
const user = yield select(getUser, login)
if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) {
yield call(fetchUser, login)
}
}
This getUser reducer (is it even a reducer) looks very different from what I would normally expect a reducer to look like.
Can anyone explain what a selector is and how getUser is a reducer and how it fits in with redux-saga?
getUser is not a reducer, it is indeed a selector, that is, a function that knows how to extract a specific piece of data from the store.
Selectors provide an additional layer such that if you altered your store structure and all of a sudden your users were no longer at state.entities.users but instead at state.users.objects.entities (or whatever) then you only need to update the getUser selector and not every place in your app where you were making a reference to the old location.
That makes them particularly handy when it comes to refactoring your Redux store.
Selectors are getters for the redux state. Like getters, selectors encapsulate the structure of the state, and are reusable. Selectors can also compute derived properties.
You can write selectors, such as the ones you saw in redux-saga. For example:
const getUsersNumber = ({ users }) => users.length;
const getUsersIds = ({ users }) => users.map(({ id }) => id);
etc...
You can also use reselect, which is a simple “selector” library for Redux, that memoize selectors to make them more efficient.
Selectors are functions that take Redux state as an argument and return some data to pass to the component.
const getUserData = state => state.user.data;
Why should it be used?
One of the main reasons is to avoid duplicated data in Redux.
Your data object shape keeps varying as your application grows, so rather than making changes in all the related component.It is much recommended/easier to change the data at one place.
Selectors should be near reducers because they operate on the same state. It is easier for data to keep in sync.
Using reselect helps to memoize data meaning when the same input is passed to the function, returns the previous result rather than recalculating again.So, this enhances your application performance.
function mapStateToProps (state) {
return {
user: state.user,
}
}
initialState of reducer by user store
const initialState = {
isAdmin:false,
isAuth:false,
access:[1,2,5]
};
class AppComp extends React.Component{
render(){
const {user: { access:access}} = this.props;
const rand = Math.floor(Math.random()*4000)
return (<div>
{`APP ${rand} `}
<input type="button" defaultValue="change auth" onClick={this.onChangeUserAuth} />
<p>TOTAL STATUS COUNT IS {access.length}</p>
</div>)
}
}}
but you can use selector
var getUser = function(state) {
return state.user
}
const getAuthProp = createSelector(
getUser,
(user) => user.access
);
function mapStateToProps (state) {
return {
// user: state.user,
access: getAuthProp(state)
}
}
Main Problem is this component use all user: state.user and any changes in user (etc isAdmin ,isAuth, access) runs rerender this component which need only part of this store - access!!!
In Redux, whenever an action is called anywhere in the application,
all mounted & connected components call their mapStateToProps
function. This is why Reselect is awesome. It will just return the
memoized result if nothing has changed.
In the real world, you will most likely need the same certain part of
your state object in multiple components.
https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
The createSelector function provided by Reselect implements the most basic way to derive a selector from previous selectors. The simplest use case is to derive a selector from a single other selector. In this case, the parameters to createSelector are the input selector and a function transforming the result of that selector into the result of the new selector. For example
var getProducts = function(state) {
return state.products
}
import {getProducts} from '../app/selectors'
import {createSelector} from 'reselect'
export const getProductTitles = createSelector(
getProducts,
(products) => products.map((product) => product.get('title'))
)
This is equivalent to (ignoring memoization):
import {getProducts} from '../app/selectors'
export const getProductTitles = (state) => {
return getProducts(state).map((product) => product.get('title'))
}
The createSelector function can combine data from multiple selectors as well as from a single selector. We can pass any number of selectors to createSelector, and their results will be passed to the function passed as the final argument. For a (somewhat contrived) example:
const isInCheckout = createSelector(
getIsShippingPage,
getIsBillingPage,
getIsConfirmationPage,
(isShipping, isBilling, isConfirmation) =>
isShipping || isBilling || isConfirmation
)
is equivalent to
const isInCheckout = (state) => {
return (
getIsShippingPage(state) ||
getIsBilingPage(state) ||
getIsConfirmationPage(state)
)
}
common pattern when writing mapStateToProps functions with selectors is to return an object with each key storing the result of a particular selector. The createStructuredSelector helper function in Reselect lets us write this pattern with the minimum of boilerplate. For example, if we writ
const mapStateToProps = createStructuredSelector({
title: getProductTitle,
price: getProductPrice,
image: getProductImage
})
it is equivalent to
const mapStateToProps = (state) => {
return {
title: getProductTitle(state),
price: getProductPrice(state),
image: getProductImage(state)
}
}
https://docs.mobify.com/progressive-web/0.15.0/guides/reselect/