Auto Scroll When new message send or received - javascript

// Hi i am a creating a simple chat Application. In this component ,I am Showing All the messages between users .Everything is working as expected .Now i want when a user receives or sends new message i want it should auto scroll to the last message. Now I want to add this feature
import React from "react";
import { Link } from "react-router-dom";
import { useSelector } from "react-redux";
function ShowMessages() {
const currentUserName = useSelector(
(state) => state.user.currentUser.username
);
const allmessages = useSelector((state) => state.message.allMessage);
const chatWith = useSelector((state) => state.user.chatWith);
const LoggedInUser = currentUserName && currentUserName.split("#")[0];
return (
<>
{allmessages &&
allmessages
.filter((item) => {
console.log("item", item.from, item.to, chatWith.id);
return item.fromto === chatWith.id;
})
.map((item, idx) => (
<li
className={item.direction === "send" ? "replies" : "sent"}
key={idx}
>
<div className="media">
<h5>
{item.messageBody}
</h5>
</div>
</li>
))}
</>
);
}
export default ShowMessages;

Right now you have a component ShowMessages, which renders messages, but in your snippet i can't see any container (perhaps you have it a different component). What i would do is put a ref (by using "useRef") on the container, which holds all of the messages.
And every time any message is added (this could be tracked via useEffect), you can get access to the container via the ref (don't forget to use .current). Then, you can choose how to scroll down from this thread - Scroll to bottom of div?
First answer should work just fine. Just remember, that you get the element by the ref!

Related

How to make element scrolled to bottom in React.js?

In my app I want to make my element always scrolled to bottom after getting new logs.
For some reason my logsRef.current.scrollTop has value of zero all the time. My logs do show on screen and in console. I am not sure why is this not working, I've tried to use different approaches using useLyaoutEffect() but nothing made logsRef.current.scrollTop value change, it stayed zero all the time.
//my Logs.jsx component
import { useEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`}>
<div ref={logsRef}>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also, I do render my Logs.jsx in BattlePhase.jsx component where I do my attack logic on click and I save logs using useState() hook.
//parts where i do save my logs in BattlePhase.jsx
const [logs, setLogs] = useState([]);
const attackHandler = () => {
//logs where pokemon on left attacked pokemon on right
setLogs((prevLogs) => [
...prevLogs,
`${pokemonDataOne.name} attacked ${
pokemonDataTwo.name
} for ${attack.toFixed(2)} dmg`,
`${pokemonDataTwo.name} died`,
])
}
...
<Attack className={isActiveArrow}>
<Button onClick={attackHandler}>Attack!</Button>
</Attack>
Slight long shot but it's possible that the ref is attached to the wrong element. Are you sure the element with the CSS property that makes it scrollable (overflow) isn't on <Container>?
//my Logs.jsx component
import { useLayoutEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useLayoutEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`} ref={logsRef}>
<div>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also to confirm, you do need useLayoutEffect here.

React context not updating on certain places

On button click I'am adding product information to local storage and setting it to context.
I'm using context in two places - header and products page, syntax is almost the same. There is a console.log in header and whenever i'am interacting with context it's instantly firing, confirming that context is working fine. However, when i enter products page i need to reload it one more time in order to get the data. Why is that?
Context file
import React, { useState, useEffect } from "react";
export const LikedContext = React.createContext([{}, () => {}]);
export const LikedProvider = (props) => {
const [liked, setLiked] = useState(null);
useEffect(() => {
if (typeof window !== "undefined") {
let likedData = localStorage.getItem("liked");
likedData = likedData ? JSON.parse(likedData) : "";
setLiked(likedData);
}
}, []);
return (
<LikedContext.Provider value={[liked, setLiked]}>
{props.children}
</LikedContext.Provider>
);
};
Header
export const LikedProductsIcon = () => {
const [liked, setLiked] = useContext(LikedContext);
console.log("liked header", liked);
const likedCount =
null !== liked && Object.keys(liked).length ? liked.totalProductsCount : "";
const totalPrice =
null !== liked && Object.keys(liked).length ? liked.totalProductsPrice : "";
return (
<>
<CartContainer to="/pamegti-produktai">
<CountCircle>
<HeartSvg />
{likedCount ? <span>{likedCount}</span> : ""}
</CountCircle>
</CartContainer>
</>
);
};
Liked products page
const LikedProducts = () => {
const [liked, setLiked] = useContext(LikedContext);
console.log("liked page", liked);
return (
<Layout>
<Container>
{liked ? (
liked.products.map((product, index) => (
<ProductsCard product={product} blackText />
))
) : (
<div>Pamėgtų produktų nėra.</div>
)}
</Container>
</Layout>
);
};
If you render multiple <LikedProviders>, then they will each have their own independent state. Setting state in one of them will not affect the other one. The reason they sync up when you refresh the page is that they both load from local storage and see the same value. But that only happens on load, and they will get out of sync after that.
If you want the state to be shared, then you want to just render one of them. Place it at the top of your component tree (app.js is a good spot) so that it's accessible by all the components that need it, and delete the other one.

Execute GetServerSideProps each time i click on a button

I've been creating an app with NextJS, my problem came when I tried to generate new content from an API when I click on a button. I get the server call succesfully but it gets updated only when I reload the page, not when I click on the button as I want.
Here's the code:
import React, {useState} from 'react'
import { FaCog } from 'react-icons/fa'
export async function getServerSideProps() {
const res = await fetch(`https://animechan.vercel.app/api/random`)
const data = await res.json()
return {
props: {
quote: data.quote,
anime: data.anime,
character: data.character
}
}
}
const Button = () => {
const [clicked, setClicked] = useState(false)
const handleClicked = (props) => {
return (
<div>
<p style={{fontWeight: 'bold'}}>{props.anime}</p>
<p>{props.quote}</p>
<p style={{fontWeight: 'bold'}}>-{props.character}</p>
</div>
)
setClicked(!clicked)
}
return (
<div className="main_button">
<button onClick={handleClicked}>{clicked ? 'Generate again...' : 'Generate content '} <FaCog className={clicked ? 'main_button-spinner' : null}/> </button>
</div>
)
}
export default Button
I want that each time I click on the button, the content gets updated and I receive new content from the API. As I explained above, this is working fine on the API call, but the content gets updated just by reloading the page and not as I need it to work. Any idea?
You're misunderstanding the role of getServerSideProps. Take a look at the Next.js documentation: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
getServerSideProps only runs on server-side and never runs on the browser
If you want your React application to change in response to hitting this API, then you need to submit a request to the API from within the React code (in response to the button click), save the results of the API to state, and then display the results in the frontend.
Psuedo code follows:
const [data, setData] = useState(null);
// ...
async function handleClicked() {
const apiResponse = await fetch("...");
setData(apiResponse); // Or whatever properties from the response you care about
}
// ...
<button onClick={handleClicked}>
{clicked ? 'Generate again...' : 'Generate content '}
<FaCog className={clicked ? 'main_button-spinner' : null}/>
</button>

Components Will Not Render After a UseState Change

This is a difficult question because there are so many moving parts, but allow me to attempt to explain the scenario before I start shoving code in everyone's face.
My goal is to allow managers to have a screen where all their drivers are displayed. They will have minimal information displayed and an edit button. If the user clicks the edit button they will stay on the same page. There is a useState, const [driverSelected, setDriverSelected] = useState("") that once an edit button is clicked, will call setDriverSelected to be the driver, not just the id. So once an edit button is clicked, an actual new value for driverSelected would look like this...
{id: 'a049c673-da36-48e6-8fbd-32ab925b6178', role: 'USER', firstname: 'STEVEN', lastname: 'MONROE', email: 'TQRGJGNFQVIO', …}
deleted: false
email: "TQRGJGNFQVIO"
firstname: "STEVEN"
id: "a049c673-da36-48e6-8fbd-32ab925b6178"
lastname: "MONROE"
locked: false
phoneNumber: "null"
profilePick: null
role: "USER"
__typename: "Driver"
[[Prototype]]: Object
Based on this, the same page will change from displaying all the drivers to just the one selected, and input fields to change his/her attributes. This all works properly.
From here, you hit submit and it sends a mutation over to the database. This also works. Then, a query is automatically launched to send the user back the new driver data. This also also works. Where everything breaks is once the mutations/queries are run, I also run setDriverSelected({id: -1}) which should render the drivers list again, but nothing appears at all.
I thought it may be an issue with the data flow, but it isn't. I have console.log statements everywhere along the way from the mutation to the re-render, and at every point the console.log statements return exactly what they're supposed to. No errors in the console, no failed fetches or anything like that from the network. I just literally get nothing. I've even tried replacing all the data with static information, still nothing.
The code is all spread out too across about 7 files since I was trying to compartmentalize as much as possible while using React, so bare with the ugly mess of code files you're about to see.
This is the first page in question, the one that is in charge of either rendering the list OR the driver's fields when chosen.
import React from "react";
import { useState } from "react";
import { useRecoilState } from "recoil";
import { userState } from "../../recoil/atoms";
import SideMenu from "../../components/Home/SideMenu/SideMenu";
import DriverCard from "./DriverCard";
import EditDriver from "./EditDriver";
import "../../styles/EditDrivers/EditDriversLanding.css"
const EditDriversLanding = () => {
// Recoil Data
const rawUser = useRecoilState(userState)
console.log(rawUser)
const user = rawUser[0]
// Local states
const [getSearch, setSearch] = useState("")
const [driverSelected, setDriverSelected] = useState({id: -1})
// Based off of what you type in the search bar, it will filter out invalid employees
const filterDriversList = (list) => {
let filteredList = []
if (getSearch == ""){
return list
}
else{
let filterString = getSearch.toUpperCase()
list.forEach( (driver) => {
if (driver.firstname.includes(filterString) || driver.lastname.includes(filterString)){
filteredList.push(driver)
}
})
return filteredList
}
}
// Takes the list of drivers and renders them all into a list of components
const renderDriverCards = (list) => {
let i = 0
console.log("Okay.... like dude you're RIGHT here, RENDER")
console.log(list)
return list.map( (driver)=> {
i++
if (i == 1){
console.log(driver)
console.log("WHY WONT YOU WORK???")
}
return (<DriverCard driver={driver} key={i} setDriverSelected={setDriverSelected} />)
})
}
const renderListOrEditScreen = () => {
// No Driver selected
if (driverSelected.id == -1){
console.log("dude.... render!!!")
return(
<div className="overlay">
<div className="edit-landing-container">
<SideMenu />
<div className="edit-landing-search-bar">
<input type="text" onChange={(event) => setSearch(event.target.value)} />
</div>
<div className="edit-landing-drivers-list">
{renderDriverCards(filterDriversList(user.drivers))}
</div>
</div>
</div>
)
}
// Driver Selected
else{
return(
<div className="overlay">
<div className="edit-landing-container">
<SideMenu />
<div>
<EditDriver driverData={driverSelected} setDriverSelected={setDriverSelected}/>
</div>
</div>
</div>
)
}
}
if (driverSelected.id == -1){
console.log("should be rendering...")
}
return (
<div>
{renderListOrEditScreen()}
</div>
)
}
export default EditDriversLanding
Its worth mentioning again that this file above works perfectly the first time it is rendered, but after a driver is edited, NOTHING renders-- not a single <div>
Here is the file for the <DriverCard />
import React from "react";
import "../../styles/EditDrivers/EditDriversLanding.css"
const DriverCard = ({driver, setDriverSelected}) => {
console.log(driver)
console.log("dude just work bro")
return(
<div className="edit-drivers-driver-card">
<div>
Image
</div>
<div>
<div>{driver.firstname} {driver.lastname}</div>
<div>{driver.email}</div>
<div>{driver.phoneNumber}</div>
<div className="edit-driver-driver-card-edit-button" onClick={() =>setDriverSelected(driver)}>Edit</div>
</div>
</div>
)
}
export default DriverCard
And finally, here is the EditDriver page which is where the mutation and re-query take place. Notice here you'll see a <div> that on press will also setDriverSelected({id: -1}) and THAT one decides to work-- just the submitting changes kills everything.
import React from "react";
import { useState } from "react";
import DriverField from "../../components/EditDrivers/DriverField";
import SubmitEdits from "./submitEdits";
import "../../styles/EditDrivers/EditDriversLanding.css"
const EditDriver = ({driverData, setDriverSelected}) => {
const [driver, setDriver] = useState(driverData)
const handleInput = (event) => {
const input = { ...driver };
input[event.target.id] = event.target.value;
setDriver(input);
};
return(
<div className="edit-driver-editting-page">
<div onClick={() => setDriverSelected({id: -1})} className="edit-driver-editting-page-exit-button">
Return to Driver Selection
</div>
<div>
<h2>Edit {driverData.firstname} {driverData.lastname}</h2>
</div>
<div>
<div>
<DriverField currentValue={driver.firstname} name="firstname" handleInput={handleInput} />
</div>
<div>
<DriverField currentValue={driver.lastname} name="lastname" handleInput={handleInput} />
</div>
<div>
<DriverField currentValue={driver.email} name="email" handleInput={handleInput} />
</div>
<div>
<DriverField currentValue={driver.phoneNumber} name="phoneNumber" handleInput={handleInput} />
</div>
<SubmitEdits driver={driver} setDriverSelected={setDriverSelected}/>
</div>
</div>
)
}
export default EditDriver
It's hard to say why nothing at all is rendering - but it looks like your landing page component is more complex than it needs to be. It's not often that you need to have functions which render content (e.g. renderListOrEditScreen and renderDriverCards) - often that's a sign that you should break those functions out into their own components.
So, I'd suggest you start by splitting that up into smaller components that do less work. It looks like one of the functions of that page is to act as the "search" page - you could split that up using something like this:
const useFilteredDriversList = (drivers, search) => {
return useMemo(() => {
if (!search) return drivers;
const searchUpper = search.toUpperCase();
return drivers.filter(driver =>
driver.firstName.includes(searchUpper) ||
driver.lastName.includes(searchUpper)
);
}, [drivers, search]);
}
const DriverSearch = ({ onDriverSelected }) => {
const [user] = useRecoilState(userState);
const [search, setSearch] = useState("");
const filteredDrivers = useFilteredDriversList(user.drivers, search);
const handleSearchChange = (event) => setSearch(event.target.value);
return (
<>
<div className="edit-landing-search-bar">
<input type="text" onChange={handleSearchChange} />
</div>
<div className="edit-landing-drivers-list">
{filteredDrivers.map(driver => (
<DriverCard
key={driver.id}
driver={driver}
setDriverSelected={onDriverSelected}
/>
))}
</div>
</>
);
}
Note here I've also split out the filtering code from the component - having it inside the component means you're redefining the filter function every time the component renders, which is unnecessary.
OK; now that the search page has been split out, you can just have a landing page component which either shows the search component or the edit component, depending on if a driver has been selected or not. One other thing that I'd do is create an explicit handler for the case of "cancelling" the edit, and have that live in the landing page. The edit page shouldn't have knowledge of how to "cancel" editing (i.e. setting the driver to { id: -1 }) - that's not its responsibility. It should just tell the parent component that it's finished, and let the parent component worry about how to handle that.
Finally, I'd use either null or undefined to represent "no driver selected" rather than a magic object. So, something like this might work:
const DriversPage = () => {
const [driver, setDriver] = useState(undefined);
const handleUnselectDriver = () => setDriver(undefined);
return (
<div className="overlay">
<div className="edit-landing-container">
<SideMenu />
{driver && (
<EditDriver
driverData={driver}
onEditComplete={handleUnselectDriver}
/>
)}
{!driver && (
<DriverSearch onDriverSelected={setDriver} />
)}
</div>
</div>
);
}

Reactj.s: Item deleted only after refresh | Delete Method

I'm trying to send a delete request to delete an item from an API.
The API request is fine when clicking on the button. But Item get's deleted only after refreshing the browser!
I'm not too sure if I should add any parameter to SetHamsterDeleted for it to work?
This is what my code looks like.
import React, {useState} from "react";
const Hamster = (props) => {
const [hamsterDeleted, setHamsterDeleted] = useState("")
async function deleteHamster(id) {
const response = await fetch(`/hamsters/${id}`, { method: "DELETE" });
setHamsterDeleted()
}
return (
<div>
<p className={props.hamster ? "" : "hide"}>
{hamsterDeleted}
</p>
<button onClick={() => deleteHamster(props.hamster.id)}>Delete</button>
<h2>{props.hamster.name}</h2>
<p>Ålder:{props.hamster.age}</p>
<p>Favorit mat:{props.hamster.favFood}</p>
<p>Matcher:{props.hamster.games}</p>
<img src={'./img/' + props.hamster.imgName} alt="hamster"/>
</div>
)
};
export default Hamster;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Imagine you have a parent component (say HamstersList) that returns/renders list of these Hamster components - it would be preferable to declare that deleteHamster method in it, so it could either: a) pass some prop like hidden into every Hamster or b) refetch list of all Hamsters from the API after one got "deleted" c) remove "deleted" hamster from an array that was stored locally in that parent List component.
But since you are trying to archive this inside of Hamster itself, few changes might help you:
change state line to const [hamsterDeleted, setHamsterDeleted] = useState(false)
call setHamsterDeleted(true) inside of deleteHamster method after awaited fetch.
a small tweak of "conditional rendering" inside of return, to actually render nothing when current Hamster has hamsterDeleted set to true:
return hamsterDeleted ? null : (<div>*all your hamster's content here*</div>)
What do you want to do in the case the hamster is deleted? If you don't want to return anything, you can just return null.
I'm not too sure if I should add any parameter to SetHamsterDeleted for it to work?
Yes, I'd make this a boolean instead. Here's an example:
import React, { useState } from "react";
const Hamster = (props) => {
const [hamsterDeleted, setHamsterDeleted] = useState(false);
async function deleteHamster(id) {
const response = await fetch(`/hamsters/${id}`, { method: "DELETE" });
setHamsterDeleted(true);
}
if (hamsterDeleted) return null;
return (
<div>
<p className={props.hamster ? "" : "hide"}>
{hamsterDeleted}
</p>
<button onClick={() => deleteHamster(props.hamster.id)}>Delete</button>
<h2>{props.hamster.name}</h2>
<p>Ålder:{props.hamster.age}</p>
<p>Favorit mat:{props.hamster.favFood}</p>
<p>Matcher:{props.hamster.games}</p>
<img src={'./img/' + props.hamster.imgName} alt="hamster"/>
</div>
);
};
HOWEVER! Having each individual hamster keep track of its deleted state doesn't sound right (of course I don't know all your requirements but it seems odd). I'm guessing that you've got a parent component which is fetching all the hamsters - that would be a better place to keep track of what has been deleted, and what hasn't. That way, if the hamster is deleted, you could just not render that hamster. Something more like this:
const Hamsters = () => {
const [hamsers, setHamsters] = useState([]);
// Load the hamsters when the component loads
useEffect(() => {
const loadHamsters = async () => {
const { data } = await fetch(`/hamsters`, { method: "GET" });
setHamsters(data);
}
loadHamsters();
}, []);
// Shared handler to delete a hamster
const handleDelete = async (id) => {
await fetch(`/hamsters/${id}`, { method: "DELETE" });
setHamsters(prev => prev.filter(h => h.id !== id));
}
return (
<>
{hamsters.map(hamster => (
<Hamster
key={hamster.id}
hamster={hamster}
onDelete={handleDelete}
/>
))}
</>
);
}
Now you can just make the Hamster component a presentational component that only cares about rendering a hamster, eg:
const Hamster = ({ hamster, onDelete }) => {
const handleDelete = () => onDelete(hamster.id);
return (
<div>
<button onClick={handleDelete}>Delete</button>
<h2>{hamster.name}</h2>
<p>Ålder:{hamster.age}</p>
<p>Favorit mat:{hamster.favFood}</p>
<p>Matcher:{hamster.games}</p>
<img src={'./img/' + hamster.imgName} alt="hamster"/>
</div>
);
};

Categories