I am mapping through an array of objects and displaying their values in a card, each object has a title, comment, and datePosted field. The datePosted field is put throught a function I made to display it on the card as 'Posted 2 hours ago' or 'Posted '2 minutes ago'. This is updating though everytime the state changes, which isn't a problem when it was posted days ago, or even hours, as nothing changes, but when a new post is first uploaded and saved, the date being displayed updates practically everytime you type a key, as it goes from 'Posted 1 second ago' to 'Posted 2 seconds ago', to 'Posted 3 seconds ago' and so on... . Is there anything I can do to get around this, I've considered saving the component in a different state and displaying that, but I don't think that will work as the state needs to be updated everytime the user changes a title or comment so it can be displayed in real time.
Here's an example of the JSX that is displaying the card in case it helps. timeCalc is the function that calculates how long ago the post was created
{posts.map(item => (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
<h3>{timeCalc(item.datePosted)}</h3>
</div>
</div>
))}
I would extract the code to its own component, and then that component can use one of several possible techniques to remember the value from its first render. Here's one with useMemo:
{posts.map(item => <Post item={item}/>)}
//... elsewhere:
const Post = ({ item }) => {
const time = useMemo(() => {
return timeCalc(item.datePosted);
}, []);
return (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
<h3>{time}</h3>
</div>
</div>
);
}
Other possible ways to remember the initial value are with state:
const Post = ({ item }) => {
// Note that i have no need for `setTime`, as this value will never be changed
const [time] = useState(timeCalc(item.datePosted));
Or with a ref:
const Post = ({ item }) => {
const timeRef = useRef(timeCalc(item.datePosted));
// ...
<h3>{timeRef.current}</h3>
You can create a component and memoize it using React.memo and areEqual function to avoid re-rendering when props i.e. date are same.
Demo:
const inputDate = new Date().toISOString();
function timeCalc(date) {
return moment(date).fromNow()
}
function TimeCalc({date}) {
return <h4>{timeCalc(date)}</h4>
}
function areEqual(prevProps, nextProps) {
return prevProps.date === nextProps.date
}
const TimeCalcMemo = React.memo(TimeCalc, areEqual);
function App() {
const [v, sv] = React.useState('')
return (<div>
<div>{v}</div>
<div>Enter something after a minute</div>
<input type="text" value={v} onChange={e => sv(e.target.value)} />
<TimeCalcMemo date={inputDate}/> memoized
<TimeCalc date={inputDate}/> not memoized
</div>)
}
ReactDOM.render(<App />, document.getElementById('mydiv'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<body>
<div id="mydiv"></div>
</body>
You can also try conditional rendering.
const lastUpdated = timeCalc(item.datePosted)
{posts.map(item => (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
{
(compare timestamp here using moment.js or anyway you prefer) &&
<h3>{timeCalc(item.datePosted)}</h3>
}
</div>
</div>
))}
Related
Problem: How to Render component many times, when i give input value in input box (Eg : If i enter 5 in input box Hello World Should render 5 times
React Interview Question .
This is pretty straight-forward and there are loads of ways about going about this.
Here's just one: new Array(number).fill(<Component />)
As with any of possibilities, you the developer, will have to justify various decisions.
I have opted to fill a new Array(n) of length n.
I would need some kind of error checking to stop users entering dumb values ("" would be such a value)
Other error: checking
min - guard against negative values (perhaps max would also be logical)
step - guard against decimal values (how could one get 2.5 elements after all?!)
type="number" - guarding against unparsable inputs like strings
// Get a hook function
const {useState} = React;
const Component = () => {
return <div>Hello World</div>
}
const Example = () => {
const [howMany, setHowMany] = useState(1);
const allComponents = new Array(howMany).fill(<Component />)
return (
<div>
<input type="number" step={1} min={0} value={howMany} onChange={(event) => {
const newValue = event.target.value
const parsedValue = parseInt(newValue)
if (parsedValue < 0 || isNaN(parsedValue)) {
console.warn("Cannot set");
// Do some error handling
return
}
console.log("setting", parsedValue)
setHowMany(parsedValue)
}} />
<div>
{allComponents.map((elem, index) => (
<React.Fragment key={index}>
{elem}
</React.Fragment>
))}
</div>
</div>
);
};
// Render it
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Example />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I am trying to create a grid layout of video components but my IDE gives me a warning saying
Expression statement is not assignment or call
import React, {Fragment} from 'react';
import VideoClip from "../Video/VideoClip";
function SubjectView(props) {
let ids = ["RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd6iZHeEknCkY00",
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd2ipHeEknCkY00",
"RfKHsvF69Vdjdiu6bdugsyRcjYpQXrpKd2iZHeEknCkY00"
];
return (
<Fragment>
<div className="columns-3" >
{ids.map((id)=>{
<VideoClip id={id}/>
console.log(id)
})}
</div>
</Fragment>
);
}
export default SubjectView;
I see the IDs printed in the console but nothing renders.
The video component looks like
function VideoClip() {
let { id } = useParams();
return (
<div className="container mx-auto px-4">
<MuxPlayer
streamType="on-demand"
playbackId={id}
metadata={{
video_id: "video-id-54321",
video_title: "Test video title",
viewer_user_id: "user-id-007",
}}
/>
</div>
);
}
export default VideoClip
I am wondering if I am trying to create the components incorrectly. Is there a best practice when trying to achieve this?
You're not returning any value from ids.map
<>
<div className="columns-3" >
{ids.map((id)=><VideoClip id={id}/>)}
</div>
</>
One issue is you are not returning <VideoClip id={id}/> in map function in jsx. Also, if map is used - key needs to be set
return (
<Fragment>
<div className="columns-3">
{ids.map((id) => {
console.log(id);
return <VideoClip key={id} id={id} />;
})}
</div>
</Fragment>
);
Next issue is about VideoClip component parameter. Id needs to be extracted in that way in case it is passed as an attribute. Also, memoize the objects that you are passing down to a components. Metadata here, for example.
function VideoClip({ id }) {
const metadata = useMemo(() => {
return {
video_id: "video-id-54321",
video_title: "Test video title",
viewer_user_id: "user-id-007"
};
}, []);
return (
<div className="container mx-auto px-4">
<MuxPlayer streamType="on-demand" playbackId={id} metadata={metadata} />
</div>
);
}
Last thing - wrap your array in useMemo so this array will not cause crazy rerenders.
const ids = useMemo(
() => [
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd6iZHeEknCkY00",
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd2ipHeEknCkY00",
"RfKHsvF69Vdjdiu6bdugsyRcjYpQXrpKd2iZHeEknCkY00"
],
[]
);
Note: you will see x2 logs in the console in the Codesandbox due to <StrictMode> is set.
className "page" needs to be modified to "page light-theme" or "page dark-theme" on toggle through local storage key "theme-color" with values of light-theme and dark-theme.
The active key value does change in local Storage but updates only show if the pages is refreshed. I need the changes to sync on toggle
Page to be changed
export default function Page({children}){
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
{children}
</div>
)
}
h3 inner text needs to change dynamically depending on the active value from key "theme-color" in local storage. I have place the variable "themeOpener" in between h3 tag. No changes take place
heres is my code
export default function Body() {
let themeOpener;
if (`${localStorage.getItem('theme-color','light-theme')}`) {
themeOpener = "🧛🏼Ahh the light it burns! Please use toggle, I prefer dark mode!";
} else {
themeOpener = "I learnt to design in React and im hooked 🤩";
}
return (
<div className="body">
{/* <h3 id="opener">I learnt to design in React and im hooked 🤩</h3> */}
<h3 id="opener">{themeOpener}</h3>
</div>
);
}
This is code for my toggle where local storage is created
const ToggleMode = () => {
// state
const [isLight, setIsLight] = useState(false);
// effect
useEffect(() => {
// check local storage
const CurrentTheme = localStorage.getItem("theme-color");
if (CurrentTheme === "light-theme") {
setIsLight(true);
} else {
setIsLight(false);
}
console.log(useEffect);
}, []);
const ToggleChecked = () => {
// logic
if (isLight) {
localStorage.setItem("theme-color", "dark-theme");
setIsLight(false);
} else {
localStorage.setItem("theme-color", "light-theme");
setIsLight(true);
}
console.log(ToggleChecked);
};
return (
<div className="toggle--container">
<input
type={"checkbox"}
id="toggle"
className="toggle--checkbox"
checked={isLight}
onChange={ToggleChecked}
/>
<label htmlFor="toggle" className="toggle--label">
<span className="toggle--label-background"></span>
</label>
<div className=""></div>
</div>
);
};
export default ToggleMode;
Yes, this is natural according to your code. Whenever toggling, ToggleMode component will only be re-rendered by changed state value isLight.
But your Body and Page component which are supposed to be ToggleMode's parent will not be re-rendered. Because their props or states never changed by ToggleChecked().
To get it done working, you need to do something to re-render parents in Page and Body component.
How? You need to create a state value in those components or create IsLight and setIsLight at the top level component. And then these two would be drilled into ToggleMode.
Something like followings.
export default function Page({children}){
// state
const [isLight, setIsLight] = useState(false);
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
<Body isLight={isLight} setIsLight={setIsLight} />
{children}
</div>
)
}
export default function Body({isLight, setIsLight}) {
return (
<div>
<ToggleMode isLight={isLight} setIsLight={setIsLight} />
</div>
);
}
export default function ToggleMode ({isLight, setIsLight}){
// This is not needed anymore.
// const [isLight, setIsLight] = useState(false);
return (<>Your toggle code...</>)
}
P.S. Don't you think this is quite irritating? To avoid prop drilling, we use state management utilities such as react context API or 3rd party libraries such as Redux.
I am working on e-commerce project (MERN).So i got Molla e-commerce React template for better UX/UI.
In my root function am getting all products and store them using redux like this :
const updateStore = () => {
store.dispatch( getAllProducts() );
}
Everything was working fine until i found out that if i try to access product page for the first time (with empty localstorage as in incognito mode) i get nothing and the product object was undefined , if i refresh the page then it works fine.
The problem is when i try to access the products page with empty redux store , it doesn't wait or rerender when the data are stored.
I tryed to use useEffect() to wait for product change to rerender but it's not working .
This is my product page code :
function SingleProduct( props ) {
let productLink= props.match.params.link;
const {product} = props;
const [productLoaded,setProductLoaded] = useState(false);
useEffect( () => {
if(productLoaded){
productGallery();
document.querySelector( '.skel-pro-single' ).classList.remove( 'loaded' );
let imgLoad = imagesLoaded( ".product-main-image", { background: true } );
imgLoad.on( 'done', function ( instance, image ) {
document.querySelector( '.skel-pro-single' ).classList.add( 'loaded' );
} );
}
}, [ productLink ] )
useEffect(()=>{
if(product){
setProductLoaded(true);
}
},[product])
return (
productLoaded ?
<>
<Helmet>
<title></title>
</Helmet>
<h1 className="d-none"></h1>
<div className="main">
<div className="page-content">
<div className="container">
<div className="product-details-top skeleton-body">
<div className="row skel-pro-single">
<div className="col-md-6">
<div className="skel-product-gallery">
</div>
<MediaOne link={ productLink } />
</div>
<div className="col-md-6">
<div className="entry-summary row">
<div className="col-md-12">
<div className="entry-summary1"></div>
</div>
<div className="col-md-12">
<div className="entry-summary2"></div>
</div>
</div>
<ProductDetailOne link={ productLink } />
</div>
</div>
</div>
<DescOne link={ productLink } />
<h2 className="title text-center mb-4">You May Also Like</h2>
<RelatedProducts />
</div>
</div>
<StickyBar link={ productLink } />
<QuickView />
</div>
</>
:
<></>
)
}
function mapStateToProps( state, props ) {
return {
product: state.data.products.filter( product => product.link == props.link )[ 0 ]
}
}
export default connect(mapStateToProps)(SingleProduct);
I tried to wait for product change using the useEffect and productLoaded state since it's undefined on first page render but it still showing undefined.
'product' variable, I think, is an object, be aware about what could happen if you use useEffect with that.
const a = {a:123};
const b = {a:123};
(a === b) === false
if it is possible see if the id or a string/number changes
The first useEffect will run before the second one so when this will run it will have productLoaded on false.
Try to gain productLoaded from props (you will not have to manage the asyncronicity of useEffect).
assuming that product is undefined before loading
const productLoaded = !!product;
useEffect( () => {
if(productLoaded){
..."do here what you need"
}
}, [ productLink, productLoaded ] )
maybe you can achieve a change in the css classes
<div className={`row skel-pro-single ${productLoaded?"loaded":""}`}>
as a suggestion try to use react as a templating with variables in the returning jsx, please don't mix js dom interaction and react interactions, they could disturb each other (react will try to report the part of the dom that he manages to the state he calculated, so could overwrite your changes made by
document.querySelector( '.skel-pro-single' ).classList.remove
I am learning about states in React, and below is an exercise to create a to-do list.
I capture the state of each inputted "item" in the to-do list and add them to an "items" array.
Then, each element in the "items" array is rendered using HTML.
I don't understand why I can't just push each item into a normal JavaScript array? After submitting the form, I log the "items" array. However, the "items" array only has 1 element, no matter how many times I submit the form. It seems like previous values are disappearing every time.
I solved the problem by using a React state that is an array. But I still don't understand why I can't use a JS array. Any explanations/resources would be greatly appreciated!
function App() {
const items = [];
//const [items, setItems] = React.useState([]);
const [inputText, setInputText] = React.useState("");
function handleSubmit(event) {
items.push(inputText);
console.log(items);
//setItems((prevValue) => {
// return [...prevValue, inputText];
//});
event.preventDefault();
}
function handleChange(event) {
setInputText(event.target.value);
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<form className="form" onSubmit={handleSubmit}>
<input type="text" value={inputText} onChange={handleChange} />
<button type="submit">
<span>Add</span>
</button>
</form>
<div>
<ul>
{items.map((item) => {
return <li>{item}</li>;
})}
</ul>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<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>
You have to define the let items = []; outside the function. Because in react each component reloaded on state update. so it will re-assigned to empty array. Make sure you define it using let not const
You can use the react hook useCallback to prevent items re-rendering every time state changes const items = useCallback([], [])