When I click delete I get the Can not read property 'id' of null error.
I am confused.* How to pass the id so only the component I clicked on (delete button) is removed?**
Reducer:
const notesReducer = (state = notes, action, id) => {
switch (action.type) {
case 'ADD':
return [...state, action.newItem]
case 'DELETING':
//const newState = [...state.filter((note) => note !== action.note)]
const newState = [...state]
newState.filter((note) => note.id !== action.payload.id)
//newNote.pop()
default:
return state
}
}
Action
export const add = (id) => {
return {
type: 'ADD',
}
}
export const deleting = (id) => {
return {
type: 'DELETING',
}
}
Component
<div>
{notes.map((item, index) => (
<div>
<Select></Select>
<Dialog key={uuid()}></Dialog>
<Button
key={uuid()}
onClick={deleteNote}
>
Delete
</Button>
</div>
))}
</div>
dispatch({ type: 'ADD', mynote }),
dispatch({ type: 'DELETING' })
Based on your current implementation, you need to pass note id to
dispatch({ type: 'DELETING', note.id })
Also, in reducer, you need to return modified state.
case 'DELETING':
return state.filter((note) => note.id !== id)
As an advice, you actually don't use actions you defined, because you directly dispatch with type. So, keep in mind that it's a better approach to write actions and fire them using mapDispatchToProps.
Related
I'm currently learning useReducer and try to convert useState to useReducer in todo app. My problem :
TOGGLE_TODO can't update the value when click.
// First try
case TOGGLE_TODO:
let targetId = todoItem[action.index];
let newTodo = [...todoItem];
return (newTodo[targetId].completed = !newTodo[targetId].completed);
// Second try
case TOGGLE_TODO:
return todoItem.map((todo, index) => {
if (index === action.index) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
<button
value={index}
onClick={(event) =>
dispatch({
type: TOGGLE_TODO,
index: event.target.value,
})
}
>
{todo.completed ? "done" : "pending"}
</button>
UPDATE_TODO, I have no clue for convert this to useReducer. Can I convert this too ? Here is my code using useState.
const onUpdate = (e) => {
const target = e.currentTarget.value;
const todoTarget = todoItem[target].name;
setInput(todoTarget);
setTodoIndex(target);
};
And here is my codesandbox for full code. MY CODE USING USESTATE and MY CODE USING USEREDUCER
First, you first TOGGLE_TODO handler if flawed -
// First try
case TOGGLE_TODO:
let targetId = todoItem[action.index];
let newTodo = [...todoItem];
return (newTodo[targetId].completed = !newTodo[targetId].completed);
Your todoAction is actually a reducer and not an action, so you should rename it to todoReducer. Also, a reducer needs to return a new copy of the entire state, and not just change one part of it, so your second try is correct.
case TOGGLE_TODO:
return todoItem.map((todo, index) => {
if (index === action.index) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
Notice how in the first case you are returning one todoItem, where in the second case you are returning an entire new array.
The problem with the code is that in your button, when you dispatch the action with the value, you are dispatching a string -
<button
value={index}
onClick={(event) =>
dispatch({
type: TOGGLE_TODO,
index: event.target.value, // <- This is a string
})
}
>
{todo.completed ? "done" : "pending"}
</button>
And in your reducer you are trying to compare it to a number -
if (index === action.index) // This will always be false since index is a .number and action.index is a string
The solution is to dispatch a number like so -
dispatch({
type: TOGGLE_TODO,
index: Number(event.target.value),
})
working codesandbox - https://codesandbox.io/s/long-monad-5yzjv7?file=/src/App.js
How can I remove a specific item (by id) from localstorage using react (redux - persist)? handleSubmit is working fine, but handleDelete, is not. I have this:
handleSubmit = event => {
event.preventDefault();
this.props.addWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
};
handleDelete = (event, id) => {
this.props.deleteWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
}
const mapStateToProps = state => ({
allWeather: state.allWeather
});
const mapDispatchToProps = dispatch =>
bindActionCreators(WeatherActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(WeatherList);
And button in form to call handleDelete:
<form onSubmit={this.handleDelete}><button type="submit" id="add" onClick={this.handleDelete}>Remove City</button></form>
My localstorage:
allWeather: "[{\"id\":0.5927975642362653,\"city\":\"Toronto\"},{\"id\":0.8124764603718682,\"city\":\"Fortaleza\"},{\"id\":0.9699736666575081,\"city\":\"Porto\"},{\"id\":0.852871998478355,\"city\":\"Tokio\"},{\"id\":0.8854642571682461,\"city\":\"New York\"}]"
My reducer:
export default function allWeather(state = [], action) {
switch (action.type) {
case "ADD_WEATHER":
return [...state, { id: Math.random(), city: action.payload.city }];
case "DELETE_ITEM":
return [...state, state.weatherCity.filter((event, id) => id !== action.payload.id)];
default:
return state;
}
}
And actions:
export const deleteWeather = id => ({
type: "DELETE_ITEM",
payload: { id }
});
I appreciate any help.
Your problem is that you are using the spread operator, which copies the content of the current state first. Then you are adding the items that were returned from the filter method. So you aren't deleting but adding. To delete from an array use the filter method only, without the spread operator like that:
return state.filter( (city) => city.id !== action.payload.id )
Also the state is an array, not an object, so this is invalid state.weatherCity.
My component is set to display a list of books, as card thumbnails. Each item from the list of books is generated by this component.
Each Card has a favorites icon, when clicking it adds the book to favoriteTitles array. By pressing again on the favorites icon it removes it from the list.
const Card = ({ title, history }) => {
const dispatch = useDispatch();
const { favoriteTitles } = useSelector(({ titles }) => titles);
const { id, name, thumbnail } = title;
const [favorite, setFavorite] = useState(favoriteTitles?.some(item => item.titleId === title.id));
const handleFavoriteClick = () => {
const isFavorite = favoriteTitles?.some(item => item.titleId === title.id);
if (isFavorite) {
dispatch(removeFavoriteTitle(title));
setFavorite(false);
} else {
dispatch(addFavoriteTitle(title));
setFavorite(true);
}
};
return (
<CardContainer>
<Thumbnail thumbnail={thumbnail} />
{name}
<FavesIcon isActive={favorite} onClick={handleFavoriteClick} />
</CardContainer>
);
};
The issue with this component is when you press once on FavesIcon to add, and if you changed your mind and want to remove it and press right away again, the favoritesTitles array still has the old value.
Let's suppose our current favorites list looks like this:
const favoritesTitles = [{titleId: 'book-1'}];
After pressing on favorites icon, the list in Redux gets updated:
const favoritesTitles = [{titleId: 'book-1'}, {titleId: 'book-2'}];
And if I press again to remove it, the favoritesTitles array inside the component is still the old array with 1 item in it. But if I look in Redux the list updated and correct.
How component should get the updated Redux value?
Update
I have specific endpoints for each action, where I add or remove from favorites:
GET: /users/{userId}/favorites - response list eg [{titleId: 'book-1'}, {titleId: 'book-2'}]
POST: /users/me/favorites/{titleId} - empty response
DELETE: /users/me/favorites/{titleId} - empty response
For each action when I add or remove items, on success request I dispatch the GET action. Bellow are my actions:
export const getFavoriteTitles = userId =>
apiDefaultAction({
url: GET_FAVORITE_TITLES_URL(userId),
onSuccess: data => {
return {
type: 'GET_FAVORITE_TITLES_SUCCESS',
payload: data,
};
},
});
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
return dispatch(
apiDefaultAction({
method: 'POST',
url: SET_FAVORITE_TITLES_URL,
data: {
titleId: id,
},
onSuccess: () => {
dispatch(getFavoriteTitles(userId));
return { type: 'SET_FAVORITE_TITLE_SUCCESS' };
},
})
);
};
My reducers are pretty straight forward, I'm not mutating any arrays. Since only GET request is returning the list of array, I don't do any mutating in my reducers:
case 'GET_FAVORITE_TITLES_SUCCESS':
return {
...state,
favoriteTitles: action.payload,
};
case 'SET_FAVORITE_TITLE_SUCCESS':
return state;
case 'DELETE_FAVORITE_TITLE_SUCCESS':
return state;
It seems that by the time you click FavesIcon second time after adding to favourites, GET: /users/{userId}/favorites request is still pending and favoriteTitles list is not updated yet. That's why the component still contains an old value.
You need to update favoriteTitles list right away after triggering addFavoriteTitle or removeFavoriteTitle actions, without waiting GET_FAVORITE_TITLES_SUCCESS action to be dispatched. This pattern is called 'Optimistic UI':
export const toggleFavorite = itemId => {
return {
type: 'TOGGLE_FAVORITE',
payload: { itemId },
};
}
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
export const removeFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
And your reducer can look something like this:
case 'TOGGLE_FAVORITE':
return {
...state,
favoriteTitles: state.favoriteTitles.map(item => item.titleId).includes(action.payload.itemId)
? state.favoriteTitles.filter(item => item.titleId !== action.payload.itemId)
: [...state.favoriteTitles, { titleId: action.payload.itemId }],
};
UPD. Please, check out a minimal working sandbox example
I am trying hard to remove an array. But it is not work. After delete my array length and data remain same.
This is my frontend react web page
renderAdmin = (id) => {
if(this.props.auth.user.userType==='normal') return(<td></td>);
return (
<React.Fragment>
<td>
<button onClick={() => this.props.changeStatus(id)}
className="ui button" >Change Status</button>
</td>
<td>
<button onClick={() => this.props.deleteUser(id)}
className="ui button" >Delete User</button>
</td>
</React.Fragment>
);
}
Here i am taking id of my array.
Now this is my redux action creator
export const deleteUser = (id) => (dispatch,getState) => {
const user = JSON.parse(localStorage.getItem('user'));
axios.delete(`/api/users/remove/${id}`, {
headers: {
'Authorization': `${user.token}`
}
}).then((res) => {
console.log(res.data);
dispatch({
type: 'DELETE_USER',
payload: res.data
})
dispatch({
type: 'CREATE_ERROR',
payload: 'User Delete'
})
})
.catch((error) => {
console.error(error)
})
}
Here first i remove it from backend server. That works fine. Then i send res.data to my reducer payload. DELETE_USER not working well. but CREATE_ERROR works fine.
This is my reducer
export default function (state = [] , action) {
// console.log(action)
switch(action.type) {
case 'ALL_USER' : return action.payload;
case 'EDIT_USER' :
return state.map(user=>{
if(user._id === action.payload._id){
return action.payload;
}
else {
return user;
}
});
case 'DELETE_USER' :
return state.filter(user=>
user !== action.payload
)
default : return state;
}
}
After pressing delete button, data remove from server but redux store always remain same. Please help me.
Thank You.
It might be due to the objects not being truly equal try filtering on the id instead:
return state.filter(user=>
user._id !== action.payload._id
)
Or check for deep equality.
I currently have a code that displays Api data on the page. This is a list :
API has this structure:
{"hij":{
"low":[{......},{.....}],
"all":[{....},{......}]}
}
How to implement such buttons on Redux? Can you help me a little bit? I watched a few dozen videos on YouTube, read many articles and no examples of how to make buttons which to take different data from API....
On React I implement it this way(App.js):
class App extends React.Component {
state = {
day: 1,
data: [],
filteredData: [],
search: "",
shift: "low"
};
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(`...`);
const data = (await response.json()).body;
this.setState({data, shift: Object.keys(data)[0]}, this.filter);
};
loadDay = day => {
this.setState({ day }, this.fetchData);
};
updateSearch = e => {
this.setState({search: e.target.value});
};
filter = () => {
this.setState(({ search, data, shift }) => {
const s = search.toLowerCase();
return {
filteredData: data[shift].filter(n =>
n.term.toLowerCase().includes(s)
)
};
});
};
onClick = ({target: { dataset: { shift }}}) => {
this.setState(() => ({ shift }), this.filter);
};
render() {
const { search, shift, data, filteredData } = this.state;
return (
<div>
<TableSearch value={search}
onChange={this.updateSearch}
onSearch={this.filter}/>
{days.map((day, i) => (
<button key={day}
onClick={() => this.loadDay(i)}
className={i === this.state.day ? "active" : ""}>{day}</button>
))}
<br />
{Object.keys(data).map(n => (
<button data-shift={n}
onClick={this.onClick}
className={n === shift ? "active" : ""}>{n} shift</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
There are a number of things to change to make this a redux compatible application, the least of your concerns would be buttons. So instead of answering the button part directly, here's an annotated refactor of your application to Redux:
import { createStore, applyMiddleware } from 'redux';
import { connect, Provider } from 'react-redux';
import thunk from 'redux-thunk';
// Take out common functionality into seperate functions, such as
// running a search here.
function searchFilter (search, data) {
return data.filter(n => n.term.toLowerCase().includes(search));
}
// This is your reducer function which updates the global redux state,
// depending on which action you dispatch:
// see https://redux.js.org/basics/reducers
function reducer (state = {}, action) {
state = { ...state }
switch (action.type) {
case 'SET_SEARCH':
state.search = action.search.toLowerCase();
break;
case 'RUN_FILTER':
state.shift = action.shift || state.shift;
state.search = action.search || state.search;
state.filteredData = searchFilter(state.search, state.data[state.shift]);
break;
case 'LOAD_DATA_START':
state.day = action.day;
break;
case 'LOAD_DATA_END':
state.data = action.data;
state.shift = Object.keys(data)[0];
state.filteredData = searchFilter(state.search, state.data[state.shift]);
break;
}
return state;
}
// This is your store object which contains an initial state, and a reducer
// that will be used for dispatched actions.
// see https://redux.js.org/basics/store
//
// Redux-thunk is used as middleware to support async data fetching which you will
// also need to read up on, although you don't really need to know how it
// works at first.
// see https://github.com/reduxjs/redux-thunk
const store = createStore(
reducer,
{
day: 1,
data: [],
filteredData: [],
search: "",
shift: "departure"
},
applyMiddleware(thunk)
);
// This is a "thunk" called fetch data, again you can read more
// about thunks in the redux-thunk docs
// see https://github.com/reduxjs/redux-thunk
function fetchData (day) {
return async (dispatch) => {
dispatch({ type: 'LOAD_DATA_START', day });
const response = await fetch(`https://api.iev.aero/api/flights/${days[this.state.day]}`);
const data = (await response.json()).body;
dispatch({ type: 'LOAD_DATA_END', data });
}
}
const days = ["23-08-2019", "24-08-2019", "25-08-2019"];
// Stripped down component, it does not handle any of its own state
// all state is passed to it through the redux connect HOC.
class Root extends React.Component {
componentDidMount() {
this.props.onFetchData(this.props.day);
}
render() {
const { search, shift, data, filteredData, onFilter, onSetSearch, onFetchData } = this.props;
return (
<div>
<TableSearch value={search}
onChange={(e) => onSetSearch(e.target.value)}
onSearch={() => onFilter()} />
{days.map((day, i) => (
<button key={day}
onClick={() => onFetchData(day)}
className={i === day ? "active" : ""}>{day}</button>
))}
<br />
{Object.keys(data).map(n => (
<button data-shift={n}
onClick={(e) => onFilter({ shift: e.target.dataset.shift })}
className={n === shift ? "active" : ""}>{n} shift</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
// This is the "connected" version of the component, which is
// your component wrapped in a connect HOC. When the reducer function
// is run, the two functions below will be executed and your component
// inside will re render.
//
// You can read more about this one in react-redux
// https://react-redux.js.org/
const ConnectedRoot = connect(
(state) => state,
(dispatch) => ({
onFilter: (args) => dispatch({ type: 'RUN_FILTER', ...args }),
onSetSearch: (search) => dispatch({ type: 'SET_SEARCH', search }),
onFetchData: (day) => dispatch(fetchData(day))
})
);
// This is your top level component that you would call in ReactDOM.render
// The Provider component is part of react-redux, and you can read about it
// there, but in most cases it is sufficient to drop it at the very top level
// of your application.
// https://react-redux.js.org/
const App = () => (
<Provider store={store}>
<ConnectedRoot />
</Provider>
);