Use child component props value in event handler in react - reactjs

Here is my code
import React from 'react';
import './style.scss';
const CalcButton = (props) => {
return (
<button id="calcBtn" style={props.style} onClick={props.onClick}>
{props.btnText}
</button>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentNum: '0',
log: ' ',
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
console.log('a', e.target.value);
this.setState((state) => ({
currentNum: state.currentNum + e.target.getAttribute(this.props.btnText),
}));
}
render() {
return (
<div id="container">
<div id="display">
<h3 id="log">{this.state.log}</h3>
<h3 id="currentNum">{this.state.currentNum}</h3>
</div>
<CalcButton
style={{
width: 150,
backgroundColor: 'rgb(173, 0, 0)',
}}
btnText="AC"
onClick={() => {
return this.setState({
currentNum: '',
log: '',
});
}}
/>
<CalcButton
btnText="/"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton
btnText="X"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton btnText="7" onClick={this.handleClick}/>
<CalcButton btnText="8" />
<CalcButton btnText="9" />
<CalcButton
btnText="-"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton btnText="4" />
<CalcButton btnText="5" />
<CalcButton btnText="6" />
<CalcButton
btnText="+"
style={{
backgroundColor: 'gray',
}}
/>
<CalcButton btnText="1" />
<CalcButton btnText="2" />
<CalcButton btnText="3" />
<CalcButton
btnText="="
style={{
float: 'right',
height: 150,
backgroundColor: 'rgb(34, 86, 134)',
}}
/>
<CalcButton
btnText="0"
style={{
width: 150,
}}
/>
<CalcButton btnText="." />
</div>
);
}
}
export default App;
As you can see I am trying to build a calculator, but the problem I am facing is that I want to use btnText prop value of CalcButton in handleClick event handler, but I am unable to figure out how to access it in the said event handler. I know it is a very basic problem but trust me I have searched and unable to find any reference regarding my problem, kindly help.

add data-btntext={props.btnText} to the button element in the CalcButton function. Then access it with e.target.dataset.btntext. –
Emil Karlsson
The above mentioned comment solves the problem,although I do agree with #maxagno3's answer, however, since I am still learning, I really wanted to learn that concept.
Thanks for all the answers.

I do not think you need to create CalcButton component. Since your button text is going to be the same, you can do it the following way -
Create a new state which stores your button texts in an array.
Map over that state and create buttons for the calculator.
When using the handle click function, pass in the button text value.
In that function check if the button text is AC then execute another function or set the state value. Better if you create a separate function for the AC button and call it in the if-else condition you'd be doing inside the handleClick function.
If you really need to create the component then you'd need to add the handle click function to the multiple components you are reusing and pass in the value of button text into it as an argument.
I hope this helps.

