Child component rerendered on every state changes - javascript

Live on codesandbox
I have a simple example
import "./styles.css";
import {useState} from "react";
const Child=({text,idx})=>{
console.log(`Child - ${idx} rendered`);
return <li>{text}</li>
}
const ShouldNotRender=()=>{
console.log("Should not render")
return <p>Should not render</p>
}
export default function App() {
const [items,setItems]=useState([]);
return (
<div className="App">
<button onClick={(e)=>{
setItems([...items,parseInt(Math.random()*500,10)]);
}}>Add list</button>
<ul>
{items.map((item,idx)=>{
return <Child text={item} key={idx} idx={idx}/>
})}
</ul>
<ShouldNotRender/>
</div>
);
}
You can see, whenever we add an item to the list, then ShouldNotRender is also rerendering. ShouldNotRender is not dependent on any of the states, then it should not rerender. It is a static and heavy component(canvas).
I thought of splitting the component into two other components, one is static and the other is dynamic. Well, it worked fine but i don't understand that why react rerender ShouldNotRender component.

You can also use React.memo()
const ShouldNotRender = React.memo(() => {
console.log("Should not render");
return <p>Should not render</p>;
});

To prevent re-rendering, you can memoize the component.
export default function App() {
const [items,setItems]=useState([]);
const memoizedShouldNotRender = useMemo(() => <ShouldNotRender/>, []);
return (
<div className="App">
<button onClick={(e)=>{
setItems([...items,parseInt(Math.random()*500,10)]);
}}>Add list</button>
<ul>
{items.map((item,idx)=>{
return <Child text={item} key={idx} idx={idx}/>
})}
</ul>
{memoizedShouldNotRender}
</div>
);
}
See documentation: https://reactjs.org/docs/hooks-reference.html#usememo

Related

Cannot render list created in one component in another

