ReactJS Redux dispatch called twice - javascript

I have an action that transfers the index of the card when I click on it and a window opens with the settings of this card. A window with settings opens, this window has a button that should close the window (changing cardIndex to null), however, when the "closeWindow" action is triggered, changing the cardIndex state to null and immediately sets the cardIndex state with a previous index. I'm new in ReactJS and Redux, what I'm doing wrong?
My code:
import React, {useEffect} from 'react'
import { connect } from 'react-redux'
import { fetchCalendar, handleCalendarIndex } from "../../actions/calendarActions";
const CalendarCard = ({getCalendarData, loading, calendarCard, cardIndex, openWindow, closeWindow}) => {
useEffect(() => {
getCalendarData()
},[getCalendarData]);
const renderCalendarCard = () => {
if (loading) return <p>Loading...</p>;
if (calendarCard !== null) return (<div className="calendar-container">
{calendarCard.calendar_results.map((dateCard, dateIndex) => {
const dateResults = calcPositions(dateCard);
return(
<div className="day-card" key={dateCard.checkin} onClick={() => openWindow(dateIndex)}>
{
cardIndex ?
<div className="day-details">
<span className="close-modal"
onClick={() => closeWindow()}>x</span>
<h3>Current date details</h3>
</div> :
null
}
<div className="card-header">
<div className="current-date">
<span className="date-name">{handleDate(dateCard.checkin)}</span>
<span className="day-name">{getDayName(dateCard.checkin)}</span>
</div>
<div className="available-count">
<span className='text-value'>{dateCard.available_listings_ng}
({dateCard.available_listings_ratio_ng.toFixed(0)}%)</span>
<span className='text-label'>Av. lists</span>
</div>
</div>
</div>
)})}
</div>);
return <p>error</p>;
};
return (
<section>
<h1>Calendar card</h1>
{renderCalendarCard()}
</section>
)
};
const mapDispatchToProps = (dispatch) => {
return {
getCalendarData: () => dispatch(fetchCalendar('4c5c752f-c9d2-42d0-9f00-2fc0b11989a5')),
openWindow: cardIndex => dispatch(handleCalendarIndex(cardIndex)),
closeWindow: () => dispatch(handleCalendarIndex(null))
}
};
const mapStateToProps = state => ({
loading: state.calendarCard.loading,
calendarCard: state.calendarCard.calendarCard,
hasErrors: state.calendarCard.hasErrors,
cardIndex: state.calendarCard.cardIndex
});
export default connect(mapStateToProps, mapDispatchToProps)(CalendarCard)
my handleCalendarIndex function
export function handleCalendarIndex(calendarIndex) {
return async dispatch => {
dispatch(changeCalendarIndex(calendarIndex))
}
}

Related

How to change the icon of only one particular item of mapped array in reactjs?

