Auto Bottom Scroll in reactjs and jsx - reactjs

I have integrated dialogflow in a react page and it is working now the issue is whenever I am writing a phrase in the bot is responding but the chat window is not getting auto scrolled to bottom. I want the bot window to be automatically scrolled to bottom every time.
class App extends Component {
render() {
const { feed, sendMessage } = this.props;// structure of the bot
return (
<div // which is the main div
style={{
backgroundColor: "green",
height: "70%",
width: "23%",
position: "fixed",
bottom: 0,
right: 5
}}
>
<div // inner div
style={{
height: "67%",
width: "22%",
position: "fixed",
bottom: "30px",
maxHeight: "65%",
right: "5px",
overflowY: "scroll",
overflowX: "hidden"
}}
>
<h1>CHATBOT!</h1>
{feed.map(entry => ( // the div where the user is typing the response
<div>{entry.text}</div> // inner- inner div
))}
</div>
<div
style={{
position: "fixed",
right: "23%",
bottom: "28px",
marginLeft: "-1300px"
}}
>
<input
style={{
position: "fixed",
width: "22%",
height: "3%"
}}
type="text" // the value by which the user is connected the bot
onKeyDown={e => // this is the box where the response is coming from the bot
e.keyCode === 13 ? sendMessage(e.target.value) : null
}// 13 is the ascii of ENTER
/>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
feed: state
});
chat.js // intergration with dialogflow
const accessToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; //you have to enter your key
const client = new ApiAiClient({ accessToken });
const ON_MESSAGE = "ON_MESSAGE";
export const sendMessage = (text, sender = "user") => ({ // bot text box
type: ON_MESSAGE,
payload: { text, sender }
});
const messageMiddleware = () => next => action => {
next(action);
if (action.type === ON_MESSAGE) {
const { text } = action.payload;
client.textRequest(text).then(onSuccess);
function onSuccess(response) {// response from dialgflow
const {
result: { fulfillment }
} = response;
next(sendMessage(fulfillment.speech, "bot"));
}
}
};
const initState = [{ text: "" }];
const messageReducer = (state = initState, action) => {
switch (action.type) {
case ON_MESSAGE:
return [...state, action.payload];
default:
return state;
}
};
app.js
class App extends Component {
render() {
const { feed, sendMessage } = this.props;// structure of the bot
return (
<div // which is the main div
style={{
backgroundColor: "green",
height: "70%",
width: "23%",
position: "fixed",
bottom: 0,
right: 5
}}
>
<div // inner div
style={{
height: "67%",
width: "22%",
position: "fixed",
bottom: "30px",
maxHeight: "65%",
right: "5px",
overflowY: "scroll",
overflowX: "hidden"
}}
>
<h1>CHATBOT!</h1>
{feed.map(entry => ( // the div where the user is typing the response
<div>{entry.text}</div> // inner- inner div
))}
</div>
<div
style={{
position: "fixed",
right: "23%",
bottom: "28px",
marginLeft: "-1300px"
}}
>
<input
style={{
position: "fixed",
width: "22%",
height: "3%"
}}
type="text" // the value by which the user is connected the bot
onKeyDown={e => // this is the box where the response is coming from the bot
e.keyCode === 13 ? sendMessage(e.target.value) : null
}// 13 is the ascii of ENTER
/>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
feed: state
});
I want the bot window to be automatically scrolled whenever I type anything in the window.

In the App component, you can create a span or div and place it right below the chat-feed.
Then give that element a ref, which we will use to scroll to upon receiving any new message.
You can use React.createRef() to make that ref. Refs essentially give us access to methods you traditionally see in vanilla JavaScript.
It also looks like you're receiving updated messages via props from Redux. So we can use componentDidUpdate() to run some logic that will scroll to that ref element.
class App extends Component {
endOfFeed = React.createRef()
scrollToEnd = () => {
if(this.endOfFeed.current){
this.endOfFeed.current.scrollIntoView()
}
}
componentDidUpdate(prevProps){
if(prevProps.feed.length !== this.props.feed.length){
this.scrollToEnd()
}
}
render() {
const { feed, sendMessage } = this.props;// structure of the bot
return (
<div // which is the main div
style={{
backgroundColor: "green",
height: "70%",
width: "23%",
position: "fixed",
bottom: 0,
right: 5
}}
>
<div // inner div
style={{
height: "67%",
width: "22%",
position: "fixed",
bottom: "30px",
maxHeight: "65%",
right: "5px",
overflowY: "scroll",
overflowX: "hidden"
}}
>
<h1>CHATBOT!</h1>
{feed.map(entry => ( // the div where the user is typing the response
<div>{entry.text}</div> // inner- inner div
))}
<span ref={this.endOfFeed}></span>
</div>
<div
style={{
position: "fixed",
right: "23%",
bottom: "28px",
marginLeft: "-1300px"
}}
>
<input
style={{
position: "fixed",
width: "22%",
height: "3%"
}}
type="text" // the value by which the user is connected the bot
onKeyDown={e => // this is the box where the response is coming from the bot
e.keyCode === 13 ? sendMessage(e.target.value) : null
}// 13 is the ascii of ENTER
/>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
feed: state
});

You can use window.scrollY property and give offset accordingly.
Check this https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY

Related

Filtering array to remove filtered object in react

Objective is to have an array with captured pokemons if user clicks on the input and an array of not-captured pokemons if user un-clicks the input. I've managed to filter out the pokemon when it's no longer captured and have it in the not-captured array but I can't seem to delete that pokemon from the old captured array.
Eg. If I click on "bulbasaur", "charmender", "squirtle", I get them all in the captured array. If I then remove one of them, I correctly get the removed one in the not-captured array but I can't seem to delete it from the previous captured array.
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import PokemonIcon from "./PokemonIcon";
const PokemonCard = ({ pokemon, capturedPkm, setCapturedPkm, notCapturedPkm, setNotCapturedPkm }) => {
const [label, setLabel] = useState('Not captured')
const toggleCaptured = (checked, id) => {
const pokemonName = { id: pokemon.id, name: pokemon.name }
if (checked && id === pokemon.id) {
setCapturedPkm([...capturedPkm, pokemonName])
setLabel('Captured!')
} else {
setLabel('Not captured!')
setNotCapturedPkm([...notCapturedPkm, pokemonName])
if (checked === false) {
let newArr = [...capturedPkm]
let pkmRemoved = newArr.filter((el, i) => el.id === id)
let newArrPkm = newArr.splice(pkmRemoved, 1)
console.log('newArr',newArrPkm, 'pkmRemoved', pkmRemoved)
setCapturedPkm(newArrPkm)
}
}
}
useEffect(() => {
console.log('captured', capturedPkm, 'not captured', notCapturedPkm)
}, [capturedPkm, notCapturedPkm])
return (
<>
<div
className="pokemon-card"
style={{
height: "250px",
maxWidth: "250px",
margin: "1rem",
boxShadow: "5px 5px 5px 4px rgba(0, 0, 0, 0.3)",
cursor: "pointer",
}}
>
<Link
to={{ pathname: `/pokemon/${pokemon.id}` }}
style={{ textDecoration: "none", color: "#000000" }}
state={{ pokemon: pokemon, capturedPkm }}
>
<div
style={{
padding: "20px",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<PokemonIcon img={pokemon.sprites?.['front_default']} />
</div>
</Link>
<div style={{ textAlign: "center" }}>
<h1>{pokemon.name}</h1>
<label>
<input type="checkbox"
defaultChecked={false}
value={pokemon.name}
onChange={(e) => toggleCaptured(e.target.checked, pokemon.id)} />
<span style={{ marginLeft: 8, cursor: 'pointer' }}>
{label}
</span>
</label>
</div>
</div>
<div></div>
</>
);
};
export default PokemonCard;
I guess you forgot to update the notCapturedPkm array. You could try something like this :
if (checked && id === pokemon.id) {
setCapturedPkm([...capturedPkm, pokemonName])
// Update this array, by removing the selected pokemon
setNotCapturedPkm([...notCapturedPkm.filter(pkm => pkm.id !== pokemon.id)])
setLabel('Captured!')
}

Drop Event won't be fired (react-draggable)

Currently i'm writing draggable component with react-draggable.
However, when i drag my component into another component (outside of component parent), onDrop event won't fire.
Below here is my component:
const DraggableBaseCard = (props: {
id: String,
positionX: Number,
positionY: Number,
positionZ: Number,
width: Number | String,
height: Number | String,
zoomFactor: Number,
isLocked: Boolean,
}) => {
const boardStore = useBoardStore();
const [position, updatePosition] = useState({
x: props.positionX,
y: props.positionY,
});
const onDragStop = (_, elementData) =>
handleDrop(elementData, updatePosition, boardStore, props.id);
return (
<Draggable
defaultClassName="gulabee-base-card"
disabled={props.isLocked}
handle={props.customHandle ?? ".draggable-component"}
bounds={props.bounds ?? { left: 0, top: 0 }}
defaultPosition={position}
onStop={props.onStop ?? onDragStop}
onDrag={props.onDrag}
scale={props.zoomFactor || 1}
key={props.id}
>
<div
{...props}
className={`draggable-component ${props.className || ""} p-2`}
onDragStart={(e) => {
e.dataTransfer.setData("cardID", props.id);
console.log("Drag Start");
}}
style={{
zIndex: props.positionZ,
cursor: "pointer",
position: "absolute",
width: props.width || "10rem",
height: props.height || "auto",
border: props.noBorder
? undefined
: "solid 1px rgba(0, 0, 0, 0.3)",
}}
>
<Dropdown
overlay={() => CardContextMenu(props.id)}
onContextMenu={(e) => {
e.stopPropagation();
}}
trigger={["contextMenu"]}
>
<div
className="card-children"
style={{ width: "100%", height: "100%" }}
>
{props.children}
</div>
</Dropdown>
</div>
</Draggable>
);
};
const handleDrop = (elementData, updatePosition, boardStore, cardId) => {
updatePosition({
x: roundByGridSize(elementData?.x || 0, GRID_SIZE),
y: roundByGridSize(elementData?.y || 0, GRID_SIZE),
});
boardStore.cards[cardId].positionX = elementData?.x / GRID_SIZE;
boardStore.cards[cardId].positionY = elementData?.y / GRID_SIZE;
};
Here is how i test drop area:
const PocketBag = observer((props) => {
return (
<div style={{ height: "100%" }} onDrop={(e) => alert("Dropped")}>
Dropzone
</div>
);
});
When i drag the DraggableBaseCard into PocketBag, the alert won't show up.
The onDragStart event of the DraggableBaseCard is not working either unless i set draggable props to true, but it somehow conflict with Draggable component
Please help me with my problem i'm crying :(
You need to allow dropping by adding this code to the element to want to drop on. HTML by default doesn't allow drops
onDragOver={event=>event.preventDefault()}

react setState not rendering everytime button is pressed

I'm trying to use setState to access my css const mystyle object to change the background color on the squares from blue to red but everytime the button is pressed. It seems everytime I press the button the Setstate does not render on screen any advice or help? Would be greatly appreciated
class MyHeader extends React.Component {
constructor(props){
super(props)
this.state = {backgroundColor: 'blue'};
}
render() {
const mystyle = {
borderRadius: "10px",
background: this.state.backgroundColor,
padding: "10px",
width: "100px",
height: "100px",
marginTop: "10px",
lineHeight: "80px",
color: "dimGrey",
fontWeight: "bold",
fontSize: "3em",
textAlign: "center"
};
function State() {
this.setState({backgroundColor: 'red'})
}
return (
<div>
<h1 style={mystyle}></h1>
<h1>{this.state.backgroundColor}</h1>
</div>
);
}
}
function Test() {
function Test2() {
setchange(change + Math.floor(Math.random() * 10));
if(change > 20) {
setchange(change + Math.floor(Math.random() - 10))
}
}
const [change, setchange] = React.useState(1)
return (
<div>
<h1>click the button to randomize colors</h1>
<button onClick={this.State}>Randomize colors!</button>
<div className='.flex-item'></div>
<h1>{change}</h1>
<div className="flex-item"></div>
<MyHeader />
<MyHeader />
</div>
);
}
ReactDOM.render(<Test />, document.getElementById("root"));
my codepen link to the code
The main issue is trying to do something where the child component, MyHeader, has the function to change state, but trying to invoke it from the parent component, Test. It's just simpler to pass the color as a prop from Test to MyHeader.
Alternatively, you can do the useContext thing, but I think this is easier. I've stripped out all superfluous code that wasn't getting used. You can of course, add them back as you need to.
const MyHeader = ({backgroundColor}) => {
const mystyle = {
borderRadius: "10px",
background: backgroundColor,
padding: "10px",
width: "100px",
height: "100px",
marginTop: "10px",
lineHeight: "80px",
color: "dimGrey",
fontWeight: "bold",
fontSize: "3em",
textAlign: "center"
};
return (
<div>
<h1 style={mystyle}></h1>
<h1>{backgroundColor}</h1>
</div>
);
}
}
const Test = (props) => {
const [backgroundColor, setBackgroundColor] = useState("blue");
const onButtonClick = () => {
setBackgroundColor("red");
}
return (
<div>
<h1>click the button to randomize colors</h1>
<button onClick={onButtonClick}>Randomize colors!</button>
<div className='.flex-item'></div>
<h1>{change}</h1>
<div className="flex-item"></div>
<MyHeader backgroundColor={backgroundColor} />
</div>
);
};

(google-maps-react) Material-UI popover detail bubble won't follow map marker when map centers to marker (LatLng)

I'm building a map with map markers that show a detail bubble built with the Material-UI Popover component. My code centers the marker when it is clicked, but the detail bubble/popover remains in the spot over where the map marker was before it was centered.
Here is a pic of the detail bubble/Popover when the map marker is centered:
I already tried positioning the detail bubble/popover as such:
.popover {
position: element(#map-marker);
transform: translateY(-100%);
}
But it still behaves the same. I think the popover component
can't calculate the change in the positioning of the map marker because the change is dictated by lat/lng values for the center of the map. I just can't think of any way to circumvent this.
Here is the full code:
Map.js
class ShowsMap extends Component {
constructor(props) {
super(props);
this.state = {
detailBubbleAnchorEl: null // The currently selected marker that the popover anchors to
}
}
handleDetailClose = () => {
this.setState({
detailBubbleAnchorEl: null
})
}
handleMarkerClick = (event, lat, lng) => {
this.setState({
detailBubbleAnchorEl: event.currentTarget
})
// Set center coordinates of map to be those of the map marker (redux action)
this.props.setSearchCenter({ lat, lng })
}
renderMap = () => {
const { detailBubbleAnchorEl } = this.state;
const detailOpen = Boolean(detailBubbleAnchorEl);
const { viewport } = this.props.searchLocation;
const { zoom } = fitBounds(viewport, { width: 400, height: 600})
return (
<GoogleMapReact
yesIWantToUseGoogleMapApiInternals
bootstrapURLKeys={{ key: MYAPIKEY }}
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
zoom={zoom + 1}
center={this.props.searchLocation.center}
onGoogleApiLoaded={({ map, maps }) => this.handleApiLoaded(map, maps)}
>
{
showLocations.map((location, index) => {
const { lat, lng } = location;
return (
<div lat={lat} lng={lng} key={index}>
<MapMarker onClick={(event) => this.handleMarkerClick(event, lat, lng)} />
<DetailBubble
id="event"
open={detailOpen}
anchorEl={detailBubbleAnchorEl}
onClose={this.handleDetailClose}
/>
</div>
)
})
}
</GoogleMapReact>
)
}
render() {
return (
<div ref={map => this.map = map} style={{ width: '100%', height: '100%',}}>
{this.renderMap()}
</div>
);
}
DetailBubble.js
const DetailBubble = ({ classes, open, anchorEl, onClose, id }) => {
return(
<Popover
id={id}
classes={{ paper: classes.container}}
open={open}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
>
</Popover>
)
}
const styles = theme => ({
container: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
width: '200px',
height: '150px'
}
});
MapMarker.js
const styles = theme => ({
markerContainer: {
position: 'absolute',
width: 35,
height: 35,
left: -35 / 2,
top: -35 / 2,
},
marker: {
fill: '#3f51b5',
'&:hover': {
fill: 'blue',
cursor: 'pointer'
}
}
})
function MapMarker({ classes, onClick }) {
return (
<div className={classes.markerContainer}>
<Marker onClick={onClick} className={classes.marker} width={30} height={30} />
</div>
)
}
Thanks in advance for your help!

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