I am following the docs to upsertMany into my redux createSlice.
I am following this part of the docs.
I keep getting this error in my upsertMany call. Why is this?
Unhandled Rejection (TypeError): Cannot use 'in' operator to search for '525' in undefined
I have noticed that normalizr returns both entities and result objects, however, RTK only uses entities. Where do we use the result if we need to at all?
Here is my createSlice
const posts = new schema.Entity('actionPosts', {}, { idAttribute: 'id' })
export const fetchPosts = createAsyncThunk(
'actionPosts/fetchPosts',
async (email) => {
const { data } = await getUserActionPostsByUser({ email })
const extractedPosts = data.userActionPostsByUser
const normalizedData = normalize(extractedPosts, [posts])
return normalizedData.entities
}
)
const adapter = createEntityAdapter({
sortComparer: (a, b) => b.createdAt.localeCompare(a.createdAt),
loading: '',
error: '',
})
const initialState = adapter.getInitialState()
const slice = createSlice({
name: 'actionPosts',
initialState,
extraReducers: {
[fetchPosts.fulfilled]: (state, { payload }) => {
console.log('payload', payload.actionPosts)
adapter.upsertMany(state, payload.actionPosts) // error happens here
},
},
})
export default slice.reducer
Here is the normalized object
{
actionPosts: {
525: {
id: 525
email: "test#test.com"
content: "lorem ipsum"
createdAt: "2020-09-24T20:29:44.848Z"
}
}
result[
525,
]
}
Related
Programming is weird, if you think not then check this case 🤣, I'm using createSlices as Redux and I have two slices with their own states.
First one is orderSlice:
export const orderSlice = createSlice({
name: 'order',
initialState: {
order: null,
message: null,
isLoading: true,
}
})
While the second slice is ordersSlice:
export const orderSlice = createSlice({
name: 'orders',
initialState: {
orders: null,
message: null,
isLoading: true,
}
})
And I have this method to fetch the order and the fulfilled phase where the state is set from:
Fetching the order:
export const fetchOrder = createAsyncThunk('', async ({ token, id }) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/view/${id}`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
Filling the order state:
extraReducers: {
[fetchOrder.fulfilled]: (state, action) => {
state.order = action.payload.data;
state.message = 'Succesfully fetched the Order.';
state.isLoading = false;
}
}
While here is method for fetching the orders:
export const fetchAllOrders = createAsyncThunk('', async (token) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/all`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
And here updating the orders state:
extraReducers: {
[fetchAllOrders.fulfilled]: (state, action) => {
state.orders = action.payload.data;
state.message = 'Succesfully fetched all Orders.';
state.isLoading = false;
}
}
So the case is that I'm calling the fetchAllOrders in the Order page with UseEffect, here is how:
import { fetchAllOrders } from '../redux/ordersSlice';
useEffect(() => dispatch(fetchAllOrders(user.token)), [user]);
So this is how i run the method to fetch orders with dispatch and it works. But the problem is that when I run this function beside the orders state that is filled with the same data, also the order state is filled with the same data and this is impossible as I've cheked all the cases where I could misstyped a user,users typo but there is none I found, and I don't know.
And here is the store:
import orderSlice from './redux/orderSlice';
import ordersSlice from './redux/ordersSlice';
const store = configureStore({
reducer: {
order: orderSlice,
orders: ordersSlice
},
});
You have to give your thunks an unique name: If you name both '' they will be handled interchangably.
Also, you should be using the builder notation for extraReducers. We will deprecate the object notation you are using soon.
I have super simple question
Why my redux state doesn't update immediately?
const { reducer, actions } = createSlice({
name: "professionals",
initialState: {
loading: false,
lastFetchList: undefined,
list: undefined,
professional: undefined,
filters: {
virtual: false
}
},
reducers: {
professionalsListRequested: (professionals, action) => {
if (action.payload.withLoading) professionals.loading = true;
},
professionalsListRequestFailed: (professionals, action) => {
professionals.loading = false;
},
professionalsListReceived: (professionals, action) => {
professionals.lastFetchList = Date.now();
professionals.list = action.payload.data.dataArr;
professionals.loading = false;
},
virtualUpdated: (categories, action) => {
categories.filters.virtual = action.payload;
}
},
});
export const { virtualUpdated } = actions;
export default reducer;
it is my slice.
and here is code of the component :
const dispatch = useDispatch();
const filters = useSelector((state) => state.professionals.filters);
const handlePressOnVirtual = async () => {
console.log("Before" , filters.virtual)
await dispatch(virtualUpdated(!filters.virtual));
console.log("after" , filters.virtual)
};
when handlePressOnVirtual function is called the both console.log(s) print previous value of the state.
When you are still in handlePressOnVirtual function, you are still in a closure, so all the references will still be your existing filters
So you would need to wait for another re-render for useSelector to invoke again then the new values will come.
One way to see the latest changes is to put your log inside a useEffect:
useEffect(() => {
console.log("after" , filters.virtual)
},[filters.virtual]);
i'm stuck while converting an old project to redux tool kit getting an "Unhandled Rejection (TypeError): state.push is not a function" error. i haven't got to grips with the action/thunk and reducer immutability yet. The alerts are working but then the error msg.
import axios from 'axios';
import { setAlert } from '../alerts/alertSlice';
const slice = createSlice({
name: 'auth',
initialState: {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null,
},
reducers: {
registerSuccess: (state, action) => {
const { payload } = action.payload;
state.push({
payload,
isAuthenticated: true,
loading: false,
});
},
registerFail: (state, action) => {
localStorage.removeItem('token');
state.push({
token: null,
isAuthenticated: false,
loading: false,
user: null,
});
},
},
});
const { registerSuccess, registerFail } = slice.actions;
export default slice.reducer;
// Register User
export const register =
({ name, email, password }) =>
async (dispatch) => {
const config = {
headers: {
'comtent-Type': 'application/json',
},
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post('/api/users', body, config);
dispatch({
type: registerSuccess,
payload: res.data,
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch(registerFail());
}
};
.push is an array function to add a new item at the end of an array - your state is not an array.
You probably wanted to do something along the lines of
state.token = null
state.isAuthenticated = false
state.loading = false
state.user = null
Running into an infinite loop when I try to dispatch an action which grabs all recent posts from state.
I have tried the following in useEffect dependency array
Object.values(statePosts)
useDeepCompare(statePosts)
passing dispatch
omitting dispatch
omitting statePosts
passing statePosts
doing the same thing in useCallback
a lot of the suggestions came from here
I have verified that data correctly updates in my redux store.
I have no idea why this is still happening
my component
const dispatch = useDispatch()
const { user } = useSelector((state) => state.user)
const { logs: statePosts } = useSelector((state) => state.actionPosts)
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
actionPosts createSlice
const slice = createSlice({
name: 'actionPosts',
initialState: {
posts: [],
},
reducers: {
postsLoading: (state, { payload }) => {
if (state.loading === 'idle') {
state.loading = 'pending'
}
},
postsReceived: (state, { payload }) => {
state.posts = payload
},
},
})
export default slice.reducer
const { postsReceived, postsLoading } = slice.actions
export const getActionPostsRest = (email) => async (dispatch) => {
try {
dispatch(postsLoading())
const { data } = await getUserActionPostsByUser({ email })
dispatch(postsReceived(data.userActionPostsByUser))
return data.userActionPostsByUser
} catch (error) {
throw new Error(error.message)
}
}
Remove dispatch from dependencies.
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
you cannot use hook as dependency and by the way, ref.current, is always undefined here
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
because useDeepCompare essentially is just a function that you initiate (together with ref) on each call, all it does is just returns value. That's where the loop starts.
In the docs for testing incrementing todo ids, this assumes a predictable response.
In an example such as below, a unique id is generated.
How could this be tested?
This test passes, but I'm not sure if it's correct, shouldn't the id be defined based on what's in the prepare callback?
slice.js
add: {
reducer: (state, {payload}: PayloadAction<{id: string, item: Item}>) => {
state[payload.id] = payload.item
},
prepare: (item: Item) => ({
payload: {id: cuid(), item}
})
}
slice.test.js
it('should handle add', () => {
expect(
reducer(
{},
{
type: actions.add,
payload: {
id: 'id-here?',
item: {
other: 'properties...'
}
},
}
)
).toEqual({
'id-here?': {
other: 'properties...'
},
})
})
You can pull out the prepare function and also the reducer function into it's own constant and then test prepare in isolation:
todosSlice.js:
[...]
let nextTodoId = 0;
export const addTodoPrepare = (text) => {
return {
payload: {
text,
id: nextTodoId++
}
}
}
export const addTodoReducer = (state,
action) => {
const {id, text} = action.payload;
state.push({
id,
text,
completed: false
});
};
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: {
prepare: addTodoPrepare,
reducer: addTodoReducer,
},
}
})
[...]
todosSlice.spec.js:
import todos, {addTodo, addTodoPrepare} from './todosSlice'
describe('addTodoPrepare',
() => {
it('should generate incrementing IDs',
() => {
const action1 = addTodoPrepare('a');
const action2 = addTodoPrepare('b');
expect(action1.payload).toEqual({
id: 0,
text: 'a'
})
expect(action2.payload).toEqual({
id: 1,
text: 'b'
})
})
})
describe('todos reducer',
() => {
[...]
})
For unit testing, NO, just test each reducer independently.
For integration testing and e2e testing, Yes.