Have to press twice to delete item in redux-toolkit - javascript

I'm trying to delete item in redux toolkit, but don't know how, the remove function only work on screen, i have to press twice to delete the previous one,
Here is the reducer
const noteReducer = createSlice({
name: "note",
initialState: NoteList,
reducers: {
addNote: (state, action: PayloadAction<NoteI>) => {
const newNote: NoteI = {
id: new Date(),
header: action.payload.header,
note: action.payload.note,
date: new Date(),
selectStatus: false,
};
state.push(newNote);
},
removeNote: (state, action: PayloadAction<NoteI>) => { //
======> Problem here
return state.filter((item) => item.id !== action.payload.id);
},
toggleSelect: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return { ...item, selectStatus: !item.selectStatus };
}
return item;
});
},
loadDefault: (state) => {
return state.map((item) => {
return { ...item, selectStatus: false };
});
},
resetNote: (state) => {
return (state = []);
},
editNote: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return {
...item,
note: action.payload.note,
header: action.payload.header,
date: action.payload.date,
};
}
return item;
});
},
},
extraReducers: (builder) => {
builder.addCase(fetchNote.fulfilled, (state, action) => {
state = [];
return state.concat(action.payload);
});
},
});
Here is the function where i use it:
export default function NoteList(props: noteListI) {
const { title, note, id, date } = props;
const data = useSelector((state: RootState) => state.persistedReducer.note);
const removeSelectedNote = () => {
dispatch(removeNote({ id: id }));
console.log(data); ====> still log 4 if i have 4
};
return (
<View>
<TouchableOpacity
onLongPress={() => {
removeSelectedNote();
}}
// flex
style={CONTAINER}
onPress={() =>
!toggleSelectedButton ? onNavDetail() : setEnableToggle()
}
>
<Note
note={note}
header={title}
date={date}
id={id}
selectedStatus={selectedButtonStatus}
/>
</TouchableOpacity>
</View>
);
}
I have to press twice to make it work, for example, i have 4 item, when i press one, the item on screen disappears but the data log still have 4 item, when i click another, it show 3 on console.log but the screen display 2, i mean the function maybe work correctly but i want to update the state also, how can i do that?
Or how can i update the state if i remove item in redux-toolkit?
When i log the data on the redux, it return correct: 3
Here is a gif to show what going on
UPDATED
As #Janik suggest, i use console.log in function, so it log correct
But how can i get this change? I mean, it log correct, but i was fetch data from firebase so i need to log this data to make change to firebase, so how can i do that, i try to put it in a function:
const getNote = useCallback(() => {
setCurrentNote(data);
}, [data]);
But it show this error:
ExceptionsManager.js:184 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

Where is your logged data coming from?
I suppose this is just a matter of order and timing, when your log happens within the React Lifecycle „Update“.
If data references your state:
component is rendered initially, data is 4.
Note removed, still in the same rendering state, therefore data still is 4
React re-renders your component, data is 3.
To check on this, you can try changing the order by moving the console.log outside of the removeSelectedNote. This way, log will happen on step 1 and 3 instead of 2

Related

How to disable an active item after dispatch?

