I've started to learn Redux with React, I got stuck in a part of my project which is a movies store that when the user chooses the number of tickets and clicks on add to cart I want to update the quantity and add the movie to the bag.
I implemented some redux in my code but the functionalities are not working?
What do you think?
<-- Constant-->
export const ActionsTypes = {
SET_MOVIES : "SET_MOVIES",
GET_MOVIE : "GET_MOVIE",
REMOVE_MOVIE : "REMOVE_MOVIE",
QUANTITY: "QUANTITY",
ADD_PRODUCT : "ADD_PRODUCT",
}
<-- My Actions -->
import {ActionsTypes} from "../Constants/ActionsTypes";
// create actions functions
export const setMovies = (movies)=> {
return {
type : ActionsTypes.SET_MOVIES,
payload : movies,
}
}
export const getMovie = (movie) => {
return {
type : ActionsTypes.GET_MOVIE,
payload: movie,
}
}
export const removeMovie = () => {
return {
type : ActionsTypes.REMOVE_MOVIE,
}
}
export const AddProduct = (singleMovie) => {
return {
type : ActionsTypes.ADD_PRODUCT,
payload: singleMovie,
}
}
<-- Reducers --->
import { ActionsTypes } from "../Constants/ActionsTypes";
const initialState = {
movies: [],
quantity :0,
};
// Reducers function takes two arguments state, action
//Movies Reducers
export const setMoviesReducers = (state = initialState, action) => {
switch (action.type) {
case ActionsTypes.SET_MOVIES:
return {...state, movies: action.payload }
default:
return state;
}
}
// single Movies Reducers
export const GetMovieDetailsReducers = (state={}, action) => {
switch(action.type) {
case ActionsTypes.GET_MOVIE :
return {...state, ...action.payload}
case ActionsTypes.REMOVE_MOVIE :
return {};
default :
return state;
}
}
// Add to card and Quatity reducers
export const AddMovieToCart = (state = initialState, action) => {
switch (action.type) {
case ActionsTypes.ADD_PRODUCT :
return {...state, quantity: state.quantity+1, products:[...state.products,action.payload]};
default :
return state;
}
}
<-- Combine reducers-->
import {combineReducers} from "redux";
import { setMoviesReducers, GetMovieDetailsReducers, AddMovieToCart} from "./MoviesReducers";
export const reducers = combineReducers({
allMovies : setMoviesReducers,
movie : GetMovieDetailsReducers,
addToCart : AddMovieToCart,
});
<-- Movie details component-->
import React, {useEffect, useState} from 'react'
import { Add, Remove } from '#material-ui/icons';
import './MovieDetails.css';
import { useSelector, useDispatch } from 'react-redux';
import { useParams } from 'react-router';
import { getMovie, removeMovie, AddProduct} from '../../Redux/Actions/Actions';
import axios from 'axios';
const MovieDetails = () => {
const [quantity, setQuantity] = useState(1)
const singleMovie = useSelector((state)=> state.movie);
const addToCardProduct = useSelector((state)=> state.addToCart);
console.log(addToCardProduct);
const {title, poster_path, overview} = singleMovie;
const dispatch = useDispatch();
let {movieId} = useParams();
console.log(movieId);
// Handle Quantity
const handleQuantity = (type) => {
if(type === "dec") {
quantity > 1 && setQuantity(quantity - 1)
} else {
setQuantity(quantity + 1)
}
}
// add to cart Handler
const CartHandler = () => {
dispatch(addToCardProduct);
}
// Get a single Product & Remove product
useEffect(()=> {
try {
const getSingleMovie = async () => {
const request = await axios.get(`https://api.themoviedb.org/3/movie/${movieId});
const response = await request.data;
dispatch(getMovie(response))
}
getSingleMovie();
} catch(error) {
console.log(`ERROR : ${error}`)
}
//Clean up
return () => {
dispatch(removeMovie());
}
}, [movieId])
return (
<section className="movieDetails_container">
<div className="wrapper">
<div className="img-container">
<img src={`${ImgPath}` + poster_path} alt={title}/>
</div>
<div className="info-container">
<h1>{title}</h1>
<p>{overview}</p>
<div className="quantity-container">
<Remove className="quantity-icon" onClick={()=> handleQuantity("dec")}/>
<span className="amount">{quantity}</span>
<Add className="quantity-icon" onClick={()=> handleQuantity("incr")}/>
</div>
<button className="btn-add" onClick={CartHandler} >Add To Cart</button>
</div>
</div>
</section>
)
}
export default MovieDetails
<---Navbar where I have my bag icon and where I want to show the quantity-->
import React from 'react'
import './Navbar.css';
import { Link } from 'react-router-dom';
import { Badge } from '#material-ui/core';
import { LocalMall } from '#material-ui/icons';
import { useSelector } from 'react-redux';
const Navbar = () => {
const quantityBag = useSelector((state)=> state.quantity);
return (
<nav className="navBar-section">
<Link to="/">
<div className="logo-container">
<img className="logo" src="./Pictures/warner-bros.png" alt="Logo"/>
</div>
</Link>
<Badge badgeContent={0} color="primary">
<LocalMall className="icon-bag" />
</Badge>
</nav>
)
}
export default Navbar
Related
I am making React application with Typescript, React Query and Recoil. I don't know why I am getting this error in the terminal. If u want more information (more code) of something like that to find the solution, I will update question.
import { atom } from 'recoil';
export const Books = atom({
key: 'book',
default: []
})
import { useQuery } from "react-query";
import axios from 'axios';
const fetchBooks = async (pageNumber: number) => {
const res = await axios.get(`http://localhost:3001/api/book?page=${pageNumber}`);
return res.data
}
export const useGetBooks = (pageNumber: number, setBooks: any) => {
return useQuery(['books', pageNumber], () => fetchBooks(pageNumber),
{
onSuccess: (data) => setBooks(data),
keepPreviousData: true
})
}
import { useGetBooks } from '../../hooks/useGetBooks';
import { BookType } from '../../types/Book';
import { SingleBook } from './SingleBook';
import styled from 'styled-components';
import { Navbar } from './Navbar';
import { Loader } from '../utilities/Loader';
import { Error } from '../utilities/Error';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { Books } from '../../recoil/globalState';
type bookType = BookType;
export const BookList = () => {
const [pageNumber, setPageNumber] = useState(1);
const [books, setBooks] = useRecoilState(Books);
const { isLoading, isError, data } = useGetBooks(pageNumber, setBooks);
if (isLoading) {
return <Loader isLoading={isLoading} />
}
if (isError) {
return <Error />
}
const displayBooks = books.data.map((book: bookType) => {
return (
<SingleBook key={book.id} book={book} />
)
})
return (
<BookContainer>
<div className='test'>
<button onClick={() => setPageNumber((page) => page - 1)} disabled={pageNumber == 1}>Prev page</button>
<p>{pageNumber}</p>
<button onClick={() => setPageNumber((page) => page + 1)} disabled={data.metadata.records_per_page * data.metadata.page > data.metadata.total_records}>Next page</button>
</div>
<BookContent>
<Navbar />
{displayBooks}
</BookContent>
</BookContainer>
)
}
How to link react with redux to add and remove selected movies to the array by clicking on the heart to display them on the favoŠ³ites page
Bellow my redux:
action:
export function addMovie(id) {
return {
type: 'ADD_MOVIE',
payload: id
}
}
export function removeMovie(id) {
return {
type: 'REMOVE_MOVIE',
payload: id
}
}
reducer:
function favouriteReducer(state = [], action) {
switch(action.type) {
case 'ADD_MOVIE':
return {
items: state.items.concat(action.payload)
}
case 'REMOVE_MOVIE':
return {
items: state.items.filter(item => item !== action.payload)
}
default:
return state;
}
}
export default favouriteReducer;
store:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import favouriteReducer from "./reducers/favouriteReducer";
const redusers = combineReducers({
favourite: favouriteReducer,
});
const store = createStore(redusers, composeWithDevTools(applyMiddleware(thunk)));
export default store;
and my component, where i need to click to add or remove chosen movie
import React, { useEffect, useState } from "react";
import './index.css';
import { Link, useParams } from "react-router-dom";
import { connect } from 'react-redux';
import { addMovie } from "../../../../redux/actions/favouriteMovie";
import store from "../../../../redux";
const MoviesContext = React.createContext();
const { Provider } = MoviesContext;
const Movie = (props, {addMovie, movies}) => {
const [active, setActive] = useState(false)
let onClick = (e) => {
e.preventDefault();
console.log('hello');
setActive(!active);
addMovie(movies);
}
return (
<Provider store={{movies, addMovie}}>
<Link to= {"/modal/:"+`${props.id}`} id={props.id} className={'movie'}>
<img src={"https://image.tmdb.org/t/p/w500/"+props.poster_path} alt={props.title} className={'main'}/>
<h1 className={'title'}>{props.title}</h1>
<div>
<svg className={active ? 'activeimg' : 'heart'} onClick={onClick} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 4.248c-3.148-5.402-12-3.825-12 2.944 0 4.661 5.571 9.427 12 15.808 6.43-6.381 12-11.147 12-15.808 0-6.792-8.875-8.306-12-2.944z"/></svg>
<p className={'text'}>{props.release_date}</p>
</div>
</Link>
</Provider>
);
};
export {
MoviesContext
};
function mapStateToProps(state){
return {
movies: state.movie.movies
}
}
const mapDispatchToProps = {
addMovie: addMovie,
}
export default connect(mapStateToProps, mapDispatchToProps)(Movie);
I don't understand how to do it in component.
Thank you very much for your help!
You need to change your "mapDispatchToProps" to have a dispatch as params
but why this mixt between context and redux ?
import React, { useEffect, useState } from 'react';
import './index.css';
import { Link, useParams } from 'react-router-dom';
import { connect } from 'react-redux';
import { addMovie } from '../../../../redux/actions/favouriteMovie';
import store from '../../../../redux';
const MoviesContext = React.createContext();
const { Provider } = MoviesContext;
const Movie = (props, { addMovie, movies }) => {
const [active, setActive] = useState(false);
const onClick = (id) => {
console.log('hello');
setActive(!active);
props.addMovie(id);
};
return (
<Provider store={{ movies, addMovie }}>
<Link to={'/modal/:' + `${props.id}`} id={props.id} className={'movie'}>
<img src={'https://image.tmdb.org/t/p/w500/' + props.poster_path} alt={props.title} className={'main'} />
<h1 className={'title'}>{props.title}</h1>
<div>
<svg className={active ? 'activeimg' : 'heart'} onClick={() => onClick(props.id)} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 4.248c-3.148-5.402-12-3.825-12 2.944 0 4.661 5.571 9.427 12 15.808 6.43-6.381 12-11.147 12-15.808 0-6.792-8.875-8.306-12-2.944z" />
</svg>
<p className={'text'}>{props.release_date}</p>
</div>
</Link>
</Provider>
);
};
export { MoviesContext };
function mapStateToProps(state) {
return {
movies: state.movie.movies,
};
}
const mapDispatchToProps = {
addMovie: addMovie,
};
const mapDispatchToProps = (dispatch) => {
return {
addMovie: (id) => dispatch(addMovie(id)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Movie);
I have a simple search aplication, where depending by user input, it get the result on front end.
My redux code:
import { persons } from "./persons";
import { createStore } from "redux";
//contant
export const SEARCH = {
SEARCH_PERSON: "SEARCH_PERSON"
};
//action
export const searchPersonAction = (person) => {
const personSearched = persons.filter((p) =>
p.name.toLowerCase().includes(person.toLowerCase())
);
return {
type: SEARCH.SEARCH_PERSON,
payload: personSearched
};
};
//reducer
const initialState = {
name: persons
};
export const search = (state = initialState, { type, payload }) => {
switch (type) {
case SEARCH.SEARCH_PERSON:
return {
...state,
name: payload
};
default:
return state;
}
};
//store
export const store = createStore(search);
UI component:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "./store";
const Search = () => {
const dispatch = useDispatch();
const selector = useSelector((s) => s);
const search = (e) => {
const txt = e.target.value;
dispatch(searchPersonAction(txt));
};
return (
<div>
<input onChange={search} placeholder="search" />
<ul>
{selector.name.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
</div>
);
};
export default Search;
Now the application works properly, but i want to integrate in my application reselect library. I want to use reselect in filter logic.
Question: Which changes should i add in my application code?
demo: https://codesandbox.io/s/brave-monad-litc1?file=/src/Search.js:0-577
You can wrap the function in useSelector into createSelector from reselect which will memoise the selector values. You can do it like this:
import React, { useEffect } from "react";
import { createSelector } from "reselect";
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "./store";
const memoiseSelector = createSelector(
(s) => s.name,
(name) => name
);
const Search = () => {
const dispatch = useDispatch();
const name = useSelector(memoiseSelector);
const search = (e) => {
const txt = e.target.value;
dispatch(searchPersonAction(txt));
};
return (
<div>
<input onChange={search} placeholder="search" />
<ul>
{name?.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
</div>
);
};
export default Search;
Further more examples you can check here on official docs.
I have to set a value on from a API into a newly created <button> component handled by Redux, but I don't know if I can use setState for this. I created a reducer and an action SET_VOTE_COUNT but I'm not seeing how this is done. This is my first Redux project, so here is the code:
// ./src/js/components/CounterList.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from '../actions/reducer';
import Counter from './Counter';
const CounterList = ({
counters,
onIncrement,
onDecrement
}) => (
<ul>
{counters.map(counter =>
<Counter style={{div: "voting"}}
key={counter.id}
value={counter.count}
onIncrement={() => onIncrement(counter.id)}
onDecrement={() => onDecrement(counter.id)}
/>
)}
</ul>
);
const mapStateToProps = (state) => {
return {
counters: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(increment(id)),
onDecrement: (id) => dispatch(decrement(id))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(CounterList);
// ./src/js/components/Counter.js
import React, { Component } from 'react';
class Counter extends Component {
render() {
return (
<div className="voting">
<span>{this.props.value}</span>
<button
onClick={() => this.props.onIncrement()}>
+
</button>
<button
onClick={() => this.props.onDecrement()}>
-
</button>
</div>
);
}
}
export default Counter;
import React, {Component} from 'react';
import logo from '../../logo.svg';
import '../../App.css';
import AddButton from './AddButton'
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
response: ''
};
}
componentDidMount() {
fetch(
"/posts"
).then(response => response.json())
.then(data => this.setState({ response: data }))
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{Array.isArray(this.state.response) &&
this.state.response.map(resIndex => <>
{ resIndex.voteScore}
<AddButton className="voting"/>
<p> { resIndex.title }, by { resIndex.author } </p>
<p> { resIndex.body } </p>
<p> {resIndex.category} </p>
</>
)}
</header>
</div>
)
}
}
export default Posts;
import React from 'react';
import { add_counter, setVoteCount } from '../actions/reducer';
import { connect } from 'react-redux';
const AddButton = ({dispatch}) => (
<div className="voting">
<button
onClick={() => {
dispatch(setVoteCount())
// dispatch(add_counter());
}}>
Vote
</button>
</div>
);
export default connect()(AddButton);
The reducer:
// ./src/js/actions/counters.js
export const setVoteCount = (id) => {
return {
type: "SET_VOTE_COUNT",
id
};
}
export const increment = (id) => {
return {
type: "INCREMENT",
id
};
};
export const decrement = (id) => {
return {
type: "DECREMENT",
id
};
};
export const add_counter = () => {
return {
type: "ADD_COUNTER"
};
};
store action:
import { createStore, applyMiddleware, compose } from 'redux';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const change_counter = (state = {}, action) => {
switch (action.type) {
case "SET_VOTE_COUNT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count : 37
}
case "INCREMENT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count: state.count+1
};
case "DECREMENT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count: state.count - 1
};
default:
return state;
}
};
let nextId = 0;
const counters = (state = [], action) => {
switch (action.type) {
case "ADD_COUNTER":
return [...state, {id: nextId++, count: 0}];
case "SET_VOTE_COUNT":
return [...state, {id: nextId++, count: action.count}];
case "INCREMENT":
return state.map(counter => change_counter(counter, action));
case "DECREMENT":
return state.map(counter => change_counter(counter, action));
default:
return state;
}
}
export default createStore(counters, composeEnhancers(applyMiddleware()));
I can upload it to GitHub if necessary. Many thanks.
In the AddButton component,the actions should be wrapped in mapDispatchToProps and passed to the connect function. You are calling the raw action in your example, but you need to wrap it with dispatch for it to update the store.
However, I'm not sure what you are trying to update the store with exactly. The action payload is empty in your example, and the reducer has 37 hardcoded as the state.count in response the SET_VOTE_COUNT action type. Did you mean to pass something from the API response?
<AddButton count={resIndex.count} className="voting"/>
import React from 'react';
import { add_counter, setVoteCount } from '../actions/reducer';
import { connect } from 'react-redux';
const mapDispatchToProps = {
setVoteCount
};
const AddButton = props => (
<div className="voting">
<button onClick={() => {
props.setVoteCount(props.count);
}}>
Vote
</button>
</div>
);
export default connect(null, mapDispatchToProps)(AddButton);
I would like ask you about passing object to Redux.
Below is my code.
// src/actions/writingType.js
export const write = () => ({
type: 'WRITE',
})
export const update = (obj) => ({
type: 'UPDATE',
obj
})
// src/reducers/writingType.js
const initialState = {
writingType: "WRITE",
obj: null
}
const writingTypeReducer = (state = initialState, action) => {
console.log('\n inside writingTypeReducer');
console.log(action);
switch (action.type) {
case 'WRITE':
return {
...state,
writingType: 'WRITE'
};
case 'UPDATE':
return {
...state,
writingType: 'UPDATE',
obj: action.obj
};
default:
return state;
}
}
export default writingTypeReducer;
// Contentview.js
import React, { useContext } from 'react';
import { Route, Link } from 'react-router-dom';
import MarkdownRenderer from 'react-markdown-renderer';
import './Contentview.css';
import { connect } from 'react-redux'
import { write, update } from '../../actions/writingType'
import { UserConsumer } from '../../contexts/userContext';
import { Test } from '../../contexts/Test';
const Contentview = (props) => {
/*
category: "React"
contentObj:
contents: "something"
createdDatetime: "2019.10.26 08:52:05"
title: "something"
wikiIndex: 1
*/
console.log('\n Contentview');
console.log(props);
console.log('\n update(props.contentObj);');
update(props.contentObj);
const url = "/editor/" + props.category;
const updateUrl = "/update/" + props.category;
return (
<div>
<div className="categoryDiv">{props.category}</div>
<div className="writingDiv"><Link to={url}> A </Link></div>
<div className="updateDiv"><Link to={updateUrl} > B </Link></div>
<hr />
<MarkdownRenderer markdown={props.contentObj.contents} />
</div>
);
};
// export default Contentview;
const mapStateToProps = (state, props) => ({
writetypestate: state.writingType,
obj: props.contentObj
})
const mapDispatchToProps = dispatch => ({
write: () => dispatch(write()),
update: (obj) => {
console.log('Contentview, mapDispatchToProps, update');
dispatch(update(obj))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Contentview)
I used update(props.contentObj); in Contentview.js to pass props.contentObj to Redux and update obj of initialState in src/reducers/writingType.js. But obj of initialState hasn't changed and existed as null.
How should I change code?
Thank you.
use props.update to call in the main file
// Contentview.js
import React, { useContext } from 'react';
import { Route, Link } from 'react-router-dom';
import MarkdownRenderer from 'react-markdown-renderer';
import './Contentview.css';
import { connect } from 'react-redux'
import { write, update } from '../../actions/writingType'
import { UserConsumer } from '../../contexts/userContext';
import { Test } from '../../contexts/Test';
const Contentview = (props) => {
/*
category: "React"
contentObj:
contents: "something"
createdDatetime: "2019.10.26 08:52:05"
title: "something"
wikiIndex: 1
*/
console.log('\n Contentview');
console.log(props);
console.log('\n update(props.contentObj);');
props.update(props.contentObj);
const url = "/editor/" + props.category;
const updateUrl = "/update/" + props.category;
return (
<div>
<div className="categoryDiv">{props.category}</div>
<div className="writingDiv"><Link to={url}> A </Link></div>
<div className="updateDiv"><Link to={updateUrl} > B </Link></div>
<hr />
<MarkdownRenderer markdown={props.contentObj.contents} />
</div>
);
};
// export default Contentview;
const mapStateToProps = (state, props) => ({
writetypestate: state.writingType,
obj: props.contentObj
})
const mapDispatchToProps = dispatch => ({
write: () => dispatch(write()),
update: (obj) => {
console.log('Contentview, mapDispatchToProps, update');
dispatch(update(obj))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Contentview)
Please use the above code