I have two functions that do about the same thing only in a different array in the state
For no apparent reason only one of them works
Only the addToCart function works
I can not understand why the addToWishList function does not work
Home component
import React, { useEffect, useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Card from "#material-ui/core/Card";
import CardActionArea from "#material-ui/core/CardActionArea";
import CardActions from "#material-ui/core/CardActions";
import CardContent from "#material-ui/core/CardContent";
import CardMedia from "#material-ui/core/CardMedia";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
import { useHistory } from "react-router-dom";
import { connect } from "react-redux";
import { addToCart } from ".././redux/Shopping/shopping-action";
import { addToWishList } from ".././redux/Shopping/shopping-action";
const useStyles = makeStyles({
root: {
maxWidth: 345,
},
media: {
height: 240,
},
});
function Home({ addToCart }) {
const classes = useStyles();
const history = useHistory();
const [products, setProducts] = useState([]);
useEffect(() => {
fetch("https://fakestoreapi.com/products")
.then((res) => res.json())
.then((json) => setProducts(json));
}, []);
return (
<div>
<h1>ALL PRODUCTS</h1>
<div className="cards ">
{products.map((product) => {
return (
<div className="card">
<Card className={classes.root}>
<CardActionArea
onClick={() => history.push(`/product/${product.id}`)}
>
<CardMedia className={classes.media} image={product.image} />
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{product.title}
</Typography>
<Typography
variant="body2"
color="textSecondary"
component="h2"
>
{product.category}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button
onClick={() => addToCart(product)}
size="small"
variant="contained"
color="primary"
>
ADD TO CART 🛒
</Button>
<Button
onClick={() => addToWishList(product)}
size="small"
variant="contained"
color="secondary"
>
Add to wish list ❤️
</Button>
</CardActions>
</Card>
</div>
);
})}
</div>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (product) => dispatch(addToCart(product)),
addToWishList: (product) => dispatch(addToWishList(product)),
};
};
export default connect(null, mapDispatchToProps)(Home);
shopping-action.js
import * as actionTypes from "./shopping-types";
export const addToCart = (item) => {
return {
type: actionTypes.ADD_TO_CART,
payload: {
item: item,
},
};
};
export const removeFromCart = (itemId) => {
return {
type: actionTypes.REMOVE_FROM_CART,
payload: {
id: itemId,
},
};
};
export const adjustQty = (itemId, value) => {
return {
type: actionTypes.ADJUST_QTY,
payload: {
id: itemId,
qty: value,
},
};
};
export const addToWishList = (item) => {
console.log(item);
return {
type: actionTypes.WISH_LIST,
payload: {
item: item,
},
};
};
shopping-reducer.js
import * as actionTypes from "./shopping-types";
const INITIAL_STATE = {
cart: [],
wishList: [],
};
const shopReducer = (state = INITIAL_STATE, action) => {
console.log(action.type);
switch (action.type) {
case actionTypes.ADD_TO_CART:
const item = action.payload.item;
const isInCart = state.cart.find((item) =>
item.id === action.payload.item.id ? true : false
);
return {
...state,
cart: isInCart
? state.cart.map((item) =>
item.id === action.payload.item.id
? { ...item, qty: item.qty + 1 }
: item
)
: [...state.cart, { ...item, qty: 1 }],
};
case actionTypes.REMOVE_FROM_CART:
return {
...state,
cart: state.cart.filter((item) => item.id !== action.payload.item.id),
};
case actionTypes.ADJUST_QTY:
return {
...state,
cart: state.cart.map((item) =>
item.id === action.payload.item.id
? { ...item, qty: action.payload.qty }
: item
),
};
case actionTypes.WISH_LIST:
const itemForWish = action.payload.item;
const isInWish = state.cart.find((item) =>
item.id === action.payload.item.id ? true : false
);
return {
...state,
wishList: isInWish ? null : [...state.wishList, { itemForWish }],
};
default:
return state;
}
};
export default shopReducer;
shopping-types.js
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const ADJUST_QTY = "ADJUST_QTY";
export const WISH_LIST = "WISH_LIST";
Thanks for your time!
You forgot to destructure the addToWishList prop that is injected by the connect HOC, so you are calling directly the action creator and not the one wrapped in a call to dispatch.
import { connect } from "react-redux";
import { addToCart } from ".././redux/Shopping/shopping-action";
import { addToWishList } from ".././redux/Shopping/shopping-action"; // <-- (1) imported
function Home({ addToCart }) { // <-- (3) not destructured
...
return (
<div>
<h1>ALL PRODUCTS</h1>
<div className="cards ">
{products.map((product) => {
return (
<div className="card">
<Card className={classes.root}>
...
<CardActions>
<Button
onClick={() => addToCart(product)}
...
>
ADD TO CART 🛒
</Button>
<Button
onClick={() => addToWishList(product)} // <-- (4) non-wrapped action
...
>
Add to wish list ❤️
</Button>
</CardActions>
</Card>
</div>
);
})}
</div>
</div>
);
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (product) => dispatch(addToCart(product)),
addToWishList: (product) => dispatch(addToWishList(product)), // <-- (2) injected
};
};
export default connect(null, mapDispatchToProps)(Home);
To resolve, you should destructure and use the correctly wrapped action.
function Home({ addToCart }) {
Should be
function Home({ addToCart, addToWishList }) {
Related
I am learning to build a to-do app in Nextjs + Typescript. I have a Chakra UI modal which pops up when the user clicks on the "edit task" button. The modal contains a Input element and it should have the value of task title. Here is the solution that I have implemented.
// components/tasks.tsx
import { Box } from '#chakra-ui/react';
import { NextPage } from 'next';
import Task from '../components/task';
import EditTaskModal from '../components/edit-task-modal';
import { useState } from 'react';
import { useModalContext } from '../store/modal';
const Tasks: NextPage = () => {
const { onOpen } = useModalContext();
const [editTaskID, setEditTaskID] = useState('');
const handleOpenModal = (id: string) => {
setEditTaskID(id);
onOpen();
};
return (
<>
<EditTaskModal id={editTaskID} />
<Box>
<Task handleOpenModal={handleOpenModal} />
</Box>
</>
);
};
export default Tasks;
// components/task.tsx
import { NextPage } from 'next';
import { Box, Checkbox, Button, Text } from '#chakra-ui/react';
import { useTaskContext } from '../store/tasks';
import { useModalContext } from '../store/modal';
type TaskProps = {
handleOpenModal: (id: string) => void;
};
const Task: NextPage<TaskProps> = ({ handleOpenModal }) => {
const { tasks } = useTaskContext();
const { onOpen } = useModalContext();
return (
<Box>
{tasks.map((task) => {
return (
<Box
key={task.id}
border="3px solid"
borderColor="gray.200"
marginBottom="1em"
p="1em"
borderRadius="5px"
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Text key={task.id}>{task.title}</Text>
<Box width="35%" display="flex" justifyContent="space-between">
<Button
size="sm"
onClick={() => {
handleOpenModal(task.id);
}}
>
Edit
</Button>
<Button size="sm" colorScheme="red">
Delete
</Button>
</Box>
</Box>
);
})}
</Box>
);
};
export default Task;
// components/edit-task-modal.tsx
import { NextPage } from 'next';
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
FormControl,
Input,
ModalFooter,
} from '#chakra-ui/react';
import { useModalContext } from '../store/modal';
import { useEffect, useState } from 'react';
import { useTaskContext } from '../store/tasks';
type EditTaskModalProps = {
id: string;
};
type updateTaskType = { id: string; title: string; status: boolean };
const updateTask = async (task: updateTaskType) => {
try {
const response = await fetch('http://localhost:3000/api/tasks', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(task),
});
} catch (error) {
console.log(error);
}
};
const EditTaskModal: NextPage<EditTaskModalProps> = ({ id }) => {
const { tasks } = useTaskContext();
const task = tasks.find((task) => {
return task.id === id;
});
const [title, setTitle] = useState(task?.title);
const { isOpen, onClose } = useModalContext();
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Update Task Name</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl>
<Input
placeholder="Task Name"
value={title}
onChange={(e) => {
setTitle(e.target.value);
}}
/>
</FormControl>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
mr={3}
onClick={() => {
if (task !== undefined)
updateTask({
title: task.title,
status: task.status,
id: task.id,
});
}}
>
Update
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default EditTaskModal;
// store/modal.tsx
import React, { createContext, useContext } from 'react';
import { useDisclosure } from '#chakra-ui/react';
const ModalContext = createContext<any>(null);
const ModalProvider = ({ children }: { children: React.ReactNode }) => {
const { onOpen, onClose, isOpen } = useDisclosure();
return (
<ModalContext.Provider value={{ onOpen, onClose, isOpen }}>
{children}
</ModalContext.Provider>
);
};
const useModalContext = () => {
return useContext(ModalContext);
};
export { ModalProvider, useModalContext };
However, I noticed that the value attribute in Input element is always one state update behind for some reason and I can't figure out why. Is there a better solution to pass task id to the modal?
I am using react +redux+typescript+firebase for a simple todo application. How to move all actions from components to store if i use react hooks(without other third party libraries, if it is possible).
Components/Todo.tsx:
import React from 'react';
import {connect} from 'react-redux';
import Switch from '#material-ui/core/Switch';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import "./Todo.scss";
import {todosRef} from "../../firebase/firebase";
const Todo = (props: any) => {
const classes = [''];
const { todo } = props;
const updateTodo = () => {
todosRef.child(todo.id).set({...todo,done:!todo.done})
}
if (todo.done) {
classes.push('_completed');
}
return (
<div className="Todo">
<Switch
edge="end" checked={todo.done} onChange={updateTodo}
inputProps={{ "aria-labelledby": "switch-list-label-bluetooth" }}
/>
<p className={classes.join(' ')}>{todo.task}</p>
<IconButton aria-label="delete" onClick={e => todosRef.child(todo.id).remove()}>
<DeleteIcon fontSize="large" />
</IconButton>
</div>
);
}
export default connect()(Todo);
components/TodoForm.tsx
import React, { useState } from "react";
import TextField from '#material-ui/core/TextField';
import {todosRef} from "../../firebase/firebase";
const TodoForm:React.FC = () => {
const [title, setTitle] = useState<string>("");
const createTodo = (e: React.FormEvent<EventTarget>) => {
e.preventDefault();
const item = {
task: title,
done: false,
};
todosRef.push(item);
setTitle("");
};
return (
<form onSubmit={createTodo}>
<TextField
style={{ width: "100%" }}
id="outlined-basic"
value={title}
onChange={(e:React.ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
label="Write task"
variant="outlined"
/>
</form>
);
}
export default TodoForm;
components/TodoList.tsx:
import React, {useState, useEffect} from "react";
import Todo from "../Todo/Todo";
import Divider from '#material-ui/core/Divider';
import {todosRef} from "../../firebase/firebase";
const TodoList:React.FC = () => {
const [todos,setTodos] = useState<any>([]);
useEffect(() => {
todosRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
task: items[item].task,
done: items[item].done
});
}
setTodos(newState)
});
},[])
return (
<>
{todos.map((todo: any, i: number) => (
<React.Fragment key={todo.id}>
<Todo todo={todo} />
{i<todos.length -1 && <Divider />}
</React.Fragment>
))}
</>
);
}
export default TodoList;
actions/index.ts:
export const fetchTodos = (todo:any) => {
return {
type: 'FETCH_TODOS',
payload: todo,
}
}
export const addToDo = (setTodos:any) => {
return {
type: 'ADD_TODO',
payload: setTodos,
}
}
export const updateTodo = (todo:any) => {
return {
type: 'UPDATE_TODO',
payload: todo,
}
}
export const removeTodo = (todo:any) => {
return {
type: 'REMOVE_TODO',
payload: todo,
}
}
I started describing the actions, but I don't know exactly what to write in the context of the store.
const initialState = {
todos: [],
setTodos: [],
}
export default (state = initialState, action:any) => {
switch (action.type) {
case FETCH_TODOS:
return {
...state,
todo:action.payload
};
case REMOVE_TODO:
...
default:
return state;
...
I will also be grateful if you can indicate with a couple of examples (instead of any) which types I should use, since I'm still new to typescript
I am having an issue that When I try to delete the employee, It says ID is not defined.
Frontend was built with react redux. Backend was built with Node,Express and Mongo DB.
I would appreciate if could help me to fix the issue please
//ACTION CREATORS..
import axios from "axios";
import {
GET_EMPLOYEES,
ADD_EMPLOYEE,
UPDATE_EMPLOYEE,
DELETE_EMPLOYEE
} from "./types";
export const getEmployees = () => async dispatch => {
await axios.get("/api/items").then(res =>
dispatch({
type: "GET_EMPLOYEES",
payload: res.data
})
);
};
export const deleteEmployee = id => async dispatch => {
await axios.delete(`/api/items/${id}`).then(res =>
dispatch({
type: "DELETE_EMPLOYEE",
payload: id
})
);
};
//REDUCERS...
import {
GET_EMPLOYEES,
ADD_EMPLOYEE,
UPDATE_EMPLOYEE,
DELETE_EMPLOYEE
} from "../actions/types";
const initialState = {
employees: []
};
const employeeReducer = (state = initialState, action) => {
switch (action.type) {
case "GET_EMPLOYEES":
return {
...state,
employees: action.payload
};
case "DELETE_EMPLOYEE":
return {
...state,
employees: state.employees.filter(emp => emp._id !== action.payload)
};
default:
return state;
}
};
export default employeeReducer;
//MY COMPONENT
import React, { Component } from "react";
import {
Container,
Table,
Button,
Card,
CardText,
CardBody,
CardTitle,
CardSubtitle
} from "reactstrap";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import { getEmployees, deleteEmployee } from "../actions";
import uniqid from "uniqid";
import { connect } from "react-redux";
class EmployeeList extends Component {
componentDidMount() {
this.props.getEmployees();
}
renderList() {
return this.props.employee.employees.map(emp => {
return (
<div key={emp._id}>
<Card style={{ marginBottom: "0.5rem" }}>
<CardBody>
<CardTitle>Employee Name : {emp.employee}</CardTitle>
<CardSubtitle style={{ marginBottom: ".5rem" }}>
Position : {emp.position}
</CardSubtitle>
<CardSubtitle style={{ marginBottom: ".5rem" }}>
Salary : {emp.salary}
</CardSubtitle>
<Button
color="danger"
onClick={this.onDeleteClick.bind(this, id)}
>
Delete
</Button>
</CardBody>
</Card>
</div>
);
});
}
onDeleteClick(id) {
this.props.deleteEmployee(id);
}
render() {
console.log(this.props);
return (
<Container>
<Button color="dark" style={{ marginBottom: "2rem" }}>
Add Employee
</Button>
<div>{this.renderList()}</div>
</Container>
);
}
}
const mapStateToProps = state => {
return { employee: state.employees };
};
export default connect(mapStateToProps, { getEmployees, deleteEmployee })(
EmployeeList
);
<Button
color="danger"
onClick={this.onDeleteClick.bind(this, id)}
>
I guess there is no "id" in current scope, try using "emp._id"
I'm trying to tell react/redux to upvote a likeCount when ADD_LIKE is called from the reducer.
However its upvoting the number for all post items
How can i do something like.
if(action.id === post.id) then do likes: state.likes + 1
That way it can only upvote for that one post only.
case ADD_LIKE:
// console.log(action.id)
return({
...state,
likes: state.likes + 1
})
Actions.js
export const postLike = (id) => {
return (dispatch) => {
// console.log(userId);
return Axios.post('/api/posts/like', {
postId: id
}).then( (like) => {
dispatch({type: ADD_LIKE, id})
// console.log('you have liked this', like)
}).catch( (err)=> {
console.log('there seem to be an error', err);
})
Full Reducer
import { ADD_LIKE, GET_LIKES_COUNT} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[],
isEditing:false,
isEditingId:null,
likes:0,
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
case ADD_LIKE:
// console.log(action.id)
return({
...state,
likes: state.likes + 1
})
default:
return state
}
}
Like Component
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '#fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import { getLikeCount, postLike} from '../actions/';
class Like extends Component{
constructor(props){
super(props);
this.state = {
likes: null,
heart: false
}
}
// dont remove () after (id) or else infinite onClick
clickLike = (id) => () => {
this.props.postLike(id);
// toggles between css class
this.setState({
heart: !this.state.heart
})
}
render(){
return(
<div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
<i style={{ marginRight: '140px'}} className={this.state.heart ? 'fa fa-heart':'fa fa-heart-o' }>
<span style={{ marginLeft: '6px'}}>
<a href="#" onClick={this.clickLike(this.props.like)}>Like </a>
</span>
{/* gets the like counts */}
{this.props.likeCount}
</i>
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
const mapDispatchToProps = (dispatch) => ({
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);
<Like like={id}/> post id is id and being passed in as a prop
Postitem.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, getLikeCount, postLike} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
},
button:{
marginRight:'30px'
}
}
class PostItem extends Component{
constructor(props){
super(props);
this.state = {
disabled: false,
}
}
onUpdate = (id, title) => () => {
// we need the id so expres knows what post to update, and the title being that only editing the title.
if(this.props.myTitle !== null){
const creds = {
id, title
}
this.props.UpdatePost(creds);
}
}
render(){
const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, likes, clickLike} = this.props
return(
<div>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={myTitle ? myTitle : title} editChange={editChange}/>
): (
<div>
{title}
</div>
)}
</Typography>
<Typography component="p">
{post_content}
<h5>
by: {username}</h5>
<Typography color="textSecondary">{moment(createdAt).calendar()}</Typography>
<Like like={id}/>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm(id)}>
Edit
</Button>
):(
// pass id, and myTitle which as we remember myTitle is the new value when updating the title
<div>
<Button
disabled={myTitle.length <= 3}
variant="outlined"
onClick={this.onUpdate(id, myTitle)}>
Update
</Button>
<Button
variant="outlined"
style={{marginLeft: '0.7%'}}
onClick={editForm(null)}>
Close
</Button>
</div>
)}
{!isEditing && (
<Button
style={{marginLeft: '0.7%'}}
variant="outlined"
color="primary"
type="submit"
onClick={removePost(id)}>
Remove
</Button>
)}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id))
// Pass id to the DeletePost functions.
});
export default connect(null, mapDispatchToProps)(PostItem);
PostList.js
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost, getLikeCount, postLike, UpdatePost,EditChange, DisableButton} from '../actions/';
import PostItem from './PostItem';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
title: '',
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
this.setState({
title: e.target.value
})
}
formEditing = (id) => ()=> {;
this.props.EditChange(id);
}
getLikes = (id) => {
// console.log(id);
this.props.getLikeCount(id)
console.log(this.props.likeCount)
}
render(){
const {posts} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
{this.getLikes(post.id)}
{/* {...post} prevents us from writing all of the properties out */}
<PostItem
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
)
}
}
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
})
const mapDispatchToProps = (dispatch) => ({
// pass creds which can be called anything, but i just call it credentials but it should be called something more
// specific.
EditChange: (id) => dispatch(EditChange(id)),
UpdatePost: (creds) => dispatch(UpdatePost(creds)),
getLikeCount: (id) => dispatch(getLikeCount(id)),
postLike: (id) => dispatch( postLike(id)),
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
Edit
Actions.js
export const getLikeCount = (id) => {
return (dispatch, getState) => {
return Axios.get(`/api/posts/likes/count/${id}`)
.then( (res) => {
const data = res.data
console.log(data); // logs data and i can see an array
dispatch({type: GET_LIKES_COUNT, data})
})
}
}
{this.props.likeCount} logs
ive tried doing this
case ADD_LIKE:
return {
...state,
posts: state.posts.map(post => {
if (post.id === action.id) {
return {
...post,
likes: post.likes + 1
}
} else return post
})
};
but it does not update the like count for some reason.
I'm not sure how your code works, but I assume Like component is like a love icon for each post? But from your code, seems like all the Like components are sharing the same redux state post.likes
const mapStateToProps = (state) => ({
isEditingId: state.post.isEditingId,
likeCount:state.post.likes
})
That is why all the Like share the same likes count. I will suggest to restructure your redux state, such as putting the likes count into the post object, for example:
Reducer
import { ADD_LIKE, GET_LIKES_COUNT} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[], // put likes count in post item, for eg { id: number, likes: number }
isEditing:false,
isEditingId:null,
postId:null
}
export default (state = initialState, action) => {
switch (action.type) {
case ADD_LIKE:
return({
...state,
posts: state.posts.map(post => (post.id === action.id ? { ...post, likes: post.likes + 1 } : post))
})
default:
return state
}
}
Then instead of use Redux in Like component, just passed the data from PostItem will be more easy to maintain:
PostItem
render() {
return (
....
<Like likeCount={post.likes} onClick={() => this.onClick(post.id)} />
....
)
}
try this,
did you mean post.id instead of postId in your state. because you can not compare if(action.id === post.id) then do likes: state.likes + 1 like this because post is array.
if you wrongly used post.id instead of postId you can try below solution
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIKES_COUNT:
// console.log(action.data)
return({
...state,
likes:action.data
})
case ADD_LIKE:
// console.log(action.id)
return({
...state,
likes: action.id === state.postId? state.likes + 1 : state.likes
})
default:
return state
}
}
I have some trouble with my code. I made an app where I use an API last fm and I want to add a rating button, I get few things from Google. Rating is displayed where I want to be, I can console log him, but it's on external file and I have no idea how to modify rate state from my app.js. Here is my code:
App.js
import React, { Component } from 'react';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import { getArtists } from './services/api';
import {
TextField,
Button,
List
} from '#material-ui/core';
import { ArtistCard } from './components/ArtistCard';
import { SearchResult } from './components/SearchResult';
import './App.css';
import { get } from 'https';
const handleChangeRate = (state) => {
this.setState({rate: state})
}
const isEmpty = (str) => str.length === 0;
class App extends Component {
state = {
searchTerm: '',
savedArtists: [],
rate:[]
}
componentDidMount() {
const existing = localStorage.getItem('savedArtists')
if (existing) {
this.setState({ savedArtists: JSON.parse(existing) })
}
}
onTextChange = (event) => {
const value = event.target.value;
this.setState({ searchTerm: value });
}
search = async (terms) => {
const artists = await getArtists(terms);
this.setState({ artists: artists })
}
onSearchClick = () => {
this.search(this.state.searchTerm);
}
clearSearch = () => {
this.setState({
searchTerm: '',
artists: []
})
}
updateArtists = (newArtists) => {
this.setState({ savedArtists: newArtists })
localStorage.setItem('savedArtists', JSON.stringify(newArtists));
}
deleteArtist = (artist) => {
const result = this.state.savedArtists.filter(item => item.name !== artist.name);
this.updateArtists(result);
}
onResultClick = (artist) => {
this.clearSearch();
const savedArtists = this.state.savedArtists;
savedArtists.push(artist);
this.updateArtists(savedArtists);
}
handleChangeRate = (state) => {
this.setState({rate: state})
}
render() {
const results = this.state.artists || [];
return (
<div className="App">
<header className="App-header">
<AppBar position="static" color="primary">
<Toolbar className="search-bar">
<Typography variant="h6" color="inherit">
Photos
</Typography>
<TextField
placeholder="Search on Last.fm"
className="search-input"
onChange={this.onTextChange}
value={this.state.searchTerm}
/>
<Button
onClick={this.onSearchClick}
variant="contained"
color="secondary"
disabled={isEmpty(this.state.searchTerm)}
>
Search
</Button>
{!isEmpty(this.state.searchTerm) && (
<Button
onClick={this.clearSearch}
variant="contained"
>
Clear
</Button>)
}
</Toolbar>
</AppBar>
</header>
<List className="search-results">
{
results.map((artist, index) => {
return <SearchResult key={index} artist={artist} onResultClick={this.onResultClick} />
})
}
</List>
<div className="artist-container">
{
this.state.savedArtists.map((artist, index) => {
return <ArtistCard artist={artist} key={index} deleteArtist={this.deleteArtist} onChangeRating={this.handleChangeRate} />
})
}
</div>
</div>
);
}
}
export default App;
artistCard.js
import React from 'react';
import { Card, CardContent, CardActions, Button } from '#material-ui/core'
import ReactStars from 'react-stars'
export const ratingChanged = (newRating) => {
const { onChangeRating } = this.props;
onChangeRating(newRating);
}
export const ArtistCard = (props) => {
const { artist, deleteArtist } = props;
console.log(artist.cardImage)
return (
<Card className="artist-card">
<div className="image-container">
<img src={artist.cardImage} alt={artist.name} />
</div>
<CardContent>
<h3>{artist.name}</h3>
<p>{artist.listeners} listeners.</p>
<ReactStars
count = {5}
onChange={ratingChanged}
size={27}
color2 ={'#ffd700'}
/>
</CardContent>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="secondary" onClick={() => deleteArtist(artist)}>
Delete
</Button>
</CardActions>
</Card>
)
}
You need to pass the function to change State to artistCard as props
In App.js add the following fucntion
const handleChangeRate = (state) => {
this.setState(rate: state)
}
and Pass the same as props to ArtistCard like specified
<ArtistCard artist={artist} key={index} deleteArtist={this.deleteArtist} onChangeRating={this.handleChangeRate} />
And in artistCard.js change ratingChanged method to
const ratingChanged = (newRating) => {
const { onChangeRating } = this.props;
onChangeRatng(newRating);
}
PS: This answer is based on the understanding i gained after going through this question, If this is not the requirement Please feel free to comment
EDIT
const handleChangeRate = (state) => {
this.setState(rate: state)
}
try adding the value prop to ReactStart like specified below
<ReactStars
value={this.props.rate}
count={5}
onChange={ratingChanged}
size={24}
color2={'#ffd700'}
/>
Pass rate state to artist card component as prop like specified below
<ArtistCard artist={artist} key={index} deleteArtist= {this.deleteArtist} onChangeRating={this.handleChangeRate} rate={this.state.rate} />
EDIT
cardComponent.js
import React from 'react';
import ReactStars from 'react-stars'
export const ArtistCard = (props) => {
const { artist, deleteArtist, onChangeRating } = props;
console.log(props.rating)
return (
<ReactStars
value={props.rating}
count = {5}
onChange={(newRating) => onChangeRating(newRating)}
size={27}
color2 ={'#ffd700'}
/>)}
App.js
handleChangeRate = (state) => {
this.setState({rate: state})
}
<ArtistCard artist={'artist'} key={'index'} rating={this.state.rate} deleteArtist={this.deleteArtist} onChangeRating={this.handleChangeRate} />
FINAL EDIT
Changes made in your code
App.js
modified state object to
state = {
searchTerm: '',
savedArtists: [],
rate: ''
}
Artistcard component line to
<ArtistCard rate={this.state.rate} artist={artist} key={index} onChangeRating={(val) => {this.setState({ rate: val })}} deleteArtist={this.deleteArtist} />
In ArtistCard.js
rendering reactstart component like this
<ReactStars
value={props.rate}
count = {5}
onChange={(newRating) => onChangeRating(newRating)}
size={27}
color2 ={'#ffd700'}
/>