I would like to create notifications that expire after a set amount of seconds.
I have created a property which is 'active' and when toggled to false it will hide.
Ideally, it would be nice to have the expiry automatically set in the slice, i.e. run the disable reducer within the runtime of the notify reducer but i'm not sure this is good practice, and am not sure how to pull it off.
What is the best way to pull this off? I was thinking of adding an expiry date on each item but since the 'active' field is already there I would like to set a timeout and toggle it to false after 3 seconds..
Notification component:
export function Notification() {
const dispatch = useDispatch();
function disableAlert(id: number) {
dispatch(disable({'id' : id}));
}
const notification_list = useSelector(getNotification);
if (notification_list && notification_list.length > 0) {
return notification_list.map((notification: any, index: number) =>
notification.active ?
<Alert onClose={() => disableAlert(index)} style={{bottom: 50 * index}} severity={notification.mode}>{notification.message}</Alert> :
console.log(notification)
)
}
return <></>
}
Currently I have these slices:
const disableMessage = (state: any, message_id: number) => {
return state.messages.map((message:any) => message.id === message_id ?
{...message, active: !message.active} :
message
);
}
export const notificationSlice = createSlice({
name: 'notification',
initialState: initialState,
reducers: {
notify: (state, action) => {
const { message, mode, active } = action.payload;
state.messages.push({id: state.messages.length , message : message, mode: mode, active: active});
},
disable: (state, action) => {
const { id } = action.payload;
state.messages = disableMessage(state, id);
}
}
})
It is convention that reducers never contain any type of logic. I recommend to stick with this.
This leaves either the action or the Notification component. For me it makes more sense to tie the disable to the rendering of the individual notification so I would start the timeout there.
Ideally, you can split your <Alert/> component into the presentation and logic. Something similar to:
const NotificationAlert = ({ disableAlert, id }) => {
const notification = useSelector((state) => selectNotificationById(state, id));
const handleClick = useCallback(() => {
disableAlert(id);
}, [disableAlert, id]);
useEffect(() => {
setTimeout(() => disableAlert(id), 3000);
}, [disableAlert]);
return (
<Alert
onClose={handleClick}
style={{bottom: 50 * id}}
severity={notification.mode}>{notification.message}</Alert>
};
And
export function Notification() {
const dispatch = useDispatch();
// memoize handler with useCallback
const disableAlert = useCallback((id: number) => {
dispatch(disable({'id' : id}));
}, [dispatch]);
// Filter for active notifications already in your selector
const notificationIds = useSelector(getActiveNotificationIds);
return notificationIds.map((id) =>
<NotificationAlert disableAlert={disableAlert} id={id} />
);
}
Also, make sure your disableAlert action is setting active to false rather than toggling it!

How to get updated redux-toolkit state when component is not re-render

I'm trying to delete item in redux toolkit, but don't know how, the remove function only work on screen, i have to press twice to delete the previous one
Here is the reducer
const noteReducer = createSlice({
name: "note",
initialState: NoteList,
reducers: {
addNote: (state, action: PayloadAction<NoteI>) => {
const newNote: NoteI = {
id: new Date(),
header: action.payload.header,
note: action.payload.note,
date: new Date(),
selectStatus: false,
};
state.push(newNote);
},
removeNote: (state, action: PayloadAction<NoteI>) => { //
======> Problem here
return state.filter((item) => item.id !== action.payload.id);
},
toggleSelect: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return { ...item, selectStatus: !item.selectStatus };
}
return item;
});
},
loadDefault: (state) => {
return state.map((item) => {
return { ...item, selectStatus: false };
});
},
resetNote: (state) => {
return (state = []);
},
editNote: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return {
...item,
note: action.payload.note,
header: action.payload.header,
date: action.payload.date,
};
}
return item;
});
},
},
extraReducers: (builder) => {
builder.addCase(fetchNote.fulfilled, (state, action) => {
state = [];
return state.concat(action.payload);
});
},
});
Here is the function where i use it:
CODE UPDATED
export default function NoteList(props: noteListI) {
const { title, note, id, date } = props;
const data = useSelector((state: RootState) => state.persistedReducer.note);
useEffect(() => {
currentDate.current = data;
}, [data]);
const removeSelectedNote = () => {
dispatch(removeNote({ id: id }));
console.log(data); ====> still log 4 if i have 4
};
console.log(data); // ====> work if i log here but a lots of logs
return (
<View>
<TouchableOpacity
onLongPress={() => {
removeSelectedNote();
console.log("current", currentDate.current); ///same
}}
// flex
style={CONTAINER}
onPress={() =>
!toggleSelectedButton ? onNavDetail() : setEnableToggle()
}
>
<Note
note={note}
header={title}
date={date}
id={id}
selectedStatus={selectedButtonStatus}
/>
</TouchableOpacity>
</View>
);
}
I have to press twice to make it work, for example, i have 4 item, when i press one, the item on screen disappears but the data log still have 4 item, when i click another, it show 3 on console.log but the screen display 2, the redux state is change outside the return() but i can't capture the updated state, it work the previous one
Here is a gif to show what going on
When i press only one item, it change on UI but when i refresh it return same state
When i click twice or more, it make changes to previous
Updated
The redux-persist code:
const reducer = combineReducers({
note: noteReducer,
firebase: authentication,
});
const persistConfig = {
key: "root",
storage: AsyncStorage,
blacklist: [],
};
const persistedReducer = persistReducer(persistConfig, reducer);
const store = configureStore({
reducer: { persistedReducer, toggle: toggleReducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const persistStorageNote = persistStore(store);
I also added the useEffect by this, but problem is when i log the changes in function, it remain the same:
here is how you can log updated data correctly, as state update is asynchronous it doesn’t change immediately when you dispatch removeNote
export default function NoteList(props: noteListI) {
const { title, note, id, date } = props;
const data = useSelector((state: RootState) => state.persistedReducer.note);
// log changed data
useEffect(() => {
console.log(data);
}, [data]);
const removeSelectedNote = () => {
dispatch(removeNote({ id: id }));
};
return (
<View>
<TouchableOpacity
onLongPress={() => {
removeSelectedNote();
}}
// flex
style={CONTAINER}
onPress={() =>
!toggleSelectedButton ? onNavDetail() : setEnableToggle()
}
>
<Note
note={note}
header={title}
date={date}
id={id}
selectedStatus={selectedButtonStatus}
/>
</TouchableOpacity>
</View>
);
}
about reloading issue, try to close the app and open it like a user of your app would (minimize the app -> remove the app from recently opened apps -> open app again ) , instead of reloading the project.

Mapped data is not shown on screen in React Native

I am getting a response from an API:
{
"data": {
// other stuff
"time_breakup": {
"break_timings": [
{
"break_in_time": "2021-11-18T05:32:35.747Z",
"break_out_time": "2021-11-18T05:32:47.871Z"
},
{
"break_in_time": "2021-11-18T06:21:35.740Z",
"break_out_time": "2021-11-18T06:21:39.909Z"
}
],
},
},
"success": true
}
I am using the below function to get this response:
const [shift, setShift]: any = useState();
const getShiftDetails = useCallback(() => {
ApiFunctions.get('shift/' + ID)
.then(async resp => {
if (resp) {
setShift(resp.data); // saving the response in state
// some work
} else {
Alert.alert('Error', resp);
}
})
.catch((err: any) => {
console.log(err);
});
}, []);
useEffect(() => {
getShiftDetails();
}, [getShiftDetails, ID]);
So, I have saved the response in a state shift. Now I want to map this state to display the time on screen:
<View>
{shift.time_breakup.break_timings.map((item: any, index: any) => {
console.log(item.break_in_time),
<>
<View>
<Text>{item.break_in_time}</Text>
<Text>{item.break_out_time}</Text>
</View>
</>;
})}
</View>
However, I am not able to see <Text>{item.break_in_time}</Text> on screen; and also, in the console, I am getting an infinite loop of time:
console.log:
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
...
I don't know what I am doing wrong.
Try adding the ID inside the getShiftDetails useCallback dependencies array.
const getShiftDetails = useCallback(() => {...}, [ID]) I believe this is what is causing the infinite loop
Put the console.log before returning the view from the map function:
<View>
{shift.time_breakup.break_timings.map((item: any, index: any) => {
console.log(item.break_in_time);
return (
<View>
<Text>{item.break_in_time}</Text>
<Text>{item.break_out_time}</Text>
</View>
);
})}
</View>
You get infinite loop because on each render your function getShiftDetails gets redefined, React creates a shallow object of it on each render cycle, you can use useCallback to memoize it and Declare ID as dependency array.

infinite loop while using useEffect and redux store

I have a component which renders tabs based on an array in redux store and also updates the store based on the user click on tab. But it is causing infinite loop when I click the tab.
function TabsList() {
const reportNamesStore = useSelector(getStoreTabs);
const [reportNames, setReportNames] = useState([]);
const handleClick = (index) => {
dispatch(tabClicked({ active: index }))
setReportNames([...reportNamesStore]);
};
useEffect(() => {
setReportNames([...reportNamesStore]);
}, [reportNamesStore]);
return (
...
<a onClick={() => handleClick(index)}/>
)
enter code here
And my reducer is as below
getTabsReducer(
state = { active: -1, tabs: [] },
...
case TAB_CLICKED:
return {
tabs: [...state],
active: action.payload.active,
};

React Redux Connect MapState not updating for a filtered item from a collection

I think I've either misunderstood something, or am doing something deeply wrong, when attempting to subscribe to changes on a specific item in a collection in my store. Unless I add a direct list subscription, my component does not receive updates.
The following works:
const mapStateToProps = (state, props) => ({
list: state.podcasts.items,
getItem: props.id
? state.podcasts.items.filter(item => item.clientId === props.id)[0] || {}
: {},
});
If I remove the list item I only receive the the initial state of the collection item I'm subscribing to.
How I'm updating the list in the reducer:
PODCAST_GRADIENT_UPDATED: (state, { payload }) => ({
...state,
items: state.items.map(item => {
if (item.clientId === payload.clientId) {
item.gradient = payload.gradient; // eslint-disable-line
}
return item;
}),
}),
Should the above work without the list subscription?
if not, how should this be done?
this was a rookie error, in that there is some state mutation in the above example. changing my function in the reducer in the following way resolved this:
PODCAST_GRADIENT_UPDATED: (state, { payload }) => ({
...state,
items: state.items.map(item => {
if (item.clientId === payload.clientId) {
return { ...item, gradient: payload.gradient };
}
return { ...item };
}),
}),
notice specifically the use of the spread operator to create and return a new item.

Categories

Resources