Using callback ref to modify scrollLeft property in React - html

I've got a container div with a lot of children, and an overflow-x set to scroll.
This allows me to scroll the content to the left and right using my trackpad (or drag on mobile).
But I really would like the default scroll position to be the maximum horizontal value. (i.e. scrolled all the way to the right). The relevant code for this is Element.scrollLeft = <some large value>.
I'm trying to figure out how to achieve this with React and callback refs. I have the following code:
const containerRef = useCallback(
(node) => {
if (node !== null) {
node.scrollLeft = values.length * 20;
}
},
[values.length]
);
return (
<div className={"scroll-wrapper"} ref={containerRef}>
values.map(()=>{
// Some stuff in here that's irrelevant.
});
</div>
)
(This code was based on https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node)
When I put a breakpoint in the useCallback function, I see it is properly invoked with a non-0 value, but right after the call when I inspect node.scrollLeft it is still 0.
When I inspect the DOM in Chrome, I can actually force the scrollLeft of the element to be high number, which does work.
So why doesn't the React useCallback ref work?

You should use useRef hook to work with refs.
To create ref use this:
const containerRef = useRef();
And add it to ref of div as you did before:
<div className={"scroll-wrapper"} ref={containerRef}>
...
</div>
And you should use useEffect and call it one time to set scrollLeft to the component in ref:
useEffect(() => {
ref.current.scrollLeft = 4000;
}, []);
Here is the full example: https://codesandbox.io/s/lingering-glitter-s0gok?file=/src/App.js

This is how I fixed
then just make some css issue for that
const [scrollX, setScrollX] = useState({
side: ""
});
const scrollLeftRef = useRef();
const handleScroll = (data) => {
setScrollX(prev => ({ ...prev, side: data.side }));
}
useEffect(() => {
if (scrollX.side === "right") {
scrollLeftRef.current.scrollLeft += 200;
} else {
scrollLeftRef.current.scrollLeft -= 200;
}
}, [scrollX]);
<div class="slide-sample">
<div id="slideRight" onClick={() => handleScroll({ side: "left" })} class="preSlide">
<i className="fas fa-caret-square-left" />
</div>
<div ref={scrollLeftRef} class="slideouter">
<div class="slideinner srcl">
<ul>
{
categories.map((category, i) => {
return (
<Button
key={category.id}
onClick={() => { dispatch(filterfood(category.name)); handleItemClick(i) }}
variant={activeclass === i ? "contained" : "outlined"}
size="medium"
>
{category.name}
</Button>
)
})}
</ul>
</div>
</div>
<div id="slideRight" onClick={() => handleScroll({ side: "right" })} className="nextSlide">
<i className="fas fa-caret-square-right" />
</div>
</div>

Related

How can I pass a number into CSS with React-Bootstrap?