in child component :
const CalcButton = (props) => {
const handleClick = () => {
if (props.onClick) {
props.onClick(props.btnText);
}
};
return (
<button id="calcBtn" style={props.style} onClick={handleClick}>
{props.btnText}
</button>
);
};
In Parent component :
class App extends React.Component { ...
handleClick(val) {
console.log('a', val);
this.setState((state) => ({
currentNum: state.currentNum + val,
}));
}
......
<CalcButton btnText="7" onClick={this.handleClick}/>

Related

How do I use a useSwipeable handler for multiple elements

I'm trying to add support for touch events on slider inputs in my React app. Taps and drags work fine with bare React. The only other touch event I need is onTouchEnd to determine when a user has finished dragging a slider and the new value is to be committed.
I'm trying to use react-swipeable. I don't know if my question is specific to Swipeable or more generally to React. I've created the suggested handler for the onTouchEnd event and it works perfectly, but for only one element. I am mapping through 20 or 30 sliders and find that only the LAST slider works properly; the other sliders do not fire the handler at all.
I've tried it with and without the refPassthrough enhancement. The problem may be my limited understanding of useRef.
In the code below, three horizontal divs are created. Touching on the last (blue) div logs the event; the others don't.
I've also provided this working code in CodeSandBox
Any help would be appreciated,
Bill
import { useRef } from "react";
import { useSwipeable } from "react-swipeable";
export default function App() {
const handlers = useSwipeable({
onTouchEndOrOnMouseUp: (e) => console.log("User Touched!", e)
});
const myRef = useRef();
const refPassthrough = (el) => {
handlers.ref(el);
myRef.current = el;
};
return (
<div>
<h1>Re-use Swipeable handlers </h1>
<div
{...handlers}
style={{ backgroundColor: "red", height: "50px" }}
></div>
<div
{...handlers}
ref={refPassthrough}
style={{ backgroundColor: "green", height: "50px" }}
></div>
<div
{...handlers}
style={{ backgroundColor: "blue", height: "50px" }}
></div>
</div>
);
}
This is AN answer. It doesn't work perfectly for me because my use is within a function, not a functional component. I will try to re-work my app so that I can call useSwipeable only within functional Components.
import { useSwipeable } from "react-swipeable";
export default function App() {
return (
<div>
<h1>Re-use Swipeable handlers </h1>
<div
{...useSwipeable({
onTouchEndOrOnMouseUp: () => console.log("touch red")
})}
style={{ backgroundColor: "red", height: "50px" }}
></div>
<div
{...useSwipeable({
onTouchEndOrOnMouseUp: () => console.log("touch green")
})}
style={{ backgroundColor: "green", height: "50px" }}
></div>
<div
{...useSwipeable({
onTouchEndOrOnMouseUp: () => console.log("touch blue")
})}
style={{ backgroundColor: "blue", height: "50px" }}
></div>
</div>
);
}
Here is a more complete answer. Also in CodeSandbox
// It's surprisingly hard to process an onTouchEnd event for a slider in React.
// The useSwipeable hook does the heavy lifting.
// However, because it is a hook, it can't be .map-ped unless it is
// wrapped in a component.
// And, once it is wrapped in a component, it is hard to communicate
// onChange events to a parent component (the ususal tricks of passing
// setState or other changehandler do not seem to work for continuous
// slider onChange events.
// The approach here is to handle all of the onChange stuff in the wrapped
// component, including a local feedback display.
// Then, on the onTouchEnd event, the "normal" communication of the final
// value is returned to the parent via the dispatch prop.
import { useReducer, useState } from "react";
import { useSwipeable } from "react-swipeable";
export default function App() {
const [reduceState, dispatch] = useReducer(reducer, {
name1: "33",
name2: "66"
});
function reducer(state, action) {
return { ...state, [action.type]: action.data };
}
const MapWrappedSlider = (props) => {
const [currentValue, setCurrentValue] = useState(props.initialValue);
return (
<div style={{ backgroundColor: "cornsilk" }}>
<h2>{currentValue}</h2>
<input
type="range"
value={currentValue}
{...{
onChange: (e) => setCurrentValue(e.target.value),
onMouseUp: () =>
dispatch({ type: props.paramName, data: currentValue })
}}
{...useSwipeable({
// note: the current version of useSwipeable does not actually
// handle onMouseUp here. Also, the advertised onTouchEnd
// does not actually handle onTouchEnd
onTouchEndOrOnMouseUp: () =>
dispatch({ type: props.paramName, data: currentValue })
})}
/>
</div>
);
};
return (
<div style={{ textAlign: "center" }}>
<h1>SWIPEABLE MAPPED SLIDERS</h1>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-around"
}}
>
<h2>{reduceState.valueName}</h2>
{["name1", "name2"].map((paramName) => {
return (
<div key={paramName}>
<h1>{reduceState[paramName]}</h1>
<MapWrappedSlider
paramName={paramName}
initialValue={reduceState[paramName]}
dispatch={dispatch}
/>
</div>
);
})}
</div>
</div>
);
}

React state does not get updated

