Avoid unnecessary component rendering with memo in nextjs - javascript

I'am trying to understand react's behaviour throught nextjs.
I have an index.js page with one component Homecard displayed three times and one button that increment a value.
Each time I click on button all Homecard components are re-render.
index.js
import { Homecard } from '../components/Homecard'
import { useState } from 'react'
export default function Home() {
const [count, increment] = useState(0);
const homecards = [
{
"main": "main0",
"sub": "sub0",
"desc": "Desc0",
"nav": [{
"href": "/page0",
"value": "see"
}]
},
{
"main": "main1",
"sub": "sub1",
"desc": "Desc1",
"nav": [{
"href": "/page1",
"value": "see"
}]
},
{
"main": "main2",
"sub": "sub2",
"desc": "Desc2",
"nav": [{
"href": "/page2",
"value": "see"
}]
}
];
const handleCount = () => {
increment(count => count + 1);
}
return (
<>
<div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
{
homecards.map((homecard, index) => (
<Homecard
key={index}
main={homecard.main}
sub={homecard.sub}
desc={homecard.desc}
nav={homecard.nav}
/>
))
}
</div>
<button onClick={handleCount}>increment {count}</button>
</>
)
}
homecard.js
export default function Homecard({ main, sub, desc, nav }) {
console.log('render Homecard');
return (
<div className={`${styles.homecard}`}>
<div>
<h3>
{main}
{sub &&
<span>{sub}</span>
}
</h3>
<p>{desc}</p>
{nav &&
<ul>
{nav.map((link, index) => (
<li key={index}>
<Link href={link.href}>
<a>{link.value}</a>
</Link>
</li>
))}
</ul>
}
</div>
</div>
)
}
I tried to wrap my Homecard with React.memo like so
const Homecard = React.memo(({ main, sub, desc, nav }) => {})
But I still see console.log('render Homecard'); when my button is clicked.
How can I could update only my button and not others components ?