I was creating the functionality of pinning and unpinning of particular note, so when the user clicks the thumbtack icon I want that icon of only that particular note changes to a cross icon but when I am clicking on the second notes to pin it then the icon that changed on previous pinned note gets restored to its original form.
I have created the pinning functionality using onPin function but struggling with changing the icon of that particular pinned item.
I want to add icons to pinned items in such a way that previously added close icons stay in their place and do not get updated.
What I tried?
So i created the state variable iconId which is an array so whenever the user clicks pinned icon then new id will be pushed to the iconId array and while displaying the output I put the condition that if the current id is included in iconId array then change icon of all those respective ids in iconId to cross icon, apparently this functionality dint work.
-----------------------App.js--------------------------------
import React, { useState } from "react";
import './App.css';
import Input from './Components/Input';
import Navbar from './Components/Navbar';
import Notesview from './Components/Notesview';
import Notesdata from "./Data/Notesdata";
function App() {
const [data, setData] = useState(Notesdata);
// const [pin, setpin] = useState(true)
const [iconId, seticonId] = useState([])
function handleDelete(id) {
let newData = data.filter((item) => item.id !== id)
setData(newData)
console.log(newData)
console.log(Notesdata)
console.log(0)
}
function handlePost(value) {
// Notesdata.push(value)
// setData(Notesdata)
// // console.log(typeof data)
// console.log(Notesdata)
setData([...data, value]);
}
function onPin(id) {
let index = data.map((item) => {
return item.id
}).indexOf(id)
let arr1 = data.slice(0, index).concat(data.slice(index + 1))
arr1.unshift(data[index])
setData(arr1);
seticonId([...iconId] , id)
console.log(iconId)
}
function handleclose() {
// setpin(!pin)
// seticonId("")
}
return (
<div className="App">
<header className="App-header">
<Navbar />
<Input data={data} handlePost={(value) => handlePost(value)} />
<Notesview handleDelete={handleDelete} Data={data} onPin={onPin} iconId={iconId} handleclose={handleclose} />
</header>
</div>
);
}
export default App;
----------------Noteview function(mapping function)---------------
import React from 'react'
import Notescard from './Notescard'
import "../Styles/Notes.css"
// import { useState } from 'react'
const Notesview = ({ Data, handleDelete, onPin , iconId, handleclose}) => {
return (
<>
<div className='notes'>
{Data && Data.map((item) => {
return <Notescard item={item} handleDelete={handleDelete} onPin={onPin} iconId={iconId} key={item.id} handleclose={handleclose}/>
})
}
</div>
</>
)
}
export default Notesview
-----------------------------Notescard component------------------
import React from "react";
import "../Styles/Notescard.css";
import { FaThumbtack, FaTrashAlt, FaPencilAlt ,FaTimesCircle} from "react-icons/fa";
// import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
const Notescard = ({ item , handleDelete,onPin,iconId,handleclose, key}) => {
return (
<>
<div className="box">
<div className="content">
<h2 className="item1">{item.title}</h2>
<h4 className="item1"> {item.tagline}</h4>
<p className="item2">{item.description}</p>
</div>
<div className="icons">
{iconId.includes(item.id) ? <FaTimesCircle onClick={handleclose}/> : <FaThumbtack id={item.id} onClick={() => onPin(item.id)}/> }
<FaTrashAlt onClick={() => handleDelete(item.id)}/>
<FaPencilAlt />
</div>
</div>
</>
);
};
export default Notescard;
Issue
You are passing two arguments to the seticonId state updater function.
seticonId([...iconId], id)
The id is never added to the iconId state.
Solution
Use a functional state update to append the id to the array.
seticonId((iconId) => iconId.concat(id));
Code:
const Notescard = ({ item, handleDelete, onPin, iconId, handleclose }) => {
return (
<div className="box">
<div className="content">
<h2 className="item1">{item.title}</h2>
<h4 className="item1"> {item.tagline}</h4>
<p className="item2">{item.description}</p>
</div>
<div className="icons">
{iconId.includes(item.id) ? (
<FaTimesCircle onClick={() => handleclose(item.id)} />
) : (
<FaThumbtack id={item.id} onClick={() => onPin(item.id)} />
)}
<FaTrashAlt onClick={() => handleDelete(item.id)} />
<FaPencilAlt />
</div>
</div>
);
};
...
const Notesview = ({ Data, handleDelete, onPin, iconId, handleclose }) => {
return (
<div className="notes">
{Data.map((item) => {
return (
<Notescard
item={item}
handleDelete={handleDelete}
onPin={onPin}
iconId={iconId}
key={item.id}
handleclose={handleclose}
/>
);
})}
</div>
);
};
...
export default function App() {
const [data, setData] = useState(Notesdata);
const [iconId, seticonId] = useState([]);
function handleDelete(id) {
let newData = data.filter((item) => item.id !== id);
setData(newData);
console.log(newData);
console.log(Notesdata);
console.log(0);
}
function handlePost(value) {
setData([...data, value]);
}
function onPin(id) {
setData((data) => {
const index = data.findIndex((item) => item.id === id);
const arr1 = data.slice(0, index).concat(data.slice(index + 1));
arr1.unshift(data[index]);
return arr1;
});
seticonId((iconId) => iconId.concat(id));
}
function handleclose(id) {
setData((data) => {
const index = data.findIndex((item) => item.id === id);
const insertIndex = data.findIndex((item) => !iconId.includes(item.id));
const arr1 = data.slice(0, index).concat(data.slice(index + 1));
arr1.splice(insertIndex - 1, 0, data[index]);
return arr1;
});
seticonId((iconId) => iconId.filter((elId) => elId !== id));
}
return (
<div className="App">
<Input data={data} handlePost={(value) => handlePost(value)} />
<Notesview
handleDelete={handleDelete}
Data={data}
onPin={onPin}
iconId={iconId}
handleclose={handleclose}
/>
</div>
);
}

