I created two actions which sort products based on their price, I assigned sorted array to redux state products array, but when I trigger the action, products disappear from the component..
I read about componentWillReceiveProps and componentWillUpdate but can't figure out how to use it..
Here are the actions:
// actions
export const sortProductsByLowestPrice = () => (dispatch, getState) => {
let products = getState().products.products.products;
const isArray = Array.isArray(products);
const sorted = products.slice().sort((a,b) => { return a.price > b.price})
dispatch({
type: SORT_PRODUCTS_BY_LOWEST_PRICE,
payload: sorted
})
}
export const sortProductsByHighestPrice = () => (dispatch, getState) => {
let products = getState().products.products.products;
const isArray = Array.isArray(products);
const sorted = products.slice().sort((a,b) => { return a.price < b.price})
dispatch({
type: SORT_PRODUCTS_BY_HIGHEST_PRICE,
payload: sorted
})
}
// rendering products
{this.props.products ? this.props.products.slice(0,5).map((product) => {
return <ProductItem key={product._id} product={...product} />
}) : null
// click events
<Button onClick={this.props.sortProductsByLowestPrice}>Lowest price</Button>
<Button onClick={this.props.sortProductsByHighestPrice}>Highest price</Button>
Actions work, they sort as they should, see the result in logger.
import React, {Component} from 'react';
import { PropTypes } from 'prop-types';
import { connect } from 'react-redux';
import { getProducts, sortProductsByLowestPrice, sortProductsByHighestPrice } from '../actions/productsActions';
import { Grid, Row, Button, Group, Loader } from 'semantic-ui-react';
import ProductItem from './ProductItem';
class ProductList extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.getProducts();
}
render() {
return <div>
<Button.Group className="buttonGroup">
<Button onClick={this.props.sortProductsByLowestPrice}>Lowest price</Button>
<Button onClick={this.props.sortProductsByHighestPrice}>Highest price</Button>
</Button.Group>
<div className="horizontal">
{this.props.products ?
this.props.products.slice(0,5).map((product) => {
return <ProductItem key={product._id} product={...product} />
}) : <Loader active inline />}
</div>
</div>
}
}
ProductList.propTypes = {
getProducts: PropTypes.func,
products: PropTypes.array,
sortProductsByLowestPrice: PropTypes.func,
sortProductsByHighestPrice: PropTypes.func
}
const mapStateToProps = state => {
return {
products: state.products.products.products
}
};
const mapDispatchToProps = (dispatch) => ({
getProducts: () => dispatch(getProducts()),
sortProductsByLowestPrice: () => dispatch(sortProductsByLowestPrice()),
sortProductsByHighestPrice: () => dispatch(sortProductsByHighestPrice())
});
export default connect(mapStateToProps, mapDispatchToProps)(ProductList);
Related
I am using react redux to create a basic blogs CRUD app.
I have a blog list which have a read more button which when click should take me to that particular blog. I am trying to get into that particular blog using blog id and using useParam Hook. You can find my code below -
acion-creators - Actions
export const listBlog = (blogList) => {
return (dispatch) => {
dispatch({
type: "list-blog",
payload: blogList
})
}
}
export const addBlog = (blog) => {
return (dispatch) => {
dispatch({
type: "add-blog",
payload: blog
})
}
}
export const deleteBlog = (id) => {
return (dispatch) => {
dispatch({
type: "delete-blog",
payload: id
})
}
}
export const findBlog = (id) => {
return (dispatch) => {
dispatch({
type: "find-blog",
payload: id
})
}
}
reducer - blogReducer -
import blogs from "../data"
const reducer = (state=blogs, action) => {
if (action.type === "list-blog") {
return state
}
else if (action.type === "add-blog"){
state.push(action.payload)
return state
}
else if (action.type === "delete-blog") {
state.pop(action.payload)
return state
}
else if (action.type === "find-blog") {
for(let i=0; i<=state.length; i++){
if(state[i].id === action.payload){
console.log(state[i])
return state[i]
}
else{
return "Blog not Found"
}
}
}
else {
return state
}
}
export default reducer
Blog page which should show that selected blog after clicking read more - BlogPage.js
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import { bindActionCreators } from "redux";
import { actionCreators } from "../State/index";
export const BlogPage = () => {
const [blogId, setBlogId] = useState();
const dispatch = useDispatch();
const actions = bindActionCreators(actionCreators, dispatch)
const params = useParams()
useEffect(() => {
setBlogId(params.blogId)
},[])
const handleEdit = (id) => {
console.log(id)
const blog = actions.findBlog(id)
console.log(blog)
}
return (
<div className="container">
<h3></h3>
<p>
Body
</p>
<button className="btn btn-success my-3" onClick={() => handleEdit(blogId)}>Edit Blog</button>
<button className="btn btn-danger mx-3">Delete Blog</button>
<hr className="my-3"/>
<button className="btn btn-primary">Like</button>
<br /> <br />
<h4 className="mx-3">Comments</h4>
</div>
)
}
In BlogPage.js I am finding the selected blog Id using useParam and using that in find-blog Action to find that particular blog in my state.
State is a list of blogs stored locally.
I am getting the id when I console.log the id of the blog but I am getting undefined when I try to use actions.findBlog()
It is showing undefined when I console.log the blog I find using the findBlog action.
I'm learning react context and while developing a todo application using useContext, I'm facing an issue where on submitting one task, the same task gets added two times to an array. The output component would loop through this array and display the results. While debugging I observed that, although the submit of task add only one entry into the array, not sure why and how, the consumer component gets the array with duplicate entry. Please let me know, what I'm missing.
Here is my code of index file that maintains context
import { createContext, useReducer } from "react";
import ContextReducer, { initialState } from "./ContextReducer";
const taskContext = createContext();
const ContextProvider = (props) => {
const [state, dispatch] = useReducer(ContextReducer, initialState);
const setTaskInput = (taskInput) => {
dispatch({
type: "SET_TASKINPUT",
payload: taskInput,
});
};
const addTask = (task) => {
dispatch({
type: "ADD_TASK",
payload: task,
});
};
const deleteTask = (id) => {
dispatch({
type: "DELETE_TASK",
payload: id,
});
};
const todoContext = {
todo: state.todo,
taskInput: state.taskInput,
setTaskInput,
addTask,
deleteTask,
};
return (
<taskContext.Provider value={todoContext}>
{props.children}
</taskContext.Provider>
);
};
export { taskContext };
export default ContextProvider;
This is the code for reducer
const initialState = {
todo: [],
taskInput: "",
};
const ContextReducer = (state = initialState, action) => {
if (action.type === "SET_TASKINPUT") {
state.taskInput = action.payload;
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "ADD_TASK") {
state.todo = [...state.todo, action.payload];
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "DELETE_TASK") {
state.todo = state.todo.filter((todo) => todo.id !== action.payload);
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
return state;
};
export { initialState };
export default ContextReducer;
This is the code of output component or say, consumer component
import React, { Fragment, useContext } from "react";
import { taskContext } from "../../Context";
import styles from "./Content.module.css";
const Output = () => {
const { todo, deleteTask } = useContext(taskContext);
const deleteHandler = (e) => {
deleteTask(+e.target.parentElement.parentElement.id);
};
return (
<Fragment>
{todo.length > 0 && (
<div className={styles.outputDiv}>
<ul>
{todo.map((task) => {
return (
<li key={task.id} id={task.id}>
<div className={styles.row1}>{task.task}</div>
<div className={styles.row2}>
<button className={styles.edit}>Edit</button>
<button className={styles.delete} onClick={deleteHandler}>
Delete
</button>
</div>
</li>
);
})}
</ul>
</div>
)}
</Fragment>
);
};
export default Output;
So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example
i have a simple app which have Courses component .in the console.log prints undefined(for both state and props).
the Courses component is as:
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { useSelector } from "react-redux";
import { styles } from "../_helpers";
import * as actions from "../_actions";
import EditIcon from "#material-ui/icons/Edit";
import DeleteIcon from "#material-ui/icons/Delete";
import { useToasts } from "react-toast-notifications";
import { Grid, Paper, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, withStyles, ButtonGroup, Button } from "#material-ui/core";
export const Courses =(props)=> {
useEffect(() => {
actions.fetchAll();
}, [])//componentDidMount
console.log(props.course);//course is undefined
//toast msg.
const { addToast } = useToasts()
const onDelete = id => {
if (window.confirm('Are you sure to delete this record?'))
props.delete(id,()=>addToast("Deleted successfully", { appearance: 'info' }))
}
function handleClick(id) {
(window.alert('you want to edit?'))
}
return (
<Paper className={styles.paper} elevation={3}>
<Grid container>
<Grid item xs={6}>
{/* <CourseForm{...({ currentId, setCurrentId })}/> */}
</Grid>
<Grid item xs={6}>
<TableContainer>
{ <Table>
<TableHead className={styles.root}>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Details</TableCell>
<TableCell>Category</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{
props.courseList.map((record, index) => {
return (<TableRow key={index} hover>
<TableCell>{record.courseTitle}</TableCell>
<TableCell>{record.details}</TableCell>
<TableCell>{record.category}</TableCell>
<TableCell>
<ButtonGroup variant="text">
<Button><EditIcon color="primary"
onClick={() => handleClick(record.courseId) } /></Button>
<Button><DeleteIcon color="secondary"
onClick={() => onDelete(record.courseId)} /></Button>
</ButtonGroup>
</TableCell>
</TableRow>)
})
}
</TableBody>
</Table> }
</TableContainer>
</Grid>
</Grid>
</Paper>
);
}
const mapStateToProps = state => ({
courseList: state.course.list
})
export default connect(mapStateToProps)(withStyles(styles)(Courses));
the courseApi.js is as :
import axios from "axios";
const baseUrl = "https://localhost:4000/api/"
export default {
course(url = baseUrl + 'courses/') {
return {
fetchAll: () => axios.get(url),
fetchById: id => axios.get(url + id),
create: newRecord => axios.post(url, newRecord),
update: (id, updateRecord) => axios.put(url + id, updateRecord),
delete: id => axios.delete(url + id)
}
}
}
the courseActions.js is as :
import courseApi from "../_services/courseApi";
import { ACTION_TYPES } from '../_constants';
const formateData = data => ({
...data,
})
export const fetchAll = () => dispatch => {
courseApi.course().fetchAll()
.then(response => {
dispatch({
type: ACTION_TYPES.FETCH_ALL,
payload: response.data
})
})
.catch(err => console.log(err))
}
export const create = (data, onSuccess) => dispatch => {
data = formateData(data)
courseApi.course().create(data)
.then(res => {
dispatch({
type: ACTION_TYPES.CREATE,
payload: res.data
})
onSuccess()
})
.catch(err => console.log(err))
}
export const update = (id, data, onSuccess) => dispatch => {
data = formateData(data)
courseApi.course().update(id, data)
.then(res => {
dispatch({
type: ACTION_TYPES.UPDATE,
payload: { id, ...data }
})
onSuccess()
})
.catch(err => console.log(err))
}
export const Delete = (id, onSuccess) => dispatch => {
courseApi.course().delete(id)
.then(res => {
dispatch({
type: ACTION_TYPES.DELETE,
payload: id
})
onSuccess()
})
.catch(err => console.log(err))
}
the courseReducer.js is as follows:
import { ACTION_TYPES } from '../_constants';
const initialState = {
list: []
}
export const course = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPES.FETCH_ALL:
return {
...state,
list: [...action.payload]
}
case ACTION_TYPES.CREATE:
return {
...state,
list: [...state.list, action.payload]
}
case ACTION_TYPES.UPDATE:
return {
...state,
list: state.list.map(x => x.id == action.payload.id ? action.payload : x)
}
case ACTION_TYPES.DELETE:
return {
...state,
list: state.list.filter(x => x.id != action.payload)
}
default:
return state
}
}
the store.js is following:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import rootReducer from '../_reducers';
const loggerMiddleware = createLogger();
export const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
//window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
the folder structure is as follows:
1.courseActions.js is in _actions folder
2. coursApi.js is in _services folder
3.courseReducer.js in _reducers folder
4.store.js is in _helper folder
5. ACTION_TYPES is in _constants folder.
i will be very thankful for kind help.
the error is that state is undefined.
i changed the Courses component and used useSelector()and useDispatch() function instead of connect .this way i was able to access the state.i removed the props argument from functional component Courses .more over removed mapStateToProps and connect completely.i am sharing so that may be helpful for anyone.
export const Courses =()=> {
const course = useSelector(state => state.course);
const dispatch = useDispatch();
console.log(course);
useEffect(() => {
dispatch(actions.fetchAll());
}, [])//componentDidMount
//other stuff
I'm digging into my first react/redux application and I've been having quite a bit of trouble mapping my dispatch actions to onClick events in my components.
I've tried a couple of variations of trying to bind the onClick Event to the dispatch, but I always end up with either :
ReferenceError: onMovieClick is not defined
or alternatively when I do end up binding a function correctly I'll get an error related to dispatch is not defined.
My Goal
I'm trying to implement a filter(delete) from store function
actions/movieActions.js
import * as actionTypes from './actionTypes'
export const createMovie = (movie) => {
return {
type: actionTypes.CREATE_MOVIE,
movie
}
};
export const deleteMovie = (id) => {
console.log('action triggered. movie index:' + id)
return {
type: actionTypes.DELETE_MOVIE,
id
}
}
reducers/movieReducers.js
export default (state = [], action) => {
switch (action.type){
case 'CREATE_MOVIE':
return [
...state,
Object.assign({}, action.movie)
];
case 'DELETE_MOVIE':
return [
state.filter(({ id }) => id !== action.id)
]
default:
return state;
}
};
components/MovieList.js
import React from 'react'
import Slider from 'react-slick'
import { dispatch, connect } from 'react-redux'
import {Icon} from 'react-fa'
import { deleteMovie } from '../../actions/movieActions'
import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'
import './MovieList.scss'
class MovieList extends React.Component{
constructor(props){
super (props)
}
handleClick(id) {
dispatch(deleteMovie(id))
}
onMovieClick(id){
dispatch.deleteMovie(id)
}
render () {
// Settings for slick-carousel
let settings = {
infinite: true,
speed: 500
}
return (
<div className='col-lg-12'>
{this.props.movies.map((b, i) =>
<div key={i} className="col-lg-2">
<Slider {...settings}>
{b.images.map((b, z) =>
<div className="img-wrapper">
<Icon name="trash" className="trash-icon" onClick={() =>
console.log(this.props.movies[i].id),
onMovieClick(this.props.movies[i].id)
}/>
<img className="img-responsive" key={z} src={b.base64}></img>
</div>
)}
</Slider>
<div className="text-left info">
<h2>{b.title}</h2>
<p>{b.genre}</p>
</div>
</div>
)}
</div>
)
}
}
// map state from store to props
const mapStateToProps = (state) => {
return {
movies: state.movies
}
};
// Map actions to props
const mapDispatchToProps = (dispatch) => {
return {
onMovieClick: (id) => {
dispatch(deleteMovie(id))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MovieList)
Would love some advice if anyone has a moment.
Since you are passing onMovieClick through connect, you can actually invoke it from the MovieList component props. First, I would remove the onMovieClick method definition in your MovieList component and then use this.props.onMovieClick in the onclick handler of Icon like so:
<Icon name="trash" className="trash-icon" onClick={() =>
console.log(this.props.movies[i].id),
this.props.onMovieClick(this.props.movies[i].id)
}/>