I am trying to delete an actor from actors array which is inside movie object.
Firstly, I map actors to display actors one by one and adding a remove action to button.
Whole ActorsList component
const PostList = ({ movie, removeActorFromMovie }, props) => {
return (
<div>
<h3>Actors in film </h3>
{movie.actors.map((actor, i) => (
<div key={actor}>
{actor}
<button onClick={() => removeActorFromMovie(actor)}>Usuń</button>
</div>
))}
</div>
);
};
const mapStateToProps = (state, props) => {
return {
movie: state.movies.find((movie) => movie.id === props.match.params.id),
};
};
const mapDispatchToProps = {
removeActorFromMovie,
};
export default withRouter(
connect
(mapStateToProps, mapDispatchToProps)(PostList)
);
Action:
export const removeActorFromMovie = (payload) => ({
type: "MOVIE_ACTOR_DELETE",
payload,
});
Reducer:
case "MOVIE_ACTOR_DELETE":
return [...state.filter((el) => el !== action.payload)];
Although, the payload wokrs great and returns the array item. It does not remove it from an array.
My movies array with movie under index 0:
movies: Array(1)
0:
actors: (2) ['Robert XYZ', 'Jake XYZ']
director: "Quentino TARANTINO, TARANTINO"
id: "352cc51e-87c4-46ec-84a0-9f308ec1a71a"
productionYear: "123"
title: "Titanic"
and payload:
payload: "Robert XYZ"
type: "MOVIE_ACTOR_DELETE"
and as I said, this doest not remove the actor from actors array which is under index 0 in movies array
Update:
I'm supposing your state is something like this:
state: {
movies: [...]
}
If you want to remove an actor from a movie you should also pass the payload a movie to remove the actor from.
removeActorFromMovie(actor, movie)
then in the reducer:
const { movie, actor } = action.payload;
const nextMovie = {
...movie,
actors: movie.actors.filter((a) => a !== actor)
};
const movieIndex = state.movies.findIndex((m) => m.id === movie.id);
return {
...state,
movies: [
...state.movies.slice(0, movieIndex),
nextMovie,
...state.movies.slice(movieIndex + 1)
]
};
OLD ANSWER (If you instead want to remove a movie):
If you want to remove a movie based on its actors you filter function will not work. You are trying to remove an item whose value is "Robert XYZ" but the element passed to you filter function is a movie.
if the state is like this:
movies: [
{
actors: ['Robert XYZ', 'Jake XYZ']
director: "Quentino TARANTINO, TARANTINO"
id: "352cc51e-87c4-46ec-84a0-9f308ec1a71a"
productionYear: "123"
title: "Titanic"
}
]
then you can do:
const nextItems = state.filter(movie => !movie.actors.includes(action.payload))
return nextItems;
There is no need to create a new copy of the filtered array with the spread operator because filter return a new copy of the original array.
Related
I have an array "movies" of objects like this:
{
id: "some id",
title: "some title",
actors: []
}
As you can see, the actors array stays empty, because I don't add actors while I am adding a movie. I want to add actors later on, while being on http://localhost:3000/movies/<movieId> .
I do this by this Form. Im just selecting a actor, from already existing lists of actors.
const PostForm = ({ addActorToMovie, movie, actors, history }, props) => {
const handleSubmit = (values) => {
addActorToMovie(values);
// history.push(`/movies/${movie.id}`);
};
let actorsList =
actors.length > 0 &&
actors.map((item, i) => {
return (
<option value={`${item.firstName}, ${item.lastName}`}>
{item.firstName + item.lastName}{" "}
</option>
);
});
return (
<div>
<h3>Add Actor</h3>
<Formik
initialValues={{
actor: "",
}}
onSubmit={(values) => handleSubmit(values)}
enableReinitialize={true}
>
<Form>
<Field name="actor" component="select">
<option value="null"> </option>
{actorsList}
</Field>
<button type="submit">Zatwierdz</button>
</Form>
</Formik>
</div>
);
};
const mapStateToProps = (state, props) => {
return {
movie: state.movie,
actors: state.actors,
};
};
const mapDispatchToProps = {
addActorToMovie,
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(PostForm)
);
The form works. Although instead of adding value to my actors array. It creates a new object under movies array which looks like this:
actors: {actor: 'Some actor name'}
My reducers looks like this. Probably this is the source of the problem, but I don't really know how to do this.
export const movieReducer = (state = [], action) => {
switch (action.type) {
case "MOVIE_ADD":
return [...state, action.payload];
case "MOVIE_DELETE":
return [...state.filter((el) => el.id !== action.payload.id)];
case "MOVIEACTOR_ADD":
return {
...state,
actors: action.payload
};
default:
return state;
}
};
What I want to do is of course push the actor which i enter in my Formik to an array of specified movie.
Instead of declaring a new actors property that is only the payload, you want to append it to the existing actors array. However, you will first need to know which movie object you are updating, so this also needs to be sent in the action payload.
Update the submit handler to send both the movie id and the actor to be added.
const handleSubmit = ({ actor }) => {
addActorToMovie({
movieId: movie.id,
actor,
});
};
Update the reducer case to map the movies array, which appears to be state in movieReducer. Shallow copy the movies state array, and for the matching movie id, shallow copy and append the actor to the actors array.
case "MOVIE_ACTOR_ADD":
const { actor, movieId } = action.payload;
return state.map(movie => movie.id === movieId
? {
...movie, // <-- shallow copy movie object
actors: [
...movie.actors, // <-- shallow copy existing array
actor // <-- append new actor payload
],
}
: movie
);
When adding a movie to state, ensure the actors array property exists. If you aren't passing it the MOVIE_ADD action payload then handle it in the reducer when adding a movie.
case "MOVIE_ADD":
return [
...state,
{ ...action.payload, actors: [] },
];
I'm creating a recipe application using MERN stack.
The issue I am stuck on is trying to delete an ingredient found in an array, inside a recipe object. My recipe object looks like this:
MongoDB Recipe Object
Each ingredient has a cross next to it that allows you to click on it to remove.
<div>
<ol className='pad5px20px'>
{addIngredients.ingredients.map(data => (
<div className='ingredient-item padTB5px' key={data}>
<li>{data}</li>
<span onClick={() => removeIngredient(data)}>
<i className='fas fa-trash'></i>
</span>{' '}
</div>
))}
</ol>
</div>
The addIngredients and removeIngredient functions look like this:
const addIngredient = e => {
e.preventDefault();
if (query === '') return;
addIngredients.ingredients.push(query);
setIngredients(addIngredients);
setRecipe(prevState => ({
...prevState,
ingredients: [
...prevState.ingredients,
{ id: Date.now(), ingredient: query }
]
}));
};
const removeIngredient = data => {
const results = addIngredients.ingredients.filter(
e => e.ingredients !== data
);
setIngredients(
addIngredients.ingredients.filter(e => e.ingredients !== data)
);
};
Every time I remove an ingredient from my list I get an error that states "TypeError: Cannot read property 'map' of undefined".
Is there something that i'm missing here? I have been working on this app for the past couple of months now and I am stuck on this particular bit. I thought a better way would be to use Redux as I have been able to delete a whole recipe using a reducer:
case DELETE_RECIPE:
return {
...state,
recipes: state.recipes.filter(recipe => recipe._id !== action.payload),
loading: false
};
but how would I be able to target one particular ingredient?
Any suggestions would be greatly appreciated.
I added notes to your code problems)
const addIngredient = e => {
e.preventDefault();
if (query === '') return;
***STATE MUTATION***
addIngredients.ingredients.push(query);
setIngredients(addIngredients);
setRecipe(prevState => ({
...prevState,
ingredients: [
...prevState.ingredients,
{ id: Date.now(), ingredient: query }
]
}));
};
const removeIngredient = data => {
const results = addIngredients.ingredients.filter(
e => e.ingredients !== data
);
***You're adding ingredients object instead of addIngredients as you used in addIngredient method***
setIngredients(
addIngredients.ingredients.filter(e => e.ingredients !== data)
);
};
addIngredients.ingredients.filter(e => e.ingredients !== data) returns filtered ingredients instead of addIngredients with filtered ingredients field
How it should be
const addIngredient = e => {
e.preventDefault();
if (query === '') return;
setIngredients({
...addIngredients,
ingredients: [
...addIngredients,
query
]
});
setRecipe(prevState => ({
...prevState,
ingredients: [
...prevState.ingredients,
{ id: Date.now(), ingredient: query }
]
}));
};
const removeIngredient = data => {
const results = addIngredients.ingredients.filter(
e => e.ingredients !== data
);
setIngredients(
{
...addIngredients,
ingredients: results
});
};
I'm working on react app with redux. I want to delete multiple item from array. I write below code in my reducer which delete single item from array but i want to delete multiple item.
case DELETE_LINK:
let dltLink = state.filter(item => {
return item._id !== action.data._id
})
return {
...state,
parentFolderlinks: dltLink
};
It seems you want to filter links from state.parentFolderlinks, say you have the ids in action.data.ids, you could
case DELETE_LINK:
const parentFolderlinks = state.parentFolderlinks.filter(item => {
return !action.data.ids.includes(item._id);
});
return {
...state,
parentFolderlinks
};
On what basis would you like to filter items? I assume that multiple items will not have the same id.
Below example shows how we can filter multiple items in redux. In this case, foods state with items that has type as fruit and removes everything else.
// initial state with all types of foods
const initialState = {
"foods": [
{
name: "apple",
type: "fruit"
},
{
name: "orange",
type: "fruit"
},
{
name: "broccoli",
type: "vegetable"
},
{
name: "spinach",
type: "vegetable"
},
]
}
// sample reducer that shows how to delete multiple items
export default (state = initialState, { type, payload }) => {
switch (type) {
// delete multiple items that does not have type fruit
// i.e both brocolli and spinach are removed because they have type vegetable
case DELETE_ITEMS_WITHOUT_TYPE_FRUIT:
const onlyFruits = state.foods.filter(food => food.type === "fruit");
return {
...state,
foods: onlyFruits
}
}
}
you could map over the state and run it through a function that works out if you want to keep it or not (I don't know what your logic is for that) then return the array at the end
const keepThisItem =(item) => {
return item.keep
}
case DELETE_LINK:
let itemsToKeep = []
let dltLink = state.map(item => {
if(keepThisItem(item){
itemsToKeep.push(item)
}
return itemsToKeep
})
All examples on redux-toolkit website show usage of either selectIds or selectAll.
Using either of them is simple. I have a redux-slice from where I am exporting
export const {
selectById: selectUserById,
selectIds: selectUserIds,
selectEntities: selectUserEntities,
selectAll: selectAllUsers,
selectTotal: selectTotalUsers
} = usersAdapter.getSelectors(state => state.users)
then I am importing the selectors in my components and using like
const valueIAmInterestedIn = useSelector(selectUserIds)
I am interested in the code related to the usage of selectUserById.
According to the documentation the by id selector has the following signature: selectById: (state: V, id: EntityId) => T | undefined.
So you can call it in your component in the following way:
const Component = ({ id }) => {
const item = useSelector((state) =>
selectUserById(state, id)
);
};
This implementation of "normalization" may not work if you sort/filter entities on the server because the state would look more like:
{
data: {
entityType: {
//query is key and value is status and result
// of the api request
'sort=name&page=1': {
loading: false,
requested: true,
error: false,
stale: false,
ids: [1, 2],
},
'sort=name:desc&page=1': {
//would have loading, requested ....
ids: [2, 1],
},
data: {
//all the data (fetched so far)
'1': { id: 1 },
'2': { id: 2 },
},
},
},
};
I have not worked with the "helpers" so have to look into it as it may facilitate for server side filtering and sorting.
I also doubt it will memoize the selector:
const List = ({ items }) => (
<ul>
{items.map(({ id }) => (
<Item key={id} id={id} />
))}
</ul>
);
const Item = React.memo(function Item({ id }) {
//selectUserById is called with different id during
// a render and nothing will be memoized
const item = useSelector((state) =>
selectUserById(state, id)
);
});
I have created a short documentation on how you can use selectors created with reselect.
Selector can be created on top of selectById as below:
export const getLanguageById = (entityId: number) => {
return createSelector(selectLanguageState, (state) =>
selectById(state, entityId)
);
};
This can be used in your component as below:
const language = useSelector(languageSelectors.getLanguageById(18));
I guess it's more straightforward and easy to read/ understand.
Thanks,
Manish
im new to react, trying to make an todolist website, i have the add and delete and displaying functionality done, just trying to add an search function, but i cant seem to get it working, where as it doesn't filter properly.
i basically want to be able to filter the values on the todos.title with the search value. such as if i enter an value of "ta" it should show the todo item of "take out the trash" or any item that matches with that string.
when i try to search, it gives random outputs of items from the filtered, i am wondering if my filtering is wrong or if i am not like displaying it correctly.
ive tried to pass the value into todo.js and display it there but didn't seem that was a viable way as it it should stay within App.js.
class App extends Component {
state = {
todos: [
{
id: uuid.v4(),
title: "take out the trash",
completed: false
},
{
id: uuid.v4(),
title: "Dinner with wife",
completed: true
},
{
id: uuid.v4(),
title: "Meeting with Boss",
completed: false
}
],
filtered: []
};
// checking complete on the state
markComplete = id => {
this.setState({
todos: this.state.filtered.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
};
//delete the item
delTodo = id => {
this.setState({
filtered: [...this.state.filtered.filter(filtered => filtered.id !== id)]
});
};
//Add item to the list
addTodo = title => {
const newTodo = {
id: uuid.v4(),
title,
comepleted: false
};
this.setState({ filtered: [...this.state.filtered, newTodo] });
};
// my attempt to do search filter on the value recieved from the search field (search):
search = (search) => {
let currentTodos = [];
let newList = [];
if (search !== "") {
currentTodos = this.state.todos;
newList = currentTodos.filter( todo => {
const lc = todo.title.toLowerCase();
const filter = search.toLowerCase();
return lc.includes(filter);
});
} else {
newList = this.state.todos;
}
this.setState({
filtered: newList
});
console.log(search);
};
componentDidMount() {
this.setState({
filtered: this.state.todos
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.todos
});
}
render() {
return (
<div className="App">
<div className="container">
<Header search={this.search} />
<AddTodo addTodo={this.addTodo} />
<Todos
todos={this.state.filtered}
markComplete={this.markComplete}
delTodo={this.delTodo}
/>
</div>
</div>
);
}
}
export default App;
search value comes from the header where the value is passed through as a props. i've checked that and it works fine.
Todos.js
class Todos extends Component {
state = {
searchResults: null
}
render() {
return (
this.props.todos.map((todo) => {
return <TodoItem key={todo.id} todo = {todo}
markComplete={this.props.markComplete}
delTodo={this.props.delTodo}
/>
})
);
}
}
TodoItem.js is just the component that displays the item.
I not sure if this is enough to understand the issue 100%, i can add more if needed.
Thank you
Not sure what is wrong with your script. Looks to me it works fine when I am trying to reconstruct by using most of your logic. Please check working demo here: https://codesandbox.io/s/q9jy17p47j
Just my guess, it could be there is something wrong with your <TodoItem/> component which makes it not rendered correctly. Maybe you could try to use a primitive element such as <li> instead custom element like <TodoItem/>. The problem could be your logic of markComplete() things ( if it is doing hiding element works ).
Please let me know if I am missing something. Thanks.