useReducer passed with useContext but a child component displays empty state.map

First, I would thank you for the support.
As new to the ReactJS world, I am trying to complete a concept example of a product store with some filters as checkbox. The idea is that you select a filter, you get displayed the products that have the selected proprety.
Everything works, except that when you refresh the page you get the filters column and a blank column where products are supposed to appear, even if the console.log(state) give back the correct array of objects.
As you click a checkbox (the filters) it render correctly and the products appear.
The GITHUB LINK for the complete code.
Here the component CardProduct that does not display at refresh.
import React, { useContext, useEffect } from 'react'
import { AppContext } from '../../App'
import { Hearty } from '../Hearty'
import Star from '../Star'
import boiler from '../../images/boiler.png'
import Confronta from '../Confronta'
const CardProduct = ({ count, setCount }) => {
const [state, dispatch] = useContext(AppContext)
console.log('State in CardProduct:', state)
function returnCardProduct () {
return (
state.map((item, i) => {
const { brand, descrizione, prezzo, note, stelle } = item
return (
<div className="row">
<div className="colcard">
<div key={ i } className="card"
style={ { width: 'auto', height: 'auto' } }>
<Hearty/>
<img className="card-img-top" src={ boiler } alt="boiler"/>
<div className="card-body">
<p className="card-title"> { brand.toUpperCase() }</p>
<h6 className="card-text">{ descrizione }</h6>
<Star stelle={ stelle }/>
<h4> { prezzo } </h4>
<h5> { note } </h5>
<Confronta count={ count } setCount={ setCount }/>
</div>
</div>
</div>
</div>
)
}))
}
return (
<div className="container">
{ returnCardProduct() }
</div>
)
}
export default CardProduct
Here the Filters component
import { useContext, useEffect, useState } from 'react'
import { AppContext } from '../App'
const Filters = () => {
const [stock, setStock] = useState([])
const [state,dispatch] = useContext(AppContext)
function fetchInitialStock () {
async function fetchStock () {
let result1 = await fetch('http://localhost:9000/stock').
then(result1 => result1.json()).
then(data => setStock(data))
}
fetchStock()
return stock
}
useEffect (()=>fetchInitialStock(),[])
console.log( 'initStock' ,stock)
return (
<>
<div className="container">
<div className="row">
<div className="categories">
<p>CATEGORIE</p>
<h6>Riscaldamento</h6>
<h6>Casa e acqua</h6>
<h6>Casa</h6>
<h6>Acqua</h6>
</div>
<div className="scegli">
<p>SCEGLI PER</p>
<h6><span><input type="checkbox"
name="DISPONIBILI"
onChange={(e)=> {
e.target.checked ? dispatch({ type: 'DISPONIBILI' }) : dispatch({ type:'PREV' })
} }/>
</span> Disponibili ({stock.map((item) => item.disponibili )}) </h6>
<h6><span><input type="checkbox"
name="PROMO"
onChange={(e)=> e.target.checked ? dispatch({ type: 'PROMO' }) : dispatch({ type: 'PREV' }) }
/> </span>In Promozione ({ stock.map((item) => item.inSconto) }) </h6><br/>
</div>
<div className="marche">
<p>MARCHE</p>
<h6><span><input type="checkbox" name="ariston" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'ARISTON' })
: dispatch({ type: 'PREV' })
}}
/> </span> Ariston ({stock.map((item)=>item.hasOwnProperty('brand')? item.brand.ariston: null)})</h6>
<h6><span><input type="checkbox" name="baxi" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'BAXI' })
: dispatch({ type: 'PREV' })
}}/> </span>Baxi ({stock.map((item)=>item.hasOwnProperty('brand')? item.brand.baxi : null)})</h6><br/>
</div>
<div className="tipologia">
<p>TIPOLOGIA</p>
<h6><span><input type="checkbox" name="condensazione" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'CONDENSAZIONE' })
: dispatch({ type: 'PREV' })
}}/> </span> A Condensazione ({stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.condensazione: null)}) </h6>
<h6><span><input type="checkbox" name="cameraAperta" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'APERTA' })
: dispatch({ type: 'PREV' })
}}/> </span>Camera Aperta ({ stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.cameraAperta: null) }) </h6>
<h6><span><input type="checkbox" name="cameraStagna" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'STAGNA' })
: dispatch({ type: 'PREV' })
}}/> </span>Camera Stagna ({ stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.cameraStagna: null) })</h6><br/>
</div>
</div>
</div>
</>
)
}
export default Filters
..and FINALLY the App()
import CardProduct from './components/CardProduct'
import { createContext, useReducer, useState, useEffect } from 'react'
import Filters from './components/Filters'
import Footer from './components/Footer/Footer'
export const AppContext = createContext()
function App () {
const [count, setCount] = useState(0)
function setInit (data, array) {
data.map((item) => array.push(item))
return array
}
/*Function for setting BOILERS from fetch*/
function fetchInitialBoiler () {
let initB = []
async function fetchBoilers () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json()).
then(data => setInit(data, initB))
}
fetchBoilers()
return initB
}
const initBoilers = fetchInitialBoiler()
const [prev, setPrev] = useState([])
const [state, dispatch] = useReducer(reducer, initBoilers)
/* Define the reducer function*/
function reducer (state, action) {
let current
switch (action.type) {
case 'DISPONIBILI':
current = []
current = state.filter((item) => item.disponibile ? item : null)
setPrev(current)
return current
case 'PROMO':
current = []
current = state.filter((item) => item.inSconto ? item : null)
setPrev(current)
return current
case 'ARISTON':
current = []
current = state.filter(
(item) => ((item.hasOwnProperty('brand')) &&
(item.brand === 'Ariston'))
? item
: null)
setPrev(current)
return current
case 'BAXI':
current = []
current = state.filter(
(item) => (item.hasOwnProperty('brand')) && (item.brand === 'Baxi')
? item
: null)
setPrev(current)
return current
case 'CONDENSAZIONE':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'condensazione')
? item
: null)
setPrev(current)
return current
case 'APERTA':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'camera-aperta')
? item
: null)
setPrev(current)
return current
case 'STAGNA':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'camera-stagna')
? item
: null)
setPrev(current)
return current
case 'PREV':
current = []
/*console.log('PREV', prev)*/
return prev
default:
return state
}
}
return (
<>
<AppContext.Provider value={ [state, dispatch] }>
<main>
<div className="container">
<div className="container">
<>
<div className="col align-self-start">
<Filters/>
</div>
<div className="col-9">
<CardProduct count={ count } setCount={ setCount }/>
</div>
</>
</div>
</div>
<>
<div>
<Footer className="footer" count={ count }/>
</div>
</>
</main>
</AppContext.Provider>
</>
)
}
export default App
--- THANK YOU ---
Your initB is an array declartion which doesn't re-render a React component. Replace it with useState to see the updated data after the API call.
const [init, setInit] = useState([]);
Secondly,
const initBoilers = fetchInitialBoiler()
you should invoke the fetchInitialBoiler() after the document is mounted, with the current approach chances are the API is invoked even before the intial mount.
Invoke it in an [useEffect][1] block.
useEffect(()=>{
fetchInitialBoiler() //
// simply invoke it, since you're not returning anything from the function, there's no need to save the response.
}, [])
Your function should set the state in the then/catch block, so that the component tree re-renders in an error case.
async function fetchBoilers () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json())
.then(data => setInit(response))
.catch(error => setError(error); // state
}
More on Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
So in the end the problem was to pass the fetch as initialState to useReducer. Searching online I found that is passed throught a dispatch action inside useEffect hook. In this case the useReducer hook is declared with initialState '[]' inside the component. Here the code:
const CardProduct = ({ count, setCount }) => {
const [state, dispatch] = useReducer(reducer, [])
async function initfetch () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json()).
then(data => dispatch({
type : 'INITIALIZE',
data: data
}))}
useEffect(() => {
initfetch()
}, [])

