Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Fetch de estado inicial

11/23
Recursos

Aportes 15

Preguntas 5

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Reto aceptado mi estimado Oscar!

// GUARDAR PELICULA DEL USUARIO

  • Primero cree mi action en el cual le paso el id del usuario y el id de la pelicula.
export const favoriteMovie = (userId, movie, cb) => (dispatch) => {
  const data = {
    userId,
    movieId: movie.id,
  };
  axios({
    url: '/user-movies',
    method: 'post',
    data,
  })
    .then(({ data }) => {
      const {
        data: { movieExist },
      } = data;

      const message = movieExist
        ? `${movie.title} ya esta en tus favoritos`
        : `${movie.title} fue agregada a tus favoritos`;

      !movieExist && dispatch(setFavorite(movie));

      cb(movieExist, message);
    })
    .catch((err) => dispatch(setError(err)));
};
  • Luego como tenemos ya la ruta del Server Render para agregar una pelicula a un usuario del curso de passport la agregue al server.js
app.post('/user-movies', async (req, res, next) => {
  try {
    const { body: userMovie } = req;
    const { token } = req.cookies;

    const { data, status } = await axios({
      url: `${config.apiUrl}/api/user-movies`,
      headers: { Authorization: `Bearer ${token}` },
      method: 'post',
      data: userMovie,
    });

    const {
      data: { movieExist },
    } = data;

    if (status !== 200 && status !== 201) {
      return next(boom.badImplementation());
    }

    const statusCode = movieExist ? 200 : 201;

    return res.status(statusCode).json(data);
  } catch (error) {
    next(error);
  }
});

// LISTAR PELICULAS DEL USUARIO

Luego en el renderApp hago la peticion para obtener las peliculas del usuario

let userMovies = await axios({
      url: `${config.apiUrl}/api/user-movies/?userId=${id}`,
      headers: {
        Authorization: `Bearer ${token}`,
      },
      method: 'get',
    });

Accedo a la data que me trae solo el userId y el movieId por lo que tengo que obtener toda la informacion de las peliculas filtrando del movieList para que se pinten correctamente en frontend.

userMovies = userMovies.data.data;

const myList = [];

    userMovies.forEach((userMovie) => {
      movieList.forEach((movie) => {
        if (movie._id === userMovie.movieId) {
          myList.push(movie);
        }
      });
    });

Por ultimo se la pasa al initialState

 initialState = {
      user: {
        email,
        name,
        id,
      },
      playing: {},
      myList,
      trends: movieList.filter(
        (movie) => movie.contentRating === 'PG' && movie._id
      ),
      originals: movieList.filter(
        (movie) => movie.contentRating === 'G' && movie._id
      ),
    };

Hola a todos!

Hubo muchas cositas que no fueron tan sencillas de cuadrar, les comparto mi repo con la funcionalidad de carousel para añadir y quitar, también para reproducir los videos, espero les sirva.

Repo

Commit: ff1671f

Bueno este es mi aporte de como hacer lo de los agregar/eliminar favoritos, en realidad hay varias formas de hacerlo, la que hice fue no tocando el proyecto del api, el cual cuando creas un favorito se agrega un favorito de devuelve el _id de la tabla user_movies, y la misma que se usa para eliminar un favorito.
Estos son mis actions:

export const setFavorite = ({
  _id,
  cover,
  title,
  year,
  contentRating,
  duration,
}) => {
  return (dispatch) => {
    axios({
      url: '/user-movies',
      method: 'POST',
      data: {
        movieId: _id,
      },
    })
      .then(() => {
        dispatch(
          setFavoriteRequest({
            _id,
            cover,
            title,
            year,
            contentRating,
            duration,
          }),
        );
      })
      .catch((err) => dispatch(setError(err)));
  };
};

export const setFavoriteRequest = (payload) => ({
  type: 'SET_FAVORITE',
  payload,
});

export const deleteFavorite = (userMovieId) => {
  return (dispatch) => {
    axios({
      url: `/user-movies/${userMovieId}`,
      method: 'DELETE',
    })
      .then(() => {
        dispatch(deleteFavoriteRequest(userMovieId));
      })
      .catch((err) => dispatch(setError(err)));
  };
};

export const deleteFavoriteRequest = (payload) => ({
  type: 'DELETE_FAVORITE',
  payload,
});

agregué los 2 actions para enviar el request al ssr
El carousel item hice un par de cambios, agregué el nuevo usermovieid al props y lo uso cuando quiero borrar un favorito, el resto es lo mismo

const { _id, cover, title, year, contentRating, duration, userMovieId, isList } = props;
...
<img
  className='carousel-item__details--img'
  src={removeIcon}
  alt='Remove Icon'
  onClick={() => handleDeleteFavorite(userMovieId)}