Ok. I have the app.js (which will render all components on my screen) and inside this file i embeded two other js files (components). The first one is basically a button that adds one more word to an array. It goes something like this:
import { useState } from "react";
function DescriptionSector() {
const [title, setTitle] = useState([]);
return (
<button onClick={() => setTitle([...title, "New title defined"])}>add word</button>
)
This first component is working just fine as I used console.log to test it.
THe problem is with the second part.
The second part consists basically of a list that renders the array create on the first part and here's where i having trouble.
function FinancialResume({ title }) {
return (
<ul>
{title.map(e => {
return (
<li>{e}</li>
)
})}
</ul>
)
}
I tried using the props object to send the updated array like this:
import { useState } from "react";
function DescriptionSector() {
const [title, setTitle] = useState([]);
return (
<button
onClick={() => {
setTitle([...title, "New title defined"]);
FinancialResume(title);
}}
>
add word
</button>
)
BUT IT DIDNT WORKED
EDIT: here's my app.js
import DescriptionSector from "./Components/descriptionSector/description";
import FinancialResume from "./Components/financialresume/financialresume";
function App() {
return (
<div className="App">
<div className="user-body__leftSector">
<DescriptionSector />
</div>
<div className="user-body__rightSector">
<FinancialResume />
</div>
</div>
)}
export default App;
Assuming you want the changes made in DescriptionSector to be rendered by FinancialResume, one way you can do that with React is by passing props from a shared parent.
Let App control the title state. It can pass the setter down to DescriptionSector and the value down to FinancialResume.
React states are reactive to changes. App and FinancialResume will re-render when title changes without you having to call any functions.
function App() {
const [title, setTitle] = useState([]);
return (
<div className="App">
<div className="user-body__leftSector">
<DescriptionSector setTitle={setTitle} />
</div>
<div className="user-body__rightSector">
<FinancialResume title={title} />
</div>
</div>
);
}
function DescriptionSector({ setTitle }) {
return (
<button
onClick={() => {
setTitle((title) => [...title, "New title defined"]);
}}
>
add word
</button>
);
}
function FinancialResume({ title }) {
return (
<ul>
{title.map((e, i) => {
return <li key={i}>{e}</li>;
})}
</ul>
);
}
There are of course other ways to manage shared state such as Context and state stores like Redux Toolkit but those are more advanced topics.

useState is not updating the DOM in my React todo list app

I am trying to learn the basics of React and thought that making a todo list app would be a great first project to help me learn.
I have a basic form to add todos, but, when enter is clicked, the DOM does not change. Here is my app.js code, which I think is where my error might be:
import AddTodoForm from './components/AddTodoForm.js';
import TodoList from './components/TodoList.js';
import { dataList } from './components/AddTodoForm.js';
import { useState } from 'react';
function App() {
const[list, setList] = useState([]);
function update(){
setList(dataList);
console.log(list);
console.log("update function has run.")
}
return (
<div>
<AddTodoForm update = {update} />
<h1>My Todos</h1>
<TodoList todos={list} />
</div>
);
}
export default App;
Here is the code for TodoList.js as somebody had asked for it:
import Todo from './Todo';
function TodoList(props) {
return (
<ul>
{props.todos.map((todo) => (
<Todo
key = {todo.id}
id= {todo.id}
text= {todo.text}
/>
))}
</ul>
)
}
export default TodoList;
here is the AddTodoForm.js:
import { useRef, useState } from 'react';
var idCounter = 1;
export const dataList = [];
function AddTodoForm(props){
const titleInputRef = useRef();
function submitHandler(event){
event.preventDefault();
const enteredTitle= titleInputRef.current.value;
const todoData = {
text: enteredTitle,
id: idCounter,
}
idCounter++;
console.log(todoData);
dataList.push(todoData);
}
return (
<div className="card">
<h2>Add New Todos</h2>
<form onSubmit={(event) => {submitHandler(event); }}>
<div>
<label htmlFor="text">New Todo: </label>
<input type="text" required id="text" ref={titleInputRef}></input>
</div> <br />
<div>
<button className="btn" onClick = {props.update}>Add Todo</button>
</div>
</form>
</div>
)
}
export default AddTodoForm;
I have checked the log and the update function runs. Also, if I make a slight change to my code, the todos I had entered will appear on the screen but I am not sure why the DOM does not change when the update function runs.
This is my first post on here so I wasn't sure how much of my code to include. Please ask if you need the code from my other components.
Many thanks in advance :)
Calling dataList.push(todoData) won't change dataList itself, only its content, and React doesn't check the content to update the DOM. You could use the Spread syntax to have a completely new dataList.
You could even get rid of that dataList, and use the empty array given to useState. Update your update function slightly, and it should work:
import AddTodoForm from "./components/AddTodoForm.js";
import TodoList from "./components/TodoList.js";
import { useState } from "react";
function App() {
const [list, setList] = useState([]);
function update(text) {
// this is for learning; consider using a proper id in real world
setList([...list, { text: text, id: list.length + 1 }]);
}
return (
<div>
<AddTodoForm update={update} />
<h1>My Todos</h1>
<TodoList todos={list} />
</div>
);
}
export default App;
import { useRef } from "react";
function AddTodoForm(props) {
const titleInputRef = useRef();
function submitHandler(event) {
event.preventDefault();
props.update(titleInputRef.current.value);
}
return (
<div className="card">
<h2>Add New Todos</h2>
<form onSubmit={submitHandler}>
<div>
<label htmlFor="text">New Todo: </label>
<input type="text" required id="text" ref={titleInputRef}></input>
</div>
<br />
<div>
<button className="btn">Add Todo</button>
</div>
</form>
</div>
);
}
export default AddTodoForm;
In App.js, I have removed the update function and instead sent the list and setList as props to the AddTodoForm component.
import AddTodoForm from './components/AddTodoForm.js';
import TodoList from './components/TodoList.js';
import { useState } from 'react';
function App() {
const[list, setList] = useState([]);
return (
<div>
<AddTodoForm setList = {setList} list = {list}/>
<h1>My Todos</h1>
<TodoList todos={list} />
</div>
);
}
export default App;
In ./components/AddTodoForm.js add this peice of code inside the AddTodoForm function.
const update = ()=>{
props.setList(datalist);
console.log(props.list);
console.log("The update function has run.")
}
I hope this might help.
I am going to post my opinion to help you with my knowledge about React. In your above code, you cannot render updated state(list) to reflect TodoList component.
In hook, useEffect insteads 2 component lifecycles of class component idea. I mean, you should reflect useEffect with updated states(list).
useEffect is similar to componentDidMount and componentDidUpdate.
Like this:
enter code here
useEfect(()=>{
setList(dataList);
console.log(list);
console.log("update function has run.")
},[list])

React useEffect Problems

So here is the problem which I can't seem to solve. I have an app component, inside of App I have rendered the Show Component. Show component has toggle functionality as well as a outside Click Logic. In the Show component I have a Button which removes an item based on his Id, problem is that When I click on the button Remove. It removes the item but it also closes the Show Component, I don't want that, I want when I press on button it removes the item but does not close the component. Thanks
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
Show Component
import React, { useContext } from 'react'
import './Show.css'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
import { GrClose } from 'react-icons/gr'
const Show = ({closeShow}) => {
const {remove, icons }= useGlobalContext()
}
return (
<div className='control__Panel'>
<div className='close__cont'>
<GrClose className='close' onClick={closeShow} />
<h3>Show</h3>
</div>
<div className='control__cont'>
{icons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
})}
</div>
</div>
)
}
export default Show
Try stop propagation function, it should stop the propagation of the click event
<button
className='unin__button'
onClick={(e) => {
e.stopPropagation();
remove(id);
}}
>remove</button>
You have a few typos in your example. Are they in your code? If they are, you're always reach the closeShow() case in your handler, since you're using the wrong ref.
const App =()=>{
const[isShowOpen, setIsShowOpen]=React.useState(false) <<-- Here 'isShowlOpen'
const show = useRef(null) <<-- here 'S'how
function openShow(){
setIsShowOpen(true)
}
function closeShow(){ <<-- Here 'closeShowl'
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !show.current.contains(e.target)){ <<-- here 'showl'
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}

React render list only when data source changes

Basically I have a modal with a state in the parent component and I have a component that renders a list. When I open the modal, I dont want the list to re render every time because there can be hundreds of items in the list its too expensive. I only want the list to render when the dataSource prop changes.
I also want to try to avoid using useMemo if possible. Im thinking maybe move the modal to a different container, im not sure.
If someone can please help it would be much appreciated. Here is the link to sandbox: https://codesandbox.io/s/rerender-reactmemo-rz6ss?file=/src/App.js
Since you said you want to avoid React.memo, I think the best approach would be to move the <Modal /> component to another "module"
export default function App() {
return (
<>
<Another list={list} />
<List dataSource={list} />
</>
);
}
And inside <Another /> component you would have you <Modal />:
import React, { useState } from "react";
import { Modal } from "antd";
const Another = ({ list }) => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<Modal
visible={showModal}
onCancel={() => setShowModal(false)}
onOk={() => {
list.push({ name: "drink" });
setShowModal(false);
}}
/>
<button onClick={() => setShowModal(true)}>Show Modal</button>
</div>
)
}
export default Another
Now the list don't rerender when you open the Modal
You can use React.memo, for more information about it please check reactmemo
const List = React.memo(({ dataSource, loading }) => {
console.log("render list");
return (
<div>
{dataSource.map((i) => {
return <div>{i.name}</div>;
})}
</div>
);
});
sandbox here