Content disappears after refreshing the page

I have a blog app that is divided into two parts. The first part is where people can write the actual blog (title, short description, body, click on the submit button) and that blog is than displayed to the screen with all the other blogs. These blogs are clickable and can be viewed. This part works just fine. If people click on a blog they can write comments to it. It works similarly like the part where you can write the blogs (people write a comment, click on the submit button and it is displayed below the blog post). Everything is store in firebase. The problem is when I refresh the page in the comment section, everything disappears and I get no error message. If I don't refresh the comment section everything works perfect, but after refresh everything disappears, but no error message is shown.
Here are the components for the comment section:
CommentHolder is responsible for displaying the comments that are connected with the actual blog post
import React from 'react';
import { projectFirestore } from '../../firebase/config';
import DeleteComment from './DeleteComment'
class CommentHolder extends React.Component {
state = { docs: [] }
_isMounted = false;
componentDidMount = () => {
const fetchDataFromFireBase = async () => {
const getData = await projectFirestore.collection("Comments")
getData.onSnapshot((querySnapshot) => {
var documents = [];
querySnapshot.forEach((doc) => {
documents.push({ ...doc.data(), id: doc.id });
});
if (this._isMounted) {
this.setState({ docs: documents })
}
});
}
fetchDataFromFireBase()
this._isMounted = true;
}
componentWillUnmount = () => {
this._isMounted = false;
}
renderContent() {
// Delete comments
const deleteComment = async (id) => {
projectFirestore.collection('Comments').doc(id).delete().then(() => {
console.log(`Blog with id: ${id} has been successfully deleted!`)
})
}
// Build comments
let user;
if (localStorage.getItem('user') === null) {
user = [];
} else {
user = JSON.parse(localStorage.getItem('user'));
const commentArray = this.state.docs?.filter(value => value.blogID === this.props.param);
const orderedComments = commentArray.sort((a, b) => (a.time > b.time) ? -1 : (b.time > a.time) ? 1 : 0);
const renderComments = orderedComments.map(comment => {
return (
<div key={comment.id} className="card mb-3" >
<div className="card-body">
<div className="row">
<div className="col-sm">
<h6>{`${comment.name} - ${comment.time}`}</h6>
<p>{comment.comment}</p>
</div>
<div className="col-sm text-right">
{user[0].id === comment.userID ? <DeleteComment commentid={comment.id} onDeleteComment={deleteComment} /> : ''}
</div>
</div>
</div>
</div>
)
})
const updateComments = () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString)
const id = urlParams.get('id')
const updateComment = projectFirestore.collection('Blogs').doc(id);
return updateComment.update({
'post.comments': commentArray.length
})
}
updateComments()
return renderComments;
}
}
render() {
return (
<div>
{this.renderContent()}
</div>
)
}
}
export default CommentHolder
The AddComment contains the whole section, the text area, the submit button and the container for the comments
import React, { useState } from 'react'
import SubmitComment from './SubmitComment'
import CommentHolder from './CommentHolder';
import { useSelector, useDispatch } from 'react-redux';
const AddComment = ({ param }) => {
const [comment, setComment] = useState('');
const dispatch = useDispatch();
const state = useSelector((state) => state.state);
if(state) {
setTimeout(() => {
setComment('')
dispatch({ type: "SET_FALSE" })
}, 50)
}
return (
<div>
<div>
<div className="row">
<div className="col-sm">
<div className="form-group">
<textarea rows="4" cols="50" placeholder="Comment" className="form-control mb-3" value={comment} onChange={(e) => setComment(e.target.value)} />
</div>
</div>
</div>
</div>
<div className="mb-3">
<SubmitComment comment={comment} param={param} />
</div>
<CommentHolder param={param} />
</div>
)
}
export default AddComment
The SubmitComment is responsible for submitting the comment to the firebase
import React from 'react'
import { projectFirestore } from '../../firebase/config';
import { v4 as uuidv4 } from 'uuid';
import { useDispatch } from 'react-redux';
const SubmitComment = ({ comment, param }) => {
const dispatch = useDispatch();
const onCommentSubmit = () => {
let user;
if (localStorage.getItem('user') === null) {
user = [];
} else {
user = JSON.parse(localStorage.getItem('user'));
projectFirestore.collection('Comments').doc().set({
id: uuidv4(),
comment,
name: `${user[0].firstName} ${user[0].lastName}`,
userID: user[0].id,
blogID: param,
time: new Date().toLocaleString()
})
dispatch({ type: "SET_TRUE" });
}
}
return (
<div>
<button onClick={() => onCommentSubmit()} className='btn btn-primary'>Add comment</button>
</div>
)
}
export default SubmitComment
The DeleteComment just deletes the comment
import React from 'react'
const DeleteComment = ({ commentid, onDeleteComment }) => {
return (
<div>
<button onClick={() => onDeleteComment(commentid)} className='btn btn-outline-danger'>X</button>
</div>
)
}
export default DeleteComment
Do you guys have any suggestions on how to solve this problem? Thank you.

