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>
);
Related
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>
);
}
I have two components, the parent and child. Currently I have these codes below. But unfortunately it returns an error:
TypeError: Cannot read property 'click' of null
For some reasons I want when button is click the Item component also will be click. But these codes below produces an error above. Anyone does know how to achieve it?
import React, { useRef } from 'react';
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
{dynamicBoolean ? (
<button onClick={() => itemRef.current.click()}>
click item
</button>
) : (
//more codes here
<Item ref={itemRef} />
)}
</div>
);
};
export default App;
Child component would look like below (demonstration purposes, the code is very lengthly)
import React from 'react';
const Item = (props) => {
return (
<div>
//some design here
</div>
);
};
export default Item;
You need useRef and you have to forward this ref to the Item component.
import React, { forwardRef, useRef } from 'react';
const Item = forwardRef((props, ref) => {
return <li {...props}
onClick={() => alert('clicked on Item')}
ref={ref} >MyItem</li>
})
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
<button onClick={() => itemRef.current.click()}>
click item
</button>
<Item ref={itemRef} />
</div>
);
};
export default App;
import React, { createRef } from "react";
const Hello = (props) => {
const itemRef = createRef();
const hello = () => {
itemRef.current.click();
};
return (
<div>
<button onClick={() => hello()}>click item</button>
<Item ref={itemRef} />
</div>
);
};
const Item = React.forwardRef((props, ref) => {
const myClick = () => {
console.log("this is clicked");
};
return (
<button ref={ref} className="FancyButton" onClick={myClick}>
{props.children}
</button>
);
});
export default Hello;
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))
}
}
I really like react context, but I think it's missing something (or maybe I don't know how to do it)
Say I have a list of todos and it's corresponding provider as
const Home = () => (
<div className="container">
<TodosProvider>
<TodosList />
</TodosProvider>
</div>
)
const TodosList = () => {
const { todos } = useTodos();
return (
<>
{todos.map((todo, idx) => (
<SingleTodo />
))}
</>
)
}
And in another file
import { createContext, useContext, useState } from "react";
const TodosContext = createContext({});
export const TodosProvider = ({ children }) => {
const [todos, setTodos] = useState([{ text: 'a' }, { text: 'b' }, { text: 'c' }])
return (
<TodosContext.Provider value={{ todos }}>
{children}
</TodosContext.Provider>
)
}
export const useTodos = () => {
const todos = useContext(TodosContext)
return todos
}
How can I update a single todo inside the SingleTodo without:
1) Passing the map idx as a property to the SingleTodo and then from SingleTodo call a method of the TodosList provider passing the idx as a parameter
2) Giving an artificial id property to the todo. And then in TodosProvider update the todo that matches with that id.
The reasons for those restrictions are that:
1) Passing down the position of the todo in the rendering as a prop, invalidates the benefits of using context, which is to not have to do prop drilling
2) I don't think it's good to pollute the model with an artificial id just for state management.
I'd like to be able to create a SingleTodoContext and instantiate a SingleTodoProvider in each iteration of the loop
const TodosList = () => {
const { todos } = useTodos();
return (
<>
{todos.map((todo, idx) => (
<SingleTodoProvider key={idx} loadFrom={todo}>
<SingleTodo />
</SingleTodoProvider>
))}
</>
)
}
But that doesn't work because the provider would then need to store the loadFrom property as a state, and that would break the sync between the list todo, and the single todo.
So, how do I update a single item inside a list without prop drilling the position of the item in the list? I don't want to use Redux
You can pass methods for updating the values in context as part of your context. Here is an example based on your code (sort of all crammed together):
import React from "react";
import "./styles.css";
import { createContext, useContext, useState } from "react";
const TodosContext = createContext({});
export const TodosProvider = ({ children }) => {
const [todos, setTodos] = useState([
{ text: "a" },
{ text: "b" },
{ text: "c" }
]);
const selectTodo = (todo, idx) => {
console.log(
"do something with the todo here and then call setTodos, or something else?",
todo.text,
idx
);
// setTodos(prev => /* Do something here to update the list */)
};
return (
<TodosContext.Provider value={{ selectTodo, todos }}>
{children}
</TodosContext.Provider>
);
};
export const useTodos = () => {
const todos = useContext(TodosContext);
return todos;
};
const Home = () => (
<div className="container">
<TodosProvider>
<TodosList />
</TodosProvider>
</div>
);
const SingleTodo = ({ todo, onClick }) => (
<div>
{todo.text} <button onClick={() => onClick(todo)}>Click Me!</button>
</div>
);
const TodosList = () => {
const { selectTodo, todos } = useTodos();
return todos.map((todo, idx) => (
<SingleTodo onClick={todo => selectTodo(todo, idx)} todo={todo} key={idx} />
));
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Home />
</div>
);
}
Hope that helps!
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)
}/>