I am following a tutorial trying to learn Redux. I got the first action working, which is a simple GET API call, but am stuck on the next action I'm trying to create. The code looks like the following:
In the Component:
class ShoppingList extends Component {
componentDidMount() {
this.props.getItems();
}
handleClick = id => {
console.log("component " + id);
this.props.deleteItem(id);
};
render() {
const { items } = this.props.item;
return (
<Container>
<ListGroup>
<TransitionGroup className="shoppingList">
{items.map(({ id, name }) => (
<CSSTransition key={id} timeout={500} classNames="fade">
<ListGroupItem>
<Button
className="button1"
color="danger"
size="sm"
onClick={e => this.handleClick(id, e)}
>
×
</Button>
{name}
</ListGroupItem>
</CSSTransition>
))}
</TransitionGroup>
</ListGroup>
</Container>
);
}
}
ShoppingList.propTypes = {
getItems: PropTypes.func.isRequired,
item: PropTypes.object.isRequired,
deleteItem: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
item: state.item
});
export default connect(mapStateToProps, { getItems, deleteItem })(ShoppingList);
In my reducer:
const initialState = {
items: [
{ id: 3, name: "Eggs" },
{ id: 4, name: "Milk" },
{ id: 5, name: "Steak" },
{ id: 6, name: "Water" }
]
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_ITEMS:
return {
...state
};
case DELETE_ITEM:
console.log("reducer");
return {
...state,
items: state.items.filter(item => item.id !== action.id)
};
default:
return state;
}
}
In my actions file:
export const getItems = () => {
return {
type: GET_ITEMS
};
};
export const deleteItem = id => {
console.log("actions");
return {
type: DELETE_ITEM,
payload: id
};
};
However, when I click on the button to try to delete an item from the list, nothing happens. I can see in the Redux console that the action is being dispatched, however it seems to have no effect. Any suggestions?
You have in deleteItem action { type, payload }. Instead you can have { type, id } or using payload in the reducer return statement.
I would do the following - so you are passing the id with the action instead of payload:
export const deleteItem = id => {
console.log("actions");
return {
type: DELETE_ITEM,
id
};
};
Or the best option for later purposes - keep payload just adding id as property:
// action
export const deleteItem = id => {
console.log("actions");
return {
type: DELETE_ITEM,
payload: { id }
};
};
// reducer
case DELETE_ITEM:
// here destructuring the property from payload
const { id } = action.payload;
return {
...state,
items: state.items.filter(item => item.id !== id)
};
I hope this helps!
Related
i cannot delete a product, although it seems that everything looks fine and i see in console that action of type: REMOVE_SELECTED_PRODUCT occurs, the product is still in array and seems like the state is not updated??
So lets start with productActions.js
export const removeSelectedProduct = (id) => {
return {
type: REMOVE_SELECTED_PRODUCT,
payload: id,
};
};
export const removeProduct = (id) => {
return async (dispatch) => {
dispatch(removeSelectedProduct(id));
try {
console.log(id); // this is for test, and i see in console that proper id is printed
await axios.delete(`https://fakestoreapi.com/products/${id}`);
} catch (err) {
console.log(err);
}
};
};
now the productsReducer.js
const intialState = {
products: [],
};
export const productsReducer = (state = intialState, { type, payload }) => {
switch (type) {
case SET_PRODUCTS:
return { ...state, products: payload };
case ADD_PRODUCT:
return { ...state, products: [...state.products, payload] };
case REMOVE_SELECTED_PRODUCT:
return {
...state,
products: state.products.filter((el) => el.id !== payload),
};
default:
return state;
}
};
And i use it in ProductDetails.js as an button so there is a whole code of this component:
const ProductDetails = ({ removeProduct, product }) => {
const { productId } = useParams();
const { image, title, price, category, description } = product;
const dispatch = useDispatch();
const fetchProductDetail = async (id) => {
const response = await axios
.get(`https://fakestoreapi.com/products/${id}`)
.catch((err) => {
console.log("Err: ", err);
});
dispatch(selectedProduct(response.data));
};
useEffect(() => {
if (productId && productId !== "") fetchProductDetail(productId);
}, [productId]);
return (
<div>
{Object.keys(product).length === 0 ? (
<div>...Loading</div>
) : (
<div>
<img alt={productId} src={image} />
<div>
<h1>{title}</h1>
<h2>
<p>${price}</p>
</h2>
<h3>{category}</h3>
<p>{description}</p>
<button>Add to Cart</button>
<button onClick={() => removeProduct(productId)}>Usuń</button>
</div>
</div>
)}
</div>
);
};
const mapStateToProps = (state) => {
return {
products: state.allProducts.products,
product: state.product,
};
};
const mapDispatchToProps = {
removeProduct,
selectedProduct,
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);
If anyone cant find the problem, i would be glad.
As an i said the acction and the id is printed to the console:
console output on clicking the button
Maybe there is some problem with state, and it dont update the state when i delete? Idk, please help me :(
As product has number type id and you're providing string i.e
2 !== "2"
Do this:
dispatch(removeSelectedProduct(+id));//change string to number
...
...
await axios.delete(`https://fakestoreapi.com/products/${+id}`);//similarly here
I've been trying to create this search app where I can display the items in a table and delete items using react redux. However, on the initial load, the app shows a table but there is no data in the table. It's an empty table. If i search for another movie name which have more than one movie for that search term, then 2 tables would be shown but I want to show everything on the same table itself. The delete button is not working as well. Is there something wrong with my action and reducer files?
Action.js
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "./types";
const fetchMoviePendig = () => ({
type: FETCH_MOVIE_PENDING
});
const fetchMovieSuccess = json => ({
type: FETCH_MOVIE_SUCCESS,
payload: json
});
const fetchMovieError = error => ({
type: FETCH_MOVIE_ERROR,
payload: error
});
export const fetchMovie = name => {
return async dispatch => {
dispatch(fetchMoviePendig());
try {
const url = `https://jsonmock.hackerrank.com/api/movies/search/?Title=${name}`;
const response = await fetch(url);
const result = await response.json(response);
console.log(result);
dispatch(fetchMovieSuccess(result.data));
} catch (error) {
dispatch(fetchMovieError(error));
}
};
};
export const deleteEvent = id => async dispatch => {
try {
dispatch({
type: DELETE_MOVIE,
payload: id
});
} catch (err) {
console.log(err);
}
};
Reducer
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "../action/types";
const initialState = {
data: [],
loading: false,
error: ""
};
const moviesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MOVIE_PENDING:
return {
...state,
loading: true
};
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: [...state.data, action.payload]
};
case FETCH_MOVIE_ERROR:
return {
...state,
loading: false,
error: action.payload
};
case DELETE_MOVIE:
return {
...state,
data: state.data.filter(movie => movie.id !== action.payload)
};
default:
return state;
}
};
export default moviesReducer;
App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovie } from "./action/movieActions";
import Input from "./components/Input";
import MovieTable from "./components/MovieTable";
class App extends Component {
state = {
searchInput: "The Rain"
};
componentDidMount() {
this.props.getMovieList(this.state.searchInput);
}
_getMovie = () => {
this.props.getMovieList(this.state.searchInput);
};
_onChangeHandler = e => {
this.setState({
searchInput: e.target.value
});
console.log(this.state.searchInput);
};
render() {
const { data, loading } = this.props.movies;
return (
<div className="center">
<div>
<h2 className="center white-text">Movie Search</h2>
</div>
<div className="container">
<Input
value={this.state.searchInput}
onChange={this._onChangeHandler}
onClick={this._getMovie}
/>
<div className="row">
{loading ? (
<p>Loading</p>
) : (
data.map(item => (
<MovieTable
key={item.imdbID}
year={item.Year}
name={item.Title}
movieId={item.imdbId}
/>
))
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
movies: state.movies
};
};
const mapDispatchToProps = dispatch => {
return {
getMovieList: name => dispatch(fetchMovie(name))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Hello please take a look at the sandbox : https://codesandbox.io/s/prod-wind-4hgq2?file=/src/App.js
I have edited
<MovieTable
data={data.map(d => ({
year: d.Year,
name: d.Title,
movieId: d.imdbId
}))}
/>
and
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: action.payload
};
And ... Currently the delete button has no event, that's why it can't work
I see data having the following pattern:
Object {page: 1, per_page: 10, total: 1, total_pages: 1, data: Array[1]}
page: 1
per_page: 10
total: 1
total_pages: 1
data: Array[1]
0: Object
Title: "Sin in the Rain"
Year: 2006
imdbID: "tt1072449"
And you are accessing wrong properties in the component render logic, can you fix that.
Duplicate table is created the way you have written the logic.
Pass the data to MovieTable component and let it render and create the table
and fill it.
In reducer (FETCH_MOVIE_SUCCESS) you need don't need to append data you have to
replace or use the current movie data only.
this is file list.js
...
class List extends React.Component {
render() {
return (
...
{this.props.todoList.map((todo, index) => <Item {...todo} key = {index}/>)} // err this code
...
);
}
};
const mapStateToProps = (state) => {
return {
todoList: state.todos
};
};
...
this is file rootReducers.js
const initialState = {
todos : [
{ id: 1, name: "Khoa" },
{ id: 2, name: "Khoai" },
{ id: 3, name: "Kha" }
],
currentName : ''
}
const TodoList = (state = initialState, action) => {
switch (action.type) {
case "ADD_TODO":
return [...state.todos, { id: state.todos.length + 1, name: action.text }];
default:
return state;
}
};
...
this is action.js
export const addTodo = (text) =>{
return {
type : 'ADD_TODO',
text
}
}
this is file fromCreate.js
...
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (text) => dispatch(addTodo(text))
};
};
...
i'm loading data success but, when i excute event addtoto it message : Cannot read property 'map' of undefined. Help me
Your reducer code seems wrong. You need to return full new state slice from it not just an array.
const TodoList = (state = initialState, action) => {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [...state.todos, { id: state.todos.length + 1, name: action.text }]
};
default:
return state;
}
};
I have a prop in my file canPurchase that I want to update (toggle) using the reducer.
My application is working when the logic to update the prop canPurchase is in the same file however when I move the logic out into the reducer it does not work. What am I doing wrong?
burgerbuilder.js file:
class BurgerBuilder extends Component {
state = {
orderInProgress: false,
loading: false,
error: false
};
render() {
let burger = this.state.error ? (
<p>Ingredients can't be loaded </p>
) : (
<Spinner />
);
burger = (
<Aux>
<Burger ingredients={this.props.ings} />
<BuildControls
ingredientAdded={this.props.onIngredientAdded}
ingredientRemoved={this.props.onIngredientRemoved}
disabled={disabledInfo}
canPurchase={updatePurchaseState(this.props.ings)}
price={this.props.price}
ordered={this.orderInProgressHandler}
/>
</Aux>
);
}
return (
<Aux>
<Modal
show={this.state.orderInProgress}
modalClosed={this.orderCancelHandler}
/>
{burger}
</Aux>
);
}
}
/
const mapStateToProps = state => {
return {
ings: state.ingredients,
price: state.totalPrice,
canPurchase: state.canPurchase
};
};
const mapDispatchToProps = dispatch => {
return {
onIngredientAdded: ingNamePayload =>
dispatch({ type: actionTypes.ADD_INGREDIENT, payload: ingNamePayload }),
onIngredientRemoved: ingNamePayload =>
dispatch({ type: actionTypes.REMOVE_INGREDIENT, payload: ingNamePayload })
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(withErrorHandler(BurgerBuilder, axios));
reducer.js
import PropTypes from "react";
const initialState = {
ingredients: {
salad: 0,
bacon: 0,
cheese: 0,
meat: 0
},
totalPrice: 0,
canPurchase: false
};
const INGREDIENT_PRICES = {
salad: 0.5,
cheese: 0.4,
meat: 1.3,
bacon: 0.7
};
const updatePurchaseState = ingredients => {
const sum = Object.keys(ingredients)
.map(igKey => {
return ingredients[igKey];
})
.reduce((sum, el) => {
return sum + el;
}, 0);
return sum > 0;
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_INGREDIENT:
return {
...state,
ingredients: {
...state.ingredients,
[action.payload]: state.ingredients[action.payload] + 1
},
totalPrice: state.totalPrice + INGREDIENT_PRICES[action.payload],
canPurchase: updatePurchaseState(state.ingredients)
};
default:
return state;
}
};
export default reducer;
the thing i am toggling
<button onClick={props.ordered} className={classes.OrderButton} disabled={!props.canPurchase}>
ORDER NOW
</button>`
if you need more information let me know.
thank you
{this.orderInProgressHandler} is being called as ordered props in BuildControls Component and used onClick button, where is it's definition?
This is just a sample code I am trying to control my controlled inputs using Redux, I add the Redux to my React project and add my reducer and action but everything works well except updating my component in one of my actions.
the following code is my Reducer:
import actionTypes from "./actions";
const uniqid = require("uniqid");
const firstID = uniqid();
const initialState = {
cons: [
{
value: "",
id: firstID,
added: false
}
],
pros: [
{
value: "",
id: firstID,
added: false
}
],
num: 0
};
const reducer = (state = initialState, action) => {
const newState = { ...state };
switch (action.type) {
case actionTypes.HANDLEINPUTCHANGE:
// const newState = state;
const changingItem = newState[action.case].find(item => {
return item.id === action.id;
});
const changingItemIndex = newState[action.case].findIndex(item => {
return item.id === action.id;
});
changingItem.value = action.event;
if (
changingItemIndex === newState[action.case].length - 1 &&
!changingItem.added
) {
alert(123);
const newItem = {
id: uniqid(),
value: "",
added: false
};
newState[action.case].push(newItem);
changingItem.added = true;
console.log(newState);
}
newState[action.case][changingItemIndex] = changingItem;
return newState;
case actionTypes.CLICK:
newState.num += 1;
return {
...newState
};
default:
return state;
}
};
export default reducer;
and the following code is my component, unfortunately, the HANDLEINPUTCHANGE action type did not update my component:
import React, { Component } from "react";
import FormElement from "../../base/components/formElement/FormElement";
import actionTypes from "../../base/store/actions";
import { connect } from "react-redux";
import "./style.scss";
class FormGenerator extends Component {
render() {
console.log(this.props);
return (
<ul className="row formGeneratorContainer fdiColumn">
<li onClick={this.props.click}>{this.props.num}</li>
{this.props[this.props.case].map((item, index) => {
return (
<li className="row formGeneratorItem" key={index}>
<div className="bullet d_flex jcCenter aiCenter">1</div>
{/* <FormElement onChange={(e,index,type,)}/> */}
<input
name={item.id}
type="text"
onChange={event =>
this.props.onFieldValueChange(
event.target.value,
index,
this.props.case,
item.id
)
}
/>
</li>
);
})}
</ul>
);
}
}
const mapStateToProps = state => {
return {
cons: state.cons,
pros: state.pros,
num: state.num
};
};
const mapDispachToProps = dispatch => {
return {
onFieldValueChange: (event, index, c, id) =>
dispatch({
event: event,
index: index,
case: c,
id: id,
type: actionTypes.HANDLEINPUTCHANGE
}),
click: () => dispatch({ type: actionTypes.CLICK })
};
};
export default connect(
mapStateToProps,
mapDispachToProps
)(FormGenerator);
You need to set value of your controlled component:
<input
name={item.id}
type="text"
value={item.value}
onChange={event =>
this.props.onFieldValueChange(
event.target.value,
index,
this.props.case,
item.id
)
}
/>
Other problems are in your reducer, you are mutating the redux state with these lines:
newState[action.case].push(newItem);
// ...
newState[action.case][changingItemIndex] = changingItem;
Look at these sections in the redux documentation:
Inserting and Removing Items in Arrays
Updating an Item in an Array