How to get updated redux-toolkit state when component is not re-render - 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:
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.

Related

Have to press twice to delete item in redux-toolkit

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

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 dispatch action in react-redux

I've created a list of users using action getUsers() and now I wanna delete any user.
so I've created an action:
export function deleteUser(id) {
return {
type: DELETE_USER,
payload: id
};
}
set up reducer:
case DELETE_USER :
return {
...state,
users: state.users.filter(i => i.id !== action.id)
}
initialState is this:
const initialState = {
users: [
{
id: 1,
name: 'Oksana'
},
{
id: 2,
name: 'Serge'
},
],
loading: true
}
and now time to dispatch deleteUser()
this is my Users component:
const mapStateToProps = (state) => ({ users: state.users });
const mapDispatchToProps = (dispatch) => {
return {
deleteUser: id => {
dispatch(deleteUser(id))
}
}
};
const Users = (props) => {
const { users } = props.users;
console.log(props);
useEffect(() => {
getUsers();
}, []);
return (
<>
<h2>Users</h2>
{users.map((user) => {
return (
<div className="d-flex justify-content-between align-items-center mb-1">
<li>{user.name}</li>
<button onClick={() => props.deleteUser(user.id)}>x</button>
</div>
);
})}
</>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(Users);
I tried to rewrite mapDispatchToProps but it does not work. I click delete (x) button and it behaves dumb. no errors on terminal.

How can I repeatedly filter an array?

So, now i'm making to-do-list, and i have problems with buttons 'active' and 'done' tasks. When i press one of these button, it has to return tasks which are done/active, and it returns, but only 1 time. I guess it makes a new array, and delete old array. So how to make filter, which won't delete my array and just filter tasks which are done or active? And every time I click on these buttons, I will be shown tasks filtered on done/active/all.
P.S. sorry for ENG
onst ADD_TASK = 'ADD_TASK'
const EDIT_STATUS = 'EDIT_STATUS'
const TASK_DELETE = 'TASK_DELETE'
const DONE_TASK = 'DONE_TASK'
const ACTIVE_TASKS = 'ACTIVE_TASKS'
const initialState = {
tasks: []
};
const mainReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK: {
return {
...state,
tasks: [{
id: shortid.generate(),
task: action.task,
status: false
}, ...state.tasks], filter: 'all'
}
}
case EDIT_STATUS: {
return {
...state,
tasks: state.tasks.map(task => task.id === action.id ? {...task, status: !task.status} : task)
}
}
case TASK_DELETE: {
return {
...state,
tasks: state.tasks.filter(t => t.id !== action.id)
}
}
case DONE_TASK: {
return {
...state,
tasks: state.tasks.filter(t => !t.status),
filter: 'done'
}
return state.tasks
}
case ACTIVE_TASKS: {
return {
...state,
tasks: state.tasks.filter(t => t.status),
filter: 'active'
}
return state.tasks
}
default:
return state
}
}
export const doneTask = () => ({type: 'DONE_TASK'})
export const activeTask = () => ({type: 'ACTIVE_TASKS'})
export const addTask = task => ({type: 'ADD_TASK', task});
export const editStatus = id => ({type: 'EDIT_STATUS', id})
export const deleteTask = id => ({type: 'TASK_DELETE', id})
export default mainReducer;
Here is an example of how to store local state and pass it to ConnectedList as props.done.
ConnectedList has selectFilteredTasks as mapStateToProps and that is a selector created with reselect to get tasks, the second argument to this function is props so if props.done is not undefined it'll filter out the tasks that are done.
const { useState } = React;
const {
Provider,
connect,
} = ReactRedux;
const { createStore } = Redux;
const { createSelector } = Reselect;
const state = {
tasks: [
{
id: 1,
task: 'one',
status: false,
},
{
id: 2,
task: 'two',
status: true,
},
],
};
const store = createStore(
(x) => x, //won't dispatch any actions
{ ...state },
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
//selectors
const selectTasks = (state) => state.tasks;
const selectFilteredTasks = createSelector(
selectTasks,
(_, { done }) => done, //get the second argument passed to selectFilteredTasks
(tasks, done) =>
done !== undefined
? {
tasks: tasks.filter(
(task) => task.status === done
),
}
: { tasks }
);
const List = ({ tasks }) => (
<ul>
{tasks.map((task) => (
<li key={task.id}>
<pre>{JSON.stringify(task)}</pre>
</li>
))}
</ul>
);
const ConnectedList = connect(selectFilteredTasks)(List);
const App = () => {
const [done, setDone] = useState();
return (
<div>
<label>
only done
<input
type="checkbox"
onClick={() => setDone(done ? undefined : true)}
></input>
</label>
<ConnectedList done={done} />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
I suggest you to go with different approach.
In button click function, you can get all todos and return filtered out todos which are active/completed instead of performing operation on reducer.

How can I filter data using Redux but keeping the old state?

I am working on a search functionality with Redux but I am having some issues.
These are the actions related to the search stuff:
export const passengersDataAction = passengersData => ({
type: ActionTypes.PASSENGERS_DATA,
// This is the array of objects that I need to search through
payload: { passengersData },
});
export const searchParamAction = searchParam => ({
type: ActionTypes.SEARCH_PARAM,
// This is the param that I need to send to passengersData
// in order to get a new array of objects based on the searchParam
payload: { searchParam },
});
Reducers:
const initialState = {
passengersData: [],
searchParam: '',
};
const handlers = {
[ActionTypes.PASSENGERS_DATA](state, action) {
return {
...state,
passengersData: action.payload.passengersData,
};
},
[ActionTypes.SEARCH_PARAM](state, action) {
return {
...state,
searchParam: action.payload.searchParam,
};
},
};
Btw this is how the array of objects looks:
[
{
"id": 3,
"name": "Marcos Alonso",
"address": "Sabana",
"phone": "712321222",
"pickup": 0,
"cardinalpoint": "N",
"latitude": "9.93683450",
"longitude": "-84.10991830",
"timestamp": "2019-02-19 21:23:46",
"dropofftimestamp": null,
"pickuptimestamp": null,
"deleted": null,
"driver": 1
},
...
]
This is something I am trying to get it to work:
[ActionTypes.SEARCH_PARAM](state, action) {
//In filter you can add your own logic to get the data
const searchedData = state.passengersData.filter((passenger) => passenger.name === action.payload.searchParam);
return {
...state,
passengersData: searchedData,
searchParam: action.payload.searchParam,
};
},
But with the code above, it is replacing the passesngerData with 'searchedData'. I need to keep the original passengerData so I guess I can create a new state in redux store and return it from the reducer. My question is, how can I do that? Every time I type something in the input, the whole passengersData array goes away and the searched is not returning anything.
What am I missing?
EDIT
I am going to add the code regarding the components that handle the search functionality:
// imports
import { searchParamAction } from '../../screens/HomeScreen/actions/homeScreen';
class AllPassengersList extends Component {
render() {
const {
searchParamActionHandler,
searchParam,
} = this.props;
return (
<View>
<View>
<TextInput
onChangeText={text => searchParamActionHandler(text)}
value={searchParam}
placeholder="Search..."
/>
</View>
<Text>{searchParam}</Text>
<PassengerCardBasedOnRoute searchParam={searchParam} />
</View>
);
}
}
AllPassengersList.propTypes = {
passengersData: PropTypes.oneOfType([PropTypes.array]).isRequired,
searchParam: PropTypes.oneOfType([PropTypes.string]).isRequired,
searchParamActionHandler: PropTypes.oneOfType([PropTypes.func]).isRequired,
};
export default compose(
connect(
store => ({
navigationStore: store.homeScreen.navigation,
searchParam: store.homeScreen.searchParam,
passengersData: store.homeScreen.passengersData,
}),
dispatch => ({
searchParamActionHandler: value => {
dispatch(searchParamAction(value));
},
}),
),
)(AllPassengersList);
The component above is the one holding the search text input.
The one below is the one where I render the array of objects that I need to filter:
import { View } from 'react-native';
import React from 'react';
import PropTypes from 'prop-types';
import { compose } from 'redux';
import { connect } from 'react-redux';
import PassengersInfo from './PassengerInfo';
import { popupsModalsAction } from '../PopupsModals/actions/popupsModals';
const PassengerCardBasedOnRoute = ({
navigationStore,
passengersData,
popupsModalsActionHandler,
searchParam,
}) => {
return (
<View>
{passengersData.map(info => (
<PassengersInfo
key={info.id}
id={info.id}
searchParam={searchParam}
cardinalpoint={info.cardinalpoint}
name={info.name}
address={info.address}
datetime={info.timestamp}
/>
))}
</View>
);
};
PassengerCardBasedOnRoute.propTypes = {
passengersData: PropTypes.oneOfType([PropTypes.array]).isRequired,
searchParam: PropTypes.oneOfType([PropTypes.string]).isRequired,
};
export default compose(
connect(
store => ({
passengersData: store.homeScreen.passengersData,
searchParam: store.homeScreen.searchParam,
}),
),
)(PassengerCardBasedOnRoute);
So passengersData is the array which handles the data I need.
You should not filter the data in redux store and assign the result to the variable you filtered data from because this way on every search you wold loose you original data, instead just store the searchParam in store and write a selector that returns you the filtered result and use that in the component
const filterSelector = (state, props) => {
return state.passengersData.filter((passenger) => passenger.name === state.searchParam);
}
const mapStateToProps = (state, props) => {
const searchData = filterSelector(state, props);
return {
searchData
}
}
and you reducer would simply be
[ActionTypes.SEARCH_PARAM](state, action) {
return {
...state,
searchParam: action.payload.searchParam,
};
}
EDIT: Updating code with example
// imports
import { searchParamAction } from '../../screens/HomeScreen/actions/homeScreen';
class AllPassengersList extends Component {
render() {
const {
searchParamActionHandler,
searchParam,
} = this.props;
return (
<View>
<View>
<TextInput
onChangeText={text => searchParamActionHandler(text)}
value={searchParam}
placeholder="Search..."
/>
</View>
<Text>{searchParam}</Text>
<PassengerCardBasedOnRoute searchParam={searchParam} />
</View>
);
}
}
AllPassengersList.propTypes = {
passengersData: PropTypes.oneOfType([PropTypes.array]).isRequired,
searchParam: PropTypes.oneOfType([PropTypes.string]).isRequired,
searchParamActionHandler: PropTypes.oneOfType([PropTypes.func]).isRequired,
};
const filterSelector = (passengersData, searchParam) => {
return passengersData.filter((passenger) => searchParams == '' || passenger.name === searchParam);
}
const mapStateToProps = store => ({
navigationStore: store.homeScreen.navigation,
searchParam: store.homeScreen.searchParam,
passengersData: filterSelector(state.homeScreen.passengersData, state.homeScreen.searchParam),
}),
export default compose(
connect(
dispatch => ({
searchParamActionHandler: value => {
dispatch(searchParamAction(value));
},
}),
),
)(AllPassengersList);
,

Categories

Resources