The problem is that you're recreating your homecards array on every render of Home, so each nav object is a new object, and so React.memo sees a difference in the props and doesn't optimize away the subsequent re-renders.
There are a couple of ways to fix it:
Define homecards outside Home; it's unchanging, so there's no reason to recreate it every time Home is called.
If you couldn't do that for some reason, you could pass a second argument to React.memo, the "areEqual" function, which will receive the old props and the new ones; in the function, do a deep comparison to see if if nav objects have the same property values are the previous ones even though they're different objects.
Example of #1:
const { useState } = React;
/*export default*/ const Homecard = React.memo(({ img, main, sub, desc, nav }) => {
console.log('render Homecard');
return (
<div className={`${""/*styles.homecard*/}`}>
<div>
<h3>
{main}
{sub &&
<span>{sub}</span>
}
</h3>
<p>{desc}</p>
{nav &&
<ul>
{nav.map((link, index) => (
<li key={index}>
<a href={link.href}>
{link.value}
</a>
</li>
))}
</ul>
}
</div>
</div>
)
});
const homecards = [
{
"main": "main0",
"sub": "sub0",
"desc": "Desc0",
"nav": [{
"href": "/page0",
"value": "see"
}]
},
{
"main": "main1",
"sub": "sub1",
"desc": "Desc1",
"nav": [{
"href": "/page1",
"value": "see"
}]
},
{
"main": "main2",
"sub": "sub2",
"desc": "Desc2",
"nav": [{
"href": "/page2",
"value": "see"
}]
}
];
/*export default*/ function Home() {
const [count, increment] = useState(0);
const handleCount = () => {
increment(count => count + 1);
}
return (
// Sadly, Stack Snippets don't support <>...</>
<React.Fragment>
<div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
{
homecards.map((homecard, index) => (
<Homecard
key={index}
main={homecard.main}
sub={homecard.sub}
desc={homecard.desc}
nav={homecard.nav}
/>
))
}
</div>
<button onClick={handleCount}>increment {count}</button>
</React.Fragment>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Home />);
<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>
(Warning: I changed a couple of minor things to make this work in Stack Snippets, like replacing Link and using React.Fragment instead of <>...</>, so don't copy and paste it directly back into your project, just move homecards in your existing code.)

Related

Error: Objects are not valid as a React child. If you meant to render a collection of children, use an array instead. Getting data from JSON

guys! I'm using ReactJS to create a small website. Since I added the following code it starts showing an error: Objects are not valid as a React child. If you meant to render a collection of children, use an array instead.
Code:
import { useState, useEffect } from 'react';
import { motion, useAnimation } from 'framer-motion';
import './css/App.min.css';
import config from './config';
function App() {
return (
<div className="myskills">
<Skills skillType="coding" />
</div>
);
}
function Skills(props){
const skillType = props.skillType;
const result = config.skills.filter(skill => skill.cat == skillType);
console.log(result);
result.map((skill, index) => (
<div className="singleSkill" key={index}>
{skill.name} Level: {skill.level}
</div>
));
return (<div>{result}</div>);
}
config.json
{
"skills": [
{
"name": "HTML",
"level": 5,
"cat": "coding"
},
{
"name": "CSS",
"level": 5,
"cat": "coding"
},
{
"name": "PHP",
"level": 4,
"cat": "coding"
}
]
}
Any ideas what's the problem?
The return statement in your Skills component is basically just this:
return (config.skills.filter(skill => skill.cat == skillType));
hence the "Objects are not valid as a React child" error.
Since result.map doesn't modify the original array, a better solution might look something like this:
function Skills(props) {
const skillType = props.skillType;
const result = config.skills.filter(skill => skill.cat == skillType);
return (
<div>
{result.map((skill, index) => (
<div className="singleSkill" key={index}>
{skill.name} Level: {skill.level}
</div>
))}
</div>
);
}

Cannot map subchildren of children

My file structure has a questions array, in which there's one more section_questions array I want to access.
The React code looks like this
<div className="questions-container">
{employee.questions.map(question => (
<p className="section-title">{question.section_name}</p>
/* This won't work, syntax error */
{ question.section_questions.map(sq => ( <p>Yo</p> )) }
)
)}
</div>
JSON-Object:
"questions": [
{
"section_name": "Random",
"section_questions": [
{
"question": "Q1",
"answer": "A1"
},
{
"question": "Q2",
"answer": "A2"
}
]
}
]
Why won't this work? How can I fix this?
The error is Parsing error: Unexpected token, expected ","
The problem seems to be that you need a tag (or fragment) that encloses everything underneath.
So you could:
move the < /p> to be after { question.section_questions.map(sq => ( <p>Yo</p> )) } .
Or enclose everything in a div.
Or use a react fragment < React.Fragment>< React.Fragment/> instead of div. Suggested by: #Antoan Elenkov
See: reactjs.org/docs/fragments.html
Example:
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.questions = [
{ section_name: "Learn JavaScript", section_questions: [1] },
{ section_name: "Learn React", section_questions: [1] },
{ section_name: "Play around in JSFiddle", section_questions: [1] },
{ section_name: "Build something awesome", section_questions: [1] }
];
}
render() {
return (
<div>
<h2>Todos:</h2>
{this.questions.map(question => (
<p className="section-title">
{question.section_name}
{question.section_questions}
{question.section_questions.map(sq => ( <p>Yo</p> ))}
</p>
)
)}
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
Fiddle: https://jsfiddle.net/opy9k5hr/
Update
Another option is to enclose everything in a <div> tag.
{this.questions.map(question => (
<div>
<p className="section-title"> {question.section_name} </p>
{question.section_questions}
{question.section_questions.map(sq => ( <p>Yo</p> ))}
</div>
)
)}

How to set data in a way that after refresh also It stays there in react

I am trying to make a react project with node and mongo where I will show elements of my periodic table onclick I would update link and show popup with data. Here is the json data
{
"elements": [
{
"name": "Hydrogen",
"appearance": "colorless gas",
"atomic_mass": 1.008,
"boil": 20.271,
"category": "diatomic nonmetal",
"color": null,
"density": 0.08988,
"discovered_by": "Henry Cavendish",
"melt": 13.99,
"molar_heat": 28.836,
"named_by": "Antoine Lavoisier",
"number": 1,
"period": 1,
"phase": "Gas",
"source": "https://en.wikipedia.org/wiki/Hydrogen",
"spectral_img": "https://en.wikipedia.org/wiki/File:Hydrogen_Spectra.jpg",
"summary": "Hydrogen is a chemical element with chemical symbol H and atomic number 1. With an atomic weight of 1.00794 u, hydrogen is the lightest element on the periodic table. Its monatomic form (H) is the most abundant chemical substance in the Universe, constituting roughly 75% of all baryonic mass.",
"symbol": "H",
"xpos": 1,
"ypos": 1,
"shells": [
1
],
"electron_configuration": "1s1",
"electron_affinity": 72.769,
"electronegativity_pauling": 2.20,
"ionization_energies": [
1312.0
]
},
{
"name": "Helium",
"appearance": "colorless gas, exhibiting a red-orange glow when placed in a high-voltage electric field",
"atomic_mass": 4.0026022,
"boil": 4.222,
"category": "noble gas",
"color": null,
"density": 0.1786,
"discovered_by": "Pierre Janssen",
"melt": 0.95,
"molar_heat": null,
"named_by": null,
"number": 2,
"period": 1,
"phase": "Gas",
"source": "https://en.wikipedia.org/wiki/Helium",
"spectral_img": "https://en.wikipedia.org/wiki/File:Helium_spectrum.jpg",
"summary": "Helium is a chemical element with symbol He and atomic number 2. It is a colorless, odorless, tasteless, non-toxic, inert, monatomic gas that heads the noble gas group in the periodic table. Its boiling and melting points are the lowest among all the elements.",
"symbol": "He",
"xpos": 18,
"ypos": 1,
"shells": [
2
],
"electron_configuration": "1s2",
"electron_affinity": -48,
"electronegativity_pauling": null,
"ionization_energies": [
2372.3,
5250.5
]
},
{
"name": "Lithium",
"appearance": "silvery-white",
"atomic_mass": 6.94,
"boil": 1603,
"category": "alkali metal",
"color": null,
"density": 0.534,
"discovered_by": "Johan August Arfwedson",
"melt": 453.65,
"molar_heat": 24.86,
"named_by": null,
"number": 3,
"period": 2,
"phase": "Solid",
"source": "https://en.wikipedia.org/wiki/Lithium",
"spectral_img": null,
"summary": "Lithium (from Greek:\u03bb\u03af\u03b8\u03bf\u03c2 lithos, \"stone\") is a chemical element with the symbol Li and atomic number 3. It is a soft, silver-white metal belonging to the alkali metal group of chemical elements. Under standard conditions it is the lightest metal and the least dense solid element.",
"symbol": "Li",
"xpos": 1,
"ypos": 2,
"shells": [
2,
1
],
"electron_configuration": "1s2 2s1",
"electron_affinity": 59.6326,
"electronegativity_pauling": 0.98,
"ionization_energies": [
520.2,
7298.1,
11815.0
]
}]}
The problem in my code is whenever somebody sets URL manually It wont get default popup with data. I want the urls to be dynamic but be accessible. I am using the element name as react link which is redirected to route on call by passing details of elements to be shown in popup.
Here are my files for react
App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import axios from 'axios';
import Element from './components/Element';
function App() {
// const context = React.createContext();
const [elements, setElements] = useState([]);
useEffect(() => {
const res = async () => {
const result = await axios.get('/data');
const data = result.data;
setElements(elements => [...elements, ...data]);
};
res();
}, []);
// console.log(elements.map(element => console.log(element)));
return (
<div className='wrapper'>
<div id='table'>
{elements.map(element => (
<Element elements={element} key={element._id} />
))}
</div>
</div>
);
}
export default App;
In App.js I am fetching the data and mapping it to send to the element to be shown in front end.
Element.js
import React, { useState } from 'react';
import {
BrowserRouter as Router,
Redirect,
Route,
Link
} from 'react-router-dom';
import Popup from './Popup';
const Element = ({ elements }) => {
const { symbol, category, name, number } = elements;
const [showPopup, setPopup] = useState(false);
const test = window.location.pathname;
console.log(abc === '/' ? 'yes' : 'no');
const handleClick = () => {
setPopup(!showPopup);
};
return (
<Router>
<div
onClick={() => handleClick()}
title={name}
className={`element element-${number} ${category}`}
>
{' '}
<Link to={name}>
<div className='symbol'>{symbol}</div>
</Link>
{showPopup ? (
<Route
exact
path='/:name'
component={() => <Popup element={elements} />}
/>
) : (
<Redirect to='/' />
)}
</div>
</Router>
);
};
export default Element;
In Element.js I am trying to get the props from app.js and on click on any element I am showing data and updating link. when they click again I make the popup variable as false and thus redirecting to home page.
Popup.js
import React from 'react';
// import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
const Popup = ({ element }) => {
console.log(element);
const { category } = element;
return (
<div className='popup'>
<center>
<div className={`popupInner ${category}`}>
{Object.entries(element).map(([key, val]) => (
<h2 key={key}>
{key}: {val ? val : 'unknown'}
</h2>
))}
</div>
</center>
</div>
);
};
export default Popup;
In Popup.js I am showing the data details by mapping through the contents.
Please tell me how can I do that? I am redirecting to '/' when showPopup is false. Please provide suggestions and description with the code.

How can I loop over a JSON object in React

I'm trying to retrieve information from the following JSON object data.json:
{
"status": "ok",
"feed": {
"title": "NOS Nieuws",
},
"items": [
{
"title": "Test Title",
"description": "Test description",
"enclosure": {
"link": "https://examplelink.com/1008x567.jpg",
"type": "image/jpeg"
},
"categories": []
},
{
"title": "Test Title 2",
"description": "Test 2",
"enclosure": {
"link": "link": "https://examplelink.com/1008x567.jpg",
"type": "image/jpeg"
},
"categories": []
}
]
}
So I want to loop over this JSON object to display all the available items and its corresponding title, description and enclosure-link.
I know i can access them separately as:
const items = data.items;
const title = items.title;
const url = items.enclosure.link;
Usually I would do a for-loop and loop through data.items[i]. However, since this is a react and an object instead of an array it works differently.
My current code:
class Feed extends Component {
render() {
const items = data.items[0];
const title = items.title;
const url = items.enclosure.link;
const description = items.description;
const feed = [
{
url: url,
title: title,
description: description
}
];
return (
<div className="feed">
<h1>Newsfeed</h1>
<div className="columns is-multiline">
{feed.map(article => (
<div className="column is-one-third">
<NewsCard
article={article}
title={items.title}
description={items.description}
/>
</div>
))}
</div>
</div>
);
}
}
Right now its only displaying the first entry of the object (because it has const items = data.items[0]) How can I loop over data.json and display its content in the NewsCard component? I know that each child should have a unique 'key' prop but thats where I'm stuck.
I want to loop over this JSON object to display all the available
items and its corresponding title, description and enclosure-link
Then instead of doing this:
const items = data.items[0];
Try this:
const items = data.items;
Then, you can use the map function, like this:
items.map(item => (
<div className="column is-one-third">
<NewsCard
article={item.enclosure.link}
title={item.title}
description={item.description}
/>
</div>
));
You could do something like this.
class Feed extends Component {
render() {
let newsCards = data.items.map(item => {
<div className="column is-one-third">
<NewsCard
article={item}
title={item.title}
description={item.description}
/>
</div>
});
return (
<div className="feed">
<h1>Newsfeed</h1>
<div className="columns is-multiline">
{newsCards}
</div>
</div>
);
}
}
const feed = data.items
{feed.map(item => (
<div className="column is-one-third">
<NewsCard
article={item.enclosure.link}
title={item.title}
description={item.description}
/>
</div>
))}
try this way

Render data from local JSON (with dynamic data) in ReactJs

I have a json array of objects as shown below, the number of objects can vary as well as the content of the "units" array inside the respective object can vary.
[{
"title": "title1",
"units": [{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
},
{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
},
{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
},
{
"title": "unittitle1",
"status": "on",
"utilization": "76.4"
}
]
},
{
"title": "title2",
"units": [{
"title": "unittitle1",
"status": "on"
},
{
"title": "unittitle1",
"status": "on"
}
]
}
]
I am calling the above json like this :
import React, { Component } from 'react';
import hardwareDetailsData from '../../../assets/json/UnitHardwareDetails.json';
import meter from '../../../assets/images/meter.svg';
class HardwareUnits extends Component {
constructor() {
super();
this.state = {
hardwareDetailsData: []
};
}
render() {
return hardwareDetailsData.map(hardwareData => (
<div className="f-14">
<ul className="list-inline list-pipe">
<li className="list-inline-item">
<span className="f-b">{hardwareData.units.title}</span>
<span className="status-text ml-2 text-success">{hardwareData.units.status}</span>
</li>
<li className="list-inline-item">
<span className="text-warning f-b">{hardwareData.units.utilization}</span>
<img src={meter} alt="Meter" className="ml-3 icon-meter-size" />
</li>
</ul>
</div>
));
}
}
export default HardwareUnits;
I am aware that the above code would require unique key for child elements. But, is there a optimal way to achieve the main objective i.e. to render the dynamic content of JSON in the list
I understood you want map in map like so:
return hardwareDetailsData.map(({ title, units }) => (
<div className="title">
{title}
units.map({ title, status, utilization}) => (
<div className="status">{status}</div>
));
</div>
));
Keep in mind shadowing, key (as you mentioned) and conditional rendering for utilization if it's the case. Also it could be splitted to components.
Your render method should be like this:
render() {
return hardwareDetailsData.map((hardwareItem, index) => (
<div className="f-14" key={index}>
<ul className="list-inline list-pipe">
{
hardwareItem.units.map((unitItem, index) => (
<li className="list-inline-item" key={index}>
<span className="f-b">{unitItem.title}</span>
<span className="status-text ml-2 text success">{unitItem.status}</span>
<span className="text-warning f-b">{unitItem.utilization}</span>
<img src={meter} alt="Meter" className="ml-3 icon-meter-size" />
</li>
))
}
</ul>
</div>
));
}

Categories

Resources