Can I share data between sibling components in this case?

Is there a way to share data between sibling components without the need to return the component from which I need the data? here is an example to explain it better:
import React from "react";
import "./App.css";
function App() {
return (
<div className="App">
<header className="App-header">
<div>
<h1>Home page</h1>
<Component1 />
<Component2 />
</div>
</header>
</div>
);
}
const Component1 = (props) => {
const importantInfo = "secret-info";
if (props.handleInfo) {
props.handleInfo(importantInfo);
}
return (
<div>
<p>I am component number 1</p>
</div>
);
};
const Component2 = () => {
const handleInfo = (info) => {
console.log(info);
};
return (
<div>
<p>I am component number 2</p>
<Component1 handleInfo={(info) => handleInfo(info)} />
</div>
);
};
export default App;
I want to use some data from Component1 in Component2. The only way I found to do this, was to return the component1 inside the component2 and put the props in there.
I mean this:
return (
<div>
<p>I am component number 2</p>
<Component1 handleInfo={(info) => handleInfo(info)} />
</div>
Is there a way not to return the component and still receive the data throw props? I know I can do it with UseContext and with other methods, but I want to know if I can do it with props. Thanks!
What you can do is creating a new state in App.js as the following:
function App() {
const [commonInfo, setCommonInfo] = useState('information')
return // rest of the code
}
Then pass that down through props in both components as:
return (
<div className="App">
<header className="App-header">
<div>
<h1>Home page</h1>
<Component1 commonInfo={commonInfo} setCommonInfo={setCommonInfo} />
<Component2 commonInfo={commonInfo} setCommonInfo={setCommonInfo} />
</div>
</header>
</div>
);
Thus you can destructure from props in the components as the following:
// and also in Component2
const Component1 = (props) => {
const { commonInfo, setCommonInfo } = props
// rest
}

Categories

Resources