How to avoid rerender of a component in React?

Creating a simple app using React and Redux.
The point is to get photos from the server, show them and if you click on the photo show modal window with bigger photo and comments.
The code for App component
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import './App.scss'
import List from './components/list/List'
import Header from './components/header/Header'
import Footer from './components/footer/Footer'
import ModalContainer from './containers/ModalContainer'
import { getPhotos, openModal } from './redux/actions/actions'
const App = () => {
const { isFetching, error } = useSelector(({ photos }) => photos)
const photos = useSelector(({ photos }) => photos.photos)
const { isOpen } = useSelector(({ modal }) => modal)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getPhotos())
}, [])
const getBigPhoto = (id) => {
dispatch(openModal(id))
}
return (
<div className="container">
<Header>Test App</Header>
<div className="list__content">
{isFetching
? <p>Loading...</p>
: error
? <p>{error}</p>
: photos.map(({ id, url }) => (
<List
key={id}
src={url}
onClick={() => getBigPhoto(id)}
/>
))
}
</div>
<Footer>© 2019-2020</Footer>
{isOpen && <ModalContainer />}
</div>
)
}
export default App
In this line I get photos only once to stop rerender if I refresh the page
useEffect(() => {
dispatch(getPhotos())
}, [])
When I click on the photo my modal opens and I want to stop rerendering all the components. For example for my header I use React.memo HOC like this
import React, { memo } from 'react'
import './Header.scss'
import PropTypes from 'prop-types'
const Header = memo(({ children }) => {
return <div className="header">{children}</div>
})
Header.propTypes = {
children: PropTypes.string,
}
Header.defaultProps = {
children: '',
}
export default Header
It works perfectly when I open and close my modal. Header and Footer are not rerendered. But List component is rerendered every time I open and close a modal window. It's happening because that prop onClick={() => getBigPhoto(id)} in List component creates a new anonymous function every time I click. As you know if your props changed, component is rerendered.
My question is how to avoid rerender of List component in my situation?
You can create a container for List that receives getBigPhoto and an id, create getBigPhoto with useCallback so the function doesn't change:
const ListContainer = React.memo(function ListContainer({
id,
src,
getBigPhoto,
}) {
return (
<List
key={id}
src={scr}
onClick={() => getBigPhoto(id)}
/>
);
});
const App = () => {
const { isFetching, error, photos } = useSelector(
({ photos }) => photos
);
const { isOpen } = useSelector(({ modal }) => modal);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPhotos());
}, []);
//use callback so getBigPhoto doesn't change
const getBigPhoto = React.useCallback((id) => {
dispatch(openModal(id));
}, []);
return (
<div className="container">
<Header>Test App</Header>
<div className="list__content">
{isFetching ? (
<p>Loading...</p>
) : error ? (
<p>{error}</p>
) : (
photos.map(({ id, url }) => (
// render pure component ListContainer
<ListContainer
key={id}
src={url}
id={id}
getBigPhoto={getBigPhoto}
/>
))
)}
</div>
<Footer>© 2019-2020</Footer>
{isOpen && <ModalContainer />}
</div>
);
};