I am building a React app using Bootstrap 5. I have a dynamic Accordion component grabbing tasks from a list:
import React, {useEffect, useState} from "react";
import 'bootstrap/dist/css/bootstrap.min.css'
import FadeIn from "react-fade-in";
import {PageLayout} from "./page-layout";
import TaskDataService from "../services/TaskService";
export const ProfilePage = () => {
const [tasks, setTasks] = useState([]);
const [currentTask, setCurrentTask] = useState(null);
const [currentIndex, setCurrentIndex] = useState(-1);
useEffect(() => {
retrieveTasks();
}, []);
const retrieveTasks = () => {
TaskDataService.getAll()
.then(response => {
setTasks(response.data);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};
const refreshList = () => {
retrieveTasks();
setCurrentTask(null);
setCurrentIndex(-1);
};
const setActiveTask = (task, index) => {
setCurrentTask(task);
setCurrentIndex(index);
};
return (
<PageLayout>
<FadeIn>
<div className="list row">
<div className="col-md-6">
<h4>Tasks List</h4>
<div className="accordion" id="accordionTask">
{tasks &&
tasks.map((task, index) => (
<div className="accordion-item">
<h2 className="accordion-header" id="a$index">
<button className="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target={"a" + {index}}
aria-expanded={(index === currentIndex ? "true" : "false")}
aria-controls={"a" + {index}}
onClick={() => setActiveTask(task, index)}
>
{task.title}
</button>
</h2>
<div id={"a" + {index}} className="accordion-collapse collapse"
aria-labelledby={"a" + {index}} data-bs-parent={"a" + {index}}>
<div className="accordion-body">
<div>
<label>
<strong>Owner:</strong>
</label>{" "}
{task.ownerName}
</div>
<div>
<label>
<strong>Description:</strong>
</label>{" "}
{task.description}
</div>
<div>
<label>
<strong>Status:</strong>
</label>{" "}
{task.completed ? "Completed" : "Pending"}
</div>
<div>
<label>
<strong>Due Date:</strong>
</label>{" "}
{task.startDate.split("T")[0]}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</FadeIn>
</PageLayout>
);
};
But when I render this, I get an error:
Uncaught DOMException: Failed to execute 'querySelector' on 'Document': 'a[object Object]' is not a valid selector.
I know that a CSS element cannot start with a number, so I tried to get around this using the below syntax:
data-bs-target={"a" + {index}}
But this gets me the error listed above, which suggests that the index number passed into CSS is not coming in as the correct type to be properly read. Any tips on how to deal with this?
Someone had posted the answer, and now their reply has disappeared so I cannot give them credit. But the answer was to simply not use the double brackets.
Wrong: data-bs-target={"a" + {index}}
Right: data-bs-target={"a" + index}
In the top example, it was basically saying "Insert a property with the value Index" rather than just inserting the value of Index itself, which was a breach of syntax.

TypeError: Cannot read properties of undefined (reading 'map') whole component code is not working

When I start the npm code for the cart component is not working and displayed the blank page
map function did not work. When I comment the part which is not working then another component like the header is displayed
import React from 'react';
import Header from './Front/Header/Header';
const Cart = (props ) => {
const {cartitems} =props;
const{handleAddProduct}=props;
const {handleRemoveProduct}=props;
return (
<>
<Header/>
<div className="cart-items">
<div className="cart-items-header"> cartitems</div>
{!cartitems?.length ? (
<div className="cart-items-empty"> No items added in cart</div>
) : null}
<div>
//this part of code is not working
{cartitems.map((item) => (
<div key={item.id}>
<div>
<img className="cart-items-image"
src={item.image}
alt={item.name} />
</div>
<button className='cart-items-add' onClick={()=>handleAddProduct(item)}>+</button>
<button className='cart-items-remove' onClick={()=>handleRemoveProduct(item)}>-</button>
<div className='cart-items-price'>{item.quantity}* ${item.price}</div>
</div>
))}
</div>
</div>
</>
);
}
export default Cart;
here is the code of app.js in this code I got an error that cartitems.find is not a function plz let me know how to fix this issue
const { productitems } = data;
const [cartitems, setCartItems] = useState([]);
const { user } = useContext(UserContext);
const History = useHistory();
const handleAddProduct = (product) => {
// console.log(product);
const ProductExist = cartitems.find((item) => item.id === product.id)
// console.log(ProductExist);
// setCartItems(ProductExist);
if (ProductExist) {
setCartItems(
cartitems.map((item )=> item.id ===product.id ?
{...ProductExist ,quantity:ProductExist.quantity +1}:item)
)
}
else {
setCartItems([...cartitems,{...product,quantity:1}])
console.log('ni gya');
}
}
const handleRemoveProduct = (product) => {
const ProductExist = cartitems.find((item) => item.id === product.id);
if (ProductExist.quantity === 1) {
setCartItems(cartitems.filter((item) => item.id !== product.id));
}
else {
setCartItems(
cartitems.map((item) => item.id === product.id ?
{ ...ProductExist, quantity: ProductExist.quantity - 1 }
: item)
);
}
}
You can see there is a check above the code you commented out:
{
!cartitems?.length ? (
<div className="cart-items-empty">No items added in cart</div>
) : null
}
It checks to make sure cartitems is an array before attempting to use map() on it. In the ternary statement, instead of returning null if cartitems is empty, you should return the map function so it would read:
{!cartitems?.length ? (
<div className="cart-items-empty"> No items added in cart</div>
) : cartitems.map(item => (
// template code...
))}

The text in Html is not changing even so variable text1 is changing in console log ,why?

This is a component in react.
I am trying to create function that changes text every second.
Variable changes every second it works but when i pass it in Html nothing happens on Page itself.
export default function Statement(){
var text = ["NFT", "CRYPTO", "METAVERSE", "WEB3"];
var counter = 0;
var text1 = "NFT";
setInterval(change, 1000);
function change() {
text1 = text[counter];
counter++;
console.log(text1,counter);
if (counter >= text.length) {
counter = 0;
}
}
return(
<div>
<img className='star-fixed' src={SmallLogo}></img>
<img className='starsky-fixed' src={StarskyText}></img>
<div className='text-content'>
<span className='statement-text'>WEB3 IS NOT ONLY THE FUTURE.
IT’S THE ONLY FUTURE!</span>
<span className='starsk-link'>starsk.pro</span>
</div>
<div className='text-content-bottom'>
<span className='statement-text-bottom'>CREATE YOUR NEXT <span className='yellow changetext'> {text1} </span>
PROJECT WITH STRSK.PRO</span>
</div>
</div>
)
}
Because the component doesn't re-render when the value of the variable is updated.. states are used to handle that..
You have to create a state of your variable and when that state changes, the component responds by re-rendering automatically.
https://reactjs.org/docs/faq-state.html
The text isn't changing because the component only renders once. To get the component to re-render, you need to use state. Specifically, you need to use a state external to the component, like Redux, so your counter/index variable isn't reset on each render.
Additionally, the setInterval call needs to be moved outside of the component and called from a useEffect hook so it doesn't get rescheduled on each render.
Lastly, using another custom component specifically for the updated text seems to result in more normal rendering behavior, because only the child component needs to be re-rendered.
Here is some example code:
Redux store and reducer
import { createStore } from "redux";
const initialState = {
tempText: "NFT",
index: 1
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "updatetext":
return {
...state,
tempText: action.payload
};
case "updateindex":
return {
...state,
index: action.payload
};
default:
return state;
}
};
export const store = createStore(reducer);
App
function TempText(props) {
return <span className="yellow changetext"> {props.body} </span>;
}
function doUpdate(callback) {
setInterval(callback, 3000);
}
export default function App() {
const dispatch = useDispatch();
const textOptions = ["NFT", "CRYPTO", "METAVERSE", "WEB3"];
const tempText = useSelector((state) => state.tempText);
function change() {
let state = store.getState();
const index = state.index;
console.log(index);
console.log(textOptions[index]);
dispatch({
type: "updatetext",
payload: textOptions[index]
});
let newIndex = index + 1 >= textOptions.length ? 0 : index + 1;
dispatch({
type: "updateindex",
payload: newIndex
});
}
useEffect(() => {
doUpdate(change);
}, []);
return (
<div>
<div className="text-content">
<span className="statement-text">
WEB3 IS NOT ONLY THE FUTURE. IT’S THE ONLY FUTURE!
</span>
<span className="starsk-link">starsk.pro</span>
</div>
<div className="text-content-bottom">
<span className="statement-text-bottom">
CREATE YOUR NEXT <TempText body={tempText} />
PROJECT WITH STRSK.PRO
</span>
</div>
</div>
);
}
And here is a working example in CodeSandbox.
I used ref to change it.
var text = ["NFT", "CRYPTO", "METAVERSE", "WEB3"];
var counter = 0;
var chn = useRef(null);
setInterval(change, 1300);
function change() {
chn.current.innerHTML = " "+text[counter]+" ";
counter++;
if (counter >= text.length) {
counter = 0;
}
}
return(
<div>
<Link to="/">
<img className='star-fixed' alt='starlogo' src={SmallLogo}></img>
</Link>
<img className='starsky-fixed' alt='starsky-project' src={StarskyText}></img>
<div className='text-content'>
<span className='statement-text'>WEB3 IS NOT ONLY THE FUTURE.
IT’S THE ONLY FUTURE!</span>
<span className='starsk-link'>starsk.pro</span>
</div>
<div className='text-content-bottom'>
<span className='statement-text-bottom'>CREATE YOUR NEXT <span
ref ={chn} id='changetext' className='yellow changetext'>
NFT </span>
PROJECT WITH STRSK.PRO</span>
</div>
</div>
)
}

Remove / add classname from a HTML elements using React hooks

I'm trying to remove an attripute from html element using on click on a button:
import React , {useState} from 'react';
import classNames from 'classnames';
function App () {
const [isActive, setIsActive] = useState(false);
const handleOnClick = () => {
setIsActive(!isActive);
};
return (
<InlineBlockLogIn
className={classNames('active', { 'active' : isActive})}
onClick={handleOnClick} >
<InlineBlockReg
className={classNames('', { 'active' : isActive})}
onClick={handleOnClick} >
)};
I would like to remove the "active" from InlineBlockLogIn when clicked on the InlineBlockReg and so on.
So basically if active at one div it should be inactive at the second one.
Any idea how to do so please?
The code in your question does not have a single root element, is missing closing tags and the handlers are not correct. Maybe the following code can help you:
const App = () => {
const [isActive, setIsActive] = React.useState(true);
const handleOnClick = (value) => {
setIsActive(value);
};
return (
<div>
<div
className={isActive ? "active" : ""}
onClick={() => handleOnClick(false)}
>
InlineBlockLogIn
</div>
<div
className={isActive ? "" : "active"}
onClick={() => handleOnClick(true)}
>
InlineBlockReg
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React - How to know if a component is in a link element synchronously?

Nested links are not allowed in HTML.
For a component like:
function MyComponent(){
return (
<a href='...'>
{/* ... */}
<a/>
);
}
How MyComponent can get to know synchronously if it has an <a> element ancestor ?
So it could adapt on its 1st render ?
This answers the original version of the question, which has since been edited
You can use closest with a React ref for this:
const SmartLink = ({ href, children }) => {
const ref = useRef(null);
useEffect(() => {
if (ref.current?.parentElement.closest("a")) {
throw new Error("<a /> elements can't be nested");
}
}, []);
return (
<a ref={ref} href={href}>
{children}
</a>
);
};
CodeSandbox demo