Search code examples
reactjsreduxreact-redux

Updating array value inside array of objects


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.


Solution

  • 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: [] },
      ];