I know there's been many questions with this topic asked already, but it really feels like each and every one of them is different and I cannot find one that matches my issue closely enough.
I have a grid with draggable ItemComponents. Once selected, additional action icons show up (ItemActionIcon). I would very much like to unselect the component (and effectively hide the action icons) once one of the actions is clicked.
and so in line 77 <div onClick={() => this.setState({selected: false})} key={index}> I'm attempting to update the state of selected to false. It already works just fine in all other cases mentioned in the file. But not in this case. When I click the icon, I can see with a debugger (or with a console.log when I tried it) that the onClick action is triggered as expected and the ItemComponent even gets another call to the render method, but the this.state.selected is still set to true.
import React, {Component} from "react";
import Draggable, {DraggableBounds} from "react-draggable";
import ItemComponentAction from "./ItemComponentAction";
import ItemActionIcon from "./ItemActionIcon";
export interface Position {
x: number;
y: number;
}
export interface ItemComponentProps {
gridSquareSize: number;
canvasBounds: DraggableBounds;
margin: number;
position: Position;
}
interface ItemComponentState {
gridSquareSize: number;
canvasBounds: DraggableBounds;
margin: number;
selected: boolean;
}
export default abstract class ItemComponent extends Component<ItemComponentProps> {
protected abstract readonly icon: string;
protected abstract readonly actions: ItemComponentAction[];
state: ItemComponentState;
protected constructor(props: ItemComponentProps) {
super(props);
this.state = {
gridSquareSize: props.gridSquareSize,
canvasBounds: props.canvasBounds,
margin: props.margin,
selected: false
};
}
render() {
return (
<Draggable grid={[this.state.gridSquareSize / 2, this.state.gridSquareSize / 2]}
defaultPosition={{
x: this.state.margin + this.props.position.x * this.state.gridSquareSize,
y: this.state.margin + this.props.position.y * this.state.gridSquareSize
}}
handle=".handle"
bounds={this.state.canvasBounds}
onDrag={() => this.setState({selected: false})}
>
<div tabIndex={0}
className="handle"
onClick={() => this.setState({selected: true})}
onBlur={() => this.setState({selected: false})}
style={{
position: 'absolute',
backgroundColor: 'red',
width: this.state.gridSquareSize,
height: this.state.gridSquareSize,
cursor: "move"
}}
>
{this.icon}
{
!this.state.selected || !this.actions.length
? null
: (
<div style={{
position: 'absolute',
bottom: "0"
}}>
{
this.actions.map((action, index) => (
<div onClick={() => this.setState({selected: false})} key={index}>
<ItemActionIcon {...action}/>
</div>
))
}
</div>
)
}
</div>
</Draggable>
);
}
}
so what's the deal?
The outer <div> of your component has its own onClick handler which is setting the value of your state back to false. Try using stopPropagation() on the inner onClick handled event. That will prevent the event from propagating to the outer parent <div>, and only the inner onClick handler will execute when it is clicked on.
{
!this.state.selected || !this.actions.length ? null : (
<div
style={{
position: "absolute",
bottom: "0"
}}
>
{this.actions.map((action, index) => (
<div
onClick={e => {
e.stopPropagation();
this.setState({ selected: false });
}}
key={index}
>
<ItemActionIcon {...action} />
</div>
))}
</div>
);
}

How to close a modal in react native?

I am unable to close a modal. I am displaying few images inside it, and onPress of the "X(close)" icon, want to close the modal. I have tried setting the state of modalvisible to false, by default which is set to true. But on press of icon the modal doesn't gets closed. Any solution would be of great help.
export default class imagenav extends Component{
constructor(props){
super(props)
state = {
modalVisible: false,
}
}
openmodal(){
this.setState(modalVisible: true)
}
render() {
return (
<Container>
<Modal onRequestClose={() => {}}>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{source: {uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/1200px-Google_Images_2015_logo.svg.png",
dimensions: {width: 1080, height: 1920}}
},
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false,
})
console.log("getting closed");
}}
/>
</Right>
</Header>
</Modal>
</Container>
);
}
}
You could use an inline if to only render your modal is your state allows it :
{this.state.modalVisible &&
<Modal onRequestClose={() => { }}>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{
source: {
uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/1200px-Google_Images_2015_logo.svg.png",
dimensions: { width: 1080, height: 1920 }
}
},
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false,
})
console.log("getting closed");
}}
/>
</Right>
</Header>
</Modal>
}
You have a state, but you are not using it
<Modal onRequestClose={()=> this.openmodal(false)} visible={this.state.modalVisible}> should be good to go.
oh and your openmodal function can be used for opening and closing the modal
openmodal(value){
this.setState({modalVisible: value})
}
<Icon
name='close'
color='white'
onPress={() => {
this.openmodal(false)
console.log("getting closed");
}}
/>
It is enough to put a state for it in visible as bellow:
<Modal onRequestClose={()=> null} visible={this.state.active} transparent={true}>
/////your Views and things to show in modal
</Modal>
in your state you have to make it as blew:
constructor(props) {
super();
this.state = {
active:false,
}
}
And then you have to toggle it in an onPress for example:
onPress=()={
this.setState({active:true})
}
So totally in your project you will have:
export default class imagenav extends Component{
constructor(props){
super(props)
state = {
modalVisible: false,
}
}
openmodal(){
this.setState({modalVisible: true})
}
render() {
return (
<Container>
<Modal visible={this.state.modalVisible} onRequestClose={() => {}}>
<View style={{flex:1}}>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{source: {uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/1200px-Google_Images_2015_logo.svg.png",
dimensions: {width: 1080, height: 1920}}
},
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false,
})
console.log("getting closed");
}}
/>
</Right>
</Header>
</View>
</Modal>
</Container>
);
}
}
Update:
According to your last request there is a way. You can pass flag to your next screen and in the componentDidMount() of next screen you can check it. if it is true you can show the modal otherwise ignore it.
I hope I could help. :)

