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

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.

Related

Passing array state data up and over from child to sibling component by way of mutual parent leaves array short by one

This Footer component creates an array. I'd like to make this array available in a sibling Body component. I'd like to do this by passing the reminders array variable up and over to the Body component by way of their mutual Parent component.
import './style.css'
function Footer({ linkToFooter }){
const [reminder, setReminder] = useState("");
const [reminders, setReminders] = useState([]);
const submitThis = (e) => {
e.preventDefault();
if (reminder){
let newReminder = {
reminder: reminder,
complete: false,
};
setReminders(prevRems => [...prevRems, newReminder])
setReminder('');
} else {
setReminder("oops there's a problem");
}
};
useEffect(
() => { localStorage.setItem('reminders', JSON.stringify(reminders)); },
[reminders]
);
console.log(reminders, "from Footer");
return(
<div className="rm-list-footer">
<form onSubmit={submitThis}>
<input
id='newReminder'
type='text'
value={reminder}
onChange={(e) => setReminder(e.target.value)}
/>
<button onClick={() => linkToFooter(reminders)} type='submit'>+</button>
</form>
</div>
)
}
export default Footer;
This is the Parent function component that I'd like to be the segway between the two child components.
import Header from "./Header";
import Footer from "./Footer";
import Body from "./Body";
function RmList() {
const [reminders, setReminders] = useState([])
return(
<div className="rm-list">
<Header></Header>
<Body reminders={reminders}></Body>
<Footer linkToFooter={setReminders}></Footer>
</div>
);
}
export default RmList;
Finally, my goal is to console.log the array in this sibling Body component every time the button in the Footer component is clicked. The problem is that the array that's logged in the Body is always one element behind what's logged in the Footer with every click.
import React from "react";
function Body({reminders}) {
console.log(reminders, "from Body");
return(
<div className="rm-list-body"></div>
);
}
export default Body;
After researching I thought my problem might have to do with changing this
setReminders([newReminder, ...reminders])
to this
setReminders(prevRems => [...prevRems, newReminder])
but that was to no avail as I can't pass prevRems to the parent.
Your help is kindly appreciated.
If im understanding correctly, you want to be able to set reminders in one child and see reminders in a sibling. if that is correct, reminders as a state should only be in the parent. you can then pass the state and state-setter to the children and access them through props. by re-declaring "reminders" in the child you could have a conflict of state.
also, it is best practice to name your props being passed like this
<Footer setReminders={setReminders}>
so that on larger projects its easy to follow your prop's path
try
import './style.css'
CHANGE >>>> function Footer({ linkToFooter > setReminders }){
const [reminder, setReminder] = useState("");
REMOVE >>>> const [reminders, setReminders] = useState([]);
const submitThis = (e) => {
e.preventDefault();
if (reminder){
let newReminder = {
reminder: reminder,
complete: false,
};
setReminders(prevRems => [...prevRems, newReminder])
setReminder('');
} else {
setReminder("oops there's a problem");
}
};
useEffect(
() => { localStorage.setItem('reminders', JSON.stringify(reminders)); },
[reminders]
);
console.log(reminders, "from Footer");
return(
<div className="rm-list-footer">
<form onSubmit={submitThis}>
<input
id='newReminder'
type='text'
value={reminder}
onChange={(e) => setReminder(e.target.value)}
/>
CHANGE >>>> <button onClick={() => linkToFooter > setReminders(reminders)} type='submit'>+</button>
</form>
</div>
)
}
export default Footer;
let me know if that has helped guide you in the right direction. and hopefully these little tricks will help you keep your code more readable. Good Luck!

React - toggle text and class in an HTML element?

I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox

getting the most recent value in a react component