/>

Luego en el SSR agregué los 2 nuevos endpoints:

app.post('/user-movies', async (req, res, next) => {
  const { body: userMovie } = req;
  const { id, token } = req.cookies;
  try {
    const response = await axios({
      url: `${process.env.API_URL}/api/user-movies`,
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
      },
      data: {
        userId: id,
        movieId: userMovie.movieId,
      },
    });

    res.status(200).json(response.data);
  } catch (error) {
    next(error);
  }
});

app.delete('/user-movies/:userMovieId', async (req, res, next) => {
  const { userMovieId } = req.params;
  const { token } = req.cookies;

  try {
    const response = await axios({
      url: `${process.env.API_URL}/api/user-movies/${userMovieId}`,
      method: 'DELETE',
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    res.status(200).json(response.data);
  } catch (error) {
    next(error);
  }
});

además cambié la lógica del initialState para poder agregar los videos favoritos en el key myList:

let initialState;
  const { email, name, id, token } = req.cookies;

  try {
    let movieList = await axios({
      url: `${process.env.API_URL}/api/movies`,
      method: 'GET',
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    movieList = movieList.data.data;

    let userMovieList = await axios({
      url: `${process.env.API_URL}/api/user-movies/?userId=${id}`,
      method: 'GET',
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    userMovieList = userMovieList.data.data;

    initialState = {
      user: {
        email,
        name,
        id,
      },
      playing: {},
      myList: movieList.filter((movie) => {
        return userMovieList.some(
          (userMovie) => movie._id === userMovie.movieId,
        );
      }).map((movie) => {
        const filteredMovie = userMovieList.find((userMovie) => movie._id === userMovie.movieId);
        if (filteredMovie) {
          // aquí agrego el userMoviId que luego lo usaré para borrar favoritos
          movie.userMovieId = filteredMovie._id;
        }
        return movie;
      }),
      trends: movieList.filter(
        (movie) => movie.contentRating === 'PG' && movie._id,
      ),
      originals: movieList.filter(
        (movie) => movie.contentRating === 'G' && movie._id,
      ),
    };
  } catch (err) {
    initialState = {
      user: {},
      playing: {},
      myList: [],
      trends: [],
      originals: [],
    };
  }

Con eso ya funcionaría el agregar y borrar favoritos, se podría agregar una lógica de validar si el favorito existe, igual se puede hacer de varias maneras, como validar en el estado, bloqueando el botón de agregar favorito si es repetido y además en el api de tener que validarlo.

Hola, alguien más tiene problemas con el token en la app… a veces si se encuentra en las cookies y otras veces no. Aún no he encontrado a que se debe este comportamiento.
Y también que la app se esta renderizando dos veces, tengo esa sensación

Para quien le parezca raro el por qué del Authorization: "Beare token" es una convención popular que viene de usar Auth0

Alguin sabe como puedo subir imagenes ¿?
quiero subir mi reto …

Oigan pero en la data tenemos varios tipos de contentRating ademas de P y PG, que pasa con los demás?

Challenge Accepted. (1 día y medio de sufrimiento pero aqui voy)
.
Los handles de CarouselItem.jsx

  const handleSetFavorite = () => {
    props.setFavorite({
      _id, cover, title, year, contentRating, duration,
    });
  };

  const handleDeleteFavorite = () => {
    props.deleteFavorite(_id);
  };

Las Actions agregadas…

// [...]

export const setFavorite = (payload) => {
  const { _id } = payload;
  return (dispatch) => {
    axios({
      url: '/user-movies',
      method: 'post',
      data: {
        movieId: _id,
      },
    })
      .then(dispatch(setFavoriteRequest(payload)))
      .catch((err) => dispatch(setError(err)));
  };
};

export const deleteFavorite = (_id) => {
  return (dispatch) => {
    axios({
      url: `/user-movies/${_id}`,
      method: 'post',
    })
      .then(dispatch(deleteFavoriteRequest(_id)))
      .catch((err) => dispatch(setError(err)));
  };
};

// [...]

.
La ruta con método POST del render server, enviando la petición a la API.

// [...]
app.post('/user-movies/:userMovieId', async function(req, res, next) {
  const { userMovieId } = req.params;
  const { id: userId, token } = req.cookies;

  try {
    const response = await axios({
      url: `${process.env.API_URL}/api/user-movies/${userMovieId}`,
      method: 'DELETE',
      headers: {
        Authorization: `Bearer ${token}`,
      },
      data: { userId }
    });

    res.status(200).json(response.data);
  } catch (error) {
    console.error('Fetch to API (user-movies/:userMovieId) failed.')
    next(error)
  }
});
// [...]

Setteo de State cada vez que carga

// [...]
const renderApp = async (req, res) => {

  let initialState;
  const { token, email, name, id } = req.cookies;

  try {
    let movieList = await axios({
      url:`${process.env.API_URL}/api/movies`,
      headers: { Authorization: `Bearer ${token}` },
      method: 'get',
    });

    let userMovieRelations = await axios ({
      url:`${process.env.API_URL}/api/user-movies?userId=${id}`,
      headers: { Authorization: `Bearer ${token}`},
      method: 'get'
    });

    userMovieRelations = userMovieRelations.data.data;
    movieList = movieList.data.data;

    const userMovieList = movieList.filter(
      movie => {
        let isUserMovie = userMovieRelations.some(
          userHas => userHas.movieId == movie._id
        );
      return isUserMovie;
      }
    );

    initialState = {
      user: {
        id, email, name,
    },
    myList: userMovieList,
    trends: movieList.filter(movie => movie.contentRating === 'PG' && movie._id),
    originals: movieList.filter(movie => movie.contentRating === 'G' && movie._id)
    };
  } catch(err){
    initialState = {
      user: {},
      myList: [],
      trends: [],
      originals: []
    }
  }
// [...]

Terminé haciendo un pequeño tweak a la lib de mongo, porque para borrar le estoy pasando, en este caso, userMovieId y movieId juntos, en vez de pasar el id identificatorio del usermovie (lo quise hacer, pero no me salió. Mi idea era evitar hacer OTRO get).

  delete(collection, data) {
    
    let finalData;
    
    if (data.userMovieId) {
      const { userMovieId, userId } = data;
      finalData = { movieId: userMovieId, userId}
    } else {
      finalData = { _id: id }
    }

    return this.connect().then(db => {
      return db.collection(collection).deleteOne(finalData)
    }).then(() => { finalData });
  }
}

Reto finalizado!!!
POST

Primero cree el action para agregar mi pelicula a la lista de favoritos.

export const userMovies = ({ id, userId, cover, title, year, contentRating, duration }) => {
  return (dispatch) => {
    axios({
      url: '/user-movies',
      method: 'post',
      data: {
        movieId: id,
        userId,
      },
    })
      .then(() => {
        dispatch(setFavorite({ id, cover, title, year, contentRating, duration }));
      })
      .catch((error) => {
        dispatch(setError(error));
      });
  };
};

En carouselItem modifiqué la función handleSetFavorite para sacar el id del usuario y antes de cualquier cosa validar en el estado actual que no esté la película en los favoritos.

const handleSetFavorite = () => {
    const userId = document.cookie.match(/[0-9a-fA-F]{24}/)[0];
    const idExist = mylist.some((item) => item._id === id);

    if (!idExist) {
      userMovies({
        id, userId, cover, title, year, contentRating, duration,
      }, mylist);
    }
  };

GET
Para obtener las peliculas hice una petición a la API en renderApp,
y con la function getUserMovies extraje la pelicula con el id y la guardé en myMovies la cual luego pasé a mylist.

let { data: { data: favorites } } = await axios({
      url: `${config.apiUrl}/api/user-movies?userId=${id}`,
      method: 'get',
      headers: { Authorization: `Bearer ${token}`},
    });

const myMovies = getUserMovie(movieList, favorites);

DELETE
para el delete lo unico que hice fué crear un action

export const deleteUserMovies = (id) => {
  return (dispatch) => {
    axios({
      url: `/user-movies/${id}`,
      method: 'delete',
    })
      .then(() => {
        dispatch(deleteFavorite(id));
      })
      .catch(() => {
        dispatch(setError(error));
      });
  };
};```

Hola, los fetch siempre se tienen que utilizar de lado de servidor? o cual seria la forma correcta de configurar axios del frontend para hacer requests a otras routas del API sin tener que enviar el token manualmente en cada petición?

Reto cumplido: agregar, eliminar y mostrar desde la BD, favoritos! 😄

<h3>Aquí mi solución en Github al reto.</h3>

.
Como varios compañeros comentaban tuvo su grado de dificultad sobre todo en la parte de eliminar la película de la base cuando el usuario la quitara de su lista.
.
En mi caso lo resolví agregándole una propiedad al objeto de la película añadida a Mi Lista para poder tener rastro del _id de creación en la base para así poder llamarlo en la función para borrar.
.
Saludos.

gracias!

1- Lo primero es crearle un id valido a cada movie(el id debe ser creado basandose en el schema que recibe el Api Server para poder crear las movies de usuarios).

2- Modificar el CarouselItem que es donde se oprime el boton de enviar a favoritos (hay que hacer un mapStateToProps para traer a user y mylist).

<
const { id, cover, title, year, contentRating, duration, isList, mylist, user } = props;
    const handleSetFavorite = () => {
        const movieExist = mylist.find(movie => movie.id === id)
        if (!movieExist) {
            const movie = { id, cover, title, year, contentRating, duration }
            props.postFavorite(movie.id, movie, user)
        }
    }
>

3- Crear el action de postFavorite.

<
export const postFavorite = (id, movie, user) => {
    return (dispatch) => {
        const userMovie = {
            userId: user.id,
            movieId: id
        }
        axios.post('/user-movies', userMovie)
            .then(() => dispatch(setFavorite(movie)))
            .catch(error => dispatch(setError(error)))
    }
}
>

4- En el archivo src/server/server.js traer la ruta que se hizo para crear los user-movies app.post("/user-movies"…) .

<
app.post("/user-movies", async function (req, res, next) {
    try {
        const { body: userMovie } = req;
        const { token } = req.cookies;

        const { data, status } = await axios({
            url: `${config.apiUrl}/api/user-movies`,
            headers: { Authorization: `Bearer ${token}` },
            method: 'post',
            data: userMovie
        });

        if (status !== 201) {
            return next(boom.badImplementation());
        }

        res.status(201).json(data);

    } catch (error) {
        next(error)
    }
});
>

5- Crear el action de removeFavorite y en el componente CarouselItem modificar el handleDeleteFavorite para que llame a este action.

<
export const removeFavorite = (userMovieId) => {
    return (dispatch) => {
        axios.delete(`/user-movies/${userMovieId}`)
            .then(({ status }) => {
                if (status === 200) {
                    dispatch(deleteFavorite(userMovieId))
                }
            })
            .catch(error => dispatch(setError(error)))
    }
}
>

6- En el archivo src/server/server.js traer la ruta que se hizo para eliminar los user-movies app.delete("/user-movies/:userMovieId"…) y hacer las modificaciones correspondientes para que puedan entrar los valores que mandamos del action. Esto es porque el userMovieId que viene del action en los params no es el id que se va a eliminar en la base de datos, el id que se va a eliminar de la base de datos es un id que mongoDB crea cuando un usuario añade una movie a favoritos.

<
app.delete("/user-movies/:userMovieId", async function (req, res, next) {
    try {
        const { userMovieId } = req.params;
        const { token, id } = req.cookies;

        let userMovies = await axios({
            url: `${config.apiUrl}/api/user-movies/?userId=${id}`,
            headers: { Authorization: `Bearer ${token}` },
            method: 'get'
        });

        userMovies = userMovies.data.data;

        const arrayMovies = userMovies.filter(movie => movie.movieId === userMovieId);

        const { data, status } = await axios({
            url: `${config.apiUrl}/api/user-movies/${arrayMovies[0]._id}`,
            headers: { Authorization: `Bearer ${token}` },
            method: 'delete'
        });

        if (status !== 200) {
            return next(boom.badImplementation());
        }

        res.status(200).json(data);

    } catch (error) {
        next(error)
    }
});
>

7- En el archivo src/server/server.js modificar el renderApp para que ademas de traer los originals y los trends, tambien me traiga mylist.

<
const renderApp = async (req, res) => {

    let initialState;
    const { token, email, name, id } = req.cookies;

    try {
        let movieList = await axios({
            url: `${config.apiUrl}/api/movies`,
            headers: { Authorization: `Bearer ${token}` },
            method: 'get'
        });
        movieList = movieList.data.data;

        let userList = await axios({
            url: `${config.apiUrl}/api/user-movies/?userId=${id}`,
            headers: { Authorization: `Bearer ${token}` },
            method: 'get'
        });
        userList = userList.data.data;

        initialState = {
            user: {
                id, email, name
            },
            mylist: userList.map(movie => movieList.find(x => x.id === movie.movieId)),
            trends: movieList.filter(movie => movie.contentRating === 'PG' || movie.contentRating === 'R' && movie._id),
            originals: movieList.filter(movie => movie.contentRating === 'G' && movie._id),
            searchContent: []
        }

    } catch (error) {
        initialState = {
            user: {},
            mylist: [],
            trends: [],
            originals: [],
            searchContent: []
        }
    }

    const store = createStore(reducer, initialState);
    const preloadedState = store.getState();
    const isLogged = (initialState.user.id);
    
    const html = renderToString(
        <Provider store={store}>
            <StaticRouter location={req.url} context={{}}>
                <Layout>
                    {renderRoutes(serverRoutes(isLogged))}
                </Layout>
            </StaticRouter>
        </Provider>,
    );

    res.set("Content-Security-Policy", "img-src * 'self' data: https:;");
    res.send(setResponse(html, preloadedState, req.hashManifest));
}
>