How can I display an array of images inside a Modal in react-native?

I am using galleryswiper library to display an array of images inside a modal but when i navigate to the modal component and onPress the modal opens up i don't see any images. Can anyone help me how to pass the image sources inside the modal? Also i am unable to close the modal on Press of the icon.
export default class imagenav extends Component{
constructor(props){
super(props)
state = {
modalVisible: true,
};
}
closeModal() {
this.setState({modalVisible: false});
}
render() {
return (
<Modal visible={this.modalVisible} onRequestClose={() => {} }>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{source: {uri:
dimensions: {width: 1080, height: 1920}}
}
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false
})
//this.closeModal()
}}
/>
</Right>
</Header>
</Modal>
);
}
}

Linking to a different page using react-day-picker and react router v4

I'm trying to utilize this example in order to create a calendar that lists out the events in the current month, I have this part working, but what I have yet to figure out is how to make it so that the user can click the event name and it would take them to that event page.
So per that example, if they click on one of the birthdays, it would take them to an events page where they could see more about that birthday.
Currently, my events page is being rendered using this function:
renderEvents() {
const {events} = this.state
this.state.events = {};
let eventItems = this.state.eventGet.map(event => {
console.log(event.id)
if(typeof(events[moment(event.date).date()]) !== "undefined") {
events[moment(event.date).date()].push(event.name)
} else {
events[moment(event.date).date()] = [event.name]
}
});
function renderDay(day) {
const date = day.getDate();
const dateStyle = {
position: 'absolute',
color: 'lightgray',
bottom: 0,
right: 0,
fontSize: 20,
};
const containerStyle = {
margin:'2px',
border: '1px solid #3a87ad',
borderRadius: '3px',
position: 'relative',
display: 'block',
cursor: 'pointer'
};
const textStyle = {
fontSize: '0.8em',
textAlign: 'left',
margin: '1.5px',
}
const cellStyle = {
height: 150,
width: 160,
position: 'relative',
};
return (
<div style={cellStyle}>
<div style={dateStyle}>{date}</div>
{events[date] &&
events[date].map((name, i) => (
<div onClick={() => this.props.history.push('/organizations/' + this.props.match.params.orgID + '/events' + i)} key={i} style={containerStyle}>
<div style={textStyle}> {name} </div>
</div>
))}
</div>
);
}
return (
<div>
<Grid component="section" className="section--center" shadow={0} noSpacing>
<Cell col={12}>
<FABButton style={{margin: '10px', float: "right"}} colored ripple onClick={() => this.props.history.push('/organizations/' + this.props.match.params.orgID + '/events')}>
<Icon name="add" />
</FABButton>
</Cell>
<DayPicker
canChangeMonth={true}
className="Birthdays"
renderDay={renderDay}
/>
</Grid>
</div>
);
}
The current problem is within the sub-function, renderDay which is called by the DayPicker component that gets the events associated with the day. When I try to push to the history property, it errors out and says that I cannot read property 'history' from undefined, which makes sense because we did not pass the history property to that function.
Can someone help me in figuring out how to modify that sub-function so that the onClick event on the div will take a user to that events page?
and says that I cannot read property 'history' from undefined
Make sure your renderDay function is bound to the correct this:
<DayPicker
canChangeMonth
className="Birthdays"
renderDay={renderDay.bind(this)}
/>

Resources