so I am trying to create add to faviourate button with icon.
so far I could make a logic if a user clicked the empty heart icon that it turns to be full heart icon and I was able to locate the item it was clicked on.
So far so good, my issue starts when products object recieves only the most recent item that is picked and loses the other items that are previously picked.
so for example If I want to click on 3 items to add them to faviourate, I see that the console.log(favProduct) only preserves the most recent item which is in my case number 3 and loses number 1 and 2.
My Question is How to get all Items I clicked on and not only the most recent one.
Edit This is where I get the product from, check the code below.
import React , { useState, useEffect } from 'react'
import { Row , Col } from 'react-bootstrap'
import Product from '../components/Product'
import axios from 'axios'
const HomeScreen = () =>{
const [products , setProducts] = useState([])
useEffect(()=>{
let componentMounted = true
const fetchProducts = async () =>{
const {data} = await axios.get('http://172.30.246.130:5000/api/products')
if( componentMounted){
setProducts(data)
}
}
fetchProducts()
return () =>{
componentMounted = false
}
},[])
console.log('products' , products)
return(
<>
<h2 className='my-3'>Latest Products</h2>
<Row>
{
products.map((product)=>(
<Col key={product._id} sm={12} md={6} lg={4} xl={3}>
<Product product={product} rating = {product.rating} reviews={product.numReviews}/>
</Col>
))
}
</Row>
</>
)
}
export default HomeScreen
import React, { useState } from 'react'
const Fav = ({products}) => {
let [checked , setChecked] = useState(false)
let[favProduct ,setFavProduct] = useState([])
const toggle = ()=>{
(!checked) ? setChecked(true) : setChecked(false)
setFavProduct([...favProduct,products]) // problem is here
}
console.log(favProduct)
return (
<>
<span onClick={toggle}>
{
<i className={(checked) ? "fas fa-heart" : "far fa-heart"}></i>
}
</span>
</>
)
}
export default Fav
At the moment each of your Fav components looks like they're trying to manage the state for all of the favourites which is not a good idea.
The general idea is to make most UI components as dumb as possible (ie. just return the bare minimum given the props they're given). They can control their own state but usually you want to a parent to control the state by lifting state up, and have them pass down a handler that the dumb component can call when their listener is triggered.
In this example Fav accepts an id, a favoured, and a handleClick listener, and then just returns some JSX.
The parent component does all the state management.
const { useEffect, useState } = React;
// Accept some props
// Render the class based on the `favoured` prop
function Fav({ id, favoured, handleClick }) {
return (
<div className="icon">
<i
data-id={id}
className={favoured ? 'fa-solid fa-heart' : 'fa-regular fa-heart'}
onClick={handleClick}
> {id}</i>
</div>
);
}
function Example() {
// The parent component manages the state
const [ favourites, setFavourites ] = useState([]);
// When a favourite icon is clicked, get its id
// and if it's in the state, remove it, otherwise add it
function handleClick(e) {
const { id } = e.target.dataset;
const found = favourites.includes(id);
if (found) {
setFavourites(favourites.filter(fav => fav !== id));
} else {
setFavourites([...favourites, id]);
}
}
// In this example I'm using a loop to generate the
// the favourites, checking if the state includes the id
// (I have to coerce it to a string because that's what
// the data attributes return, and `i` will always be a number
// Pass down the handler that the favourite button
// will use to update the state
function getFavs() {
const favs = [];
for (let i = 0; i < 5; i++) {
const isFavoured = favourites.includes(i.toString());
const fav = (
<Fav
id={i}
favoured={isFavoured}
handleClick={handleClick}
/>
);
favs.push(fav);
}
return favs;
}
return (
<div>
{getFavs()}
</div>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

how can i make a component re render in react

so i'm creating my first fullstack website and once a user signs in it gets stored in the localStorage and i want to display the name of the user in my header once he is logged in but my header is not re rendering so nothing happens : this is the header before logging in
header
and this is how i want it to Be after signing in :
header after logging in this is my Layout code:
import "../assets/sass/categoriesbar.scss";
import Header from "./Header/Header";
const Layout = (props) => {
return (
<>
<Header/>
<main>
{ props.children}
</main>
</>
);
}
export default Layout;
and this is the toolBar in my Header :
const ToolBar = () => {
const history = useHistory();
let currentUser= JSON.parse(localStorage.getItem("user-info"));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{localStorage.getItem("user-info")?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
please help me it's frustrating
PS: this is my first stackoverflow question sorry if it's unorganized and unclear and sorry for my bad english.
Hazem, welcome to Stack Overflow.
In react, if you want the component to re-render when some data changes, that info must be in the component state. In your code the current user is a const, not bind to the component's state. This is how it could auto re-render when the user logs in:
const ToolBar = () => {
const [currentUser, setCurrentUser] = useState(JSON.parse(localStorage.getItem("user-info")));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{currentUser?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
See more about state in the official documentation.

Auto Scroll When new message send or received

// 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!

Categories