Dispatch prop as argument to store from React Redux Container

I want to dispatch a component prop as payload to my store from the click event of a sub-component:
const Aircraft = ({ ident, type, base, handleOnClick }) => (
<div className="item" onClick={handleOnClick}>
<i className="large plane middle aligned icon"></i>
<div className="content">
<div className="header">{ident}</div>
<div className="description">{type}</div>
<div className="description">{base}</div>
</div>
</div>
);
So I want to dispatch ident to handleOnClick.
The click event is passed as a prop from the parent component and mapped from a redux container.
const AircraftList = ({ aircraftList, setCurrentAircraft }) => (
<div className="ui relaxed celled list">
{aircraftList.map((el, index) => (
<Aircraft key={index} {...el} handleOnClick={setCurrentAircraft} />
))}
</div>
);
Redux container:
import { connect } from 'react-redux';
import AircraftList from '../../components/AircraftList/AircraftList';
import { setCurrentAircraft } from '../../actions/actions';
const mapStateToProps = state => {
return {
aircraftList: state.aircraft,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setCurrentAircraft: (e) => {
dispatch(setCurrentAircraft(ident));
}
};
};
const AircraftListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(AircraftList);
export default AircraftListContainer;
I am not sure how to pass the ident from the sub component to the dispatch and subsequently update the store?
If you need the event object inside setCurrentAircraft add the ident as a second argument, else keep ident as the only argument.
const mapDispatchToProps = (dispatch) => {
return {
setCurrentAircraft: (e, ident) => { // If `event` needed, else `(ident)`
dispatch(setCurrentAircraft(ident));
}
};
};
Then wrap the handleOnClick on Aircraft inside another function to give it ident as an argument.
const Aircraft = ({ ident, type, base, handleOnClick }) => (
<div className="item" onClick={(e) => handleOnClick(e, ident)}> // or just handleOnClick(ident)
{/* ... */}
</div>
);
Try this. You need to bind your click handler and dispatch the action from that. You also need to assign the ident value somewhere in you child component, so you can access it.
Redux Container
import { connect } from 'react-redux';
import AircraftList from '../../components/AircraftList/AircraftList';
import { setCurrentAircraft } from '../../actions/actions';
class AircraftListContainer extends React.Component {
constructor(props) {
super(props)
}
handleClick = (e) => {
const ident = e.currentTarget.getAttribute("data-ident")
this.props.setCurrentAircraft(ident)
}
render () {
return (<AircraftList aircraftlist={this.props.aircraftlist} handleClick=
{this.handleClick} />)
}
}
const mapStateToProps = state => {
return {
aircraftList: state.aircraft,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setCurrentAircraft: (ident) => {
dispatch(setCurrentAircraft(ident));
}
};
};
export default connect(mapStateToProps,
mapDispatchToProps)(AircraftListContainer);
AircraftList
const AircraftList = ({ aircraftList, handleClick }) => (
<div className="ui relaxed celled list">
{aircraftList.map((el, index) => (
<Aircraft key={index} {...el} handleOnClick={handleClick} />
))}
</div>
);
Aircraft
const Aircraft = ({ ident, type, base, handleOnClick }) => (
<div className="item" onClick={handleOnClick} data-ident={ident}>
<i className="large plane middle aligned icon"></i>
<div className="content">
<div className="header">{ident}</div>
<div className="description">{type}</div>
<div className="description">{base}</div>
</div>
</div>
);

Categories