i propgramed a todo-app in React. i create components for several parts. Now everytime i try to run the app it will be not displayed.
i alwas get that error Uncaught TypeError: todo is undefined in footer.js:15.
I create a todo-list app and put all my todos in an array where i have the use-state todo. This is the property i pass todo = {todo} ind my component Todocounter which is in the file footer.
I tried to rename the prop and to change its postion in the footer in order to invoke in the right place.
This is the app.js:
import React, { useState } from 'react';
import './App.css';
import InputTodos from './input.js';
import ListTodos from './list.js';
import TodoCounter from './footer.js';
import ClearButton from './clearbutton.js';
function App() {
// create usestates for todos
const [todo, setTodo] = useState([]);
// render all components i have in diffrent files
return (
<div className="App">
<div className="container">
<div className="header">
<InputTodos todo={todo} setTodo={setTodo} />
</div>
<div className="containerMid">
<ListTodos todo={todo} />
</div>
<div className="footer">
<TodoCounter todo={todo} />
</div>
<div className="buttonCleardiv">
<ClearButton todo={todo} setTodo={setTodo} />
</div>
</div>
</div>
);
}
export default App;
and this is the footer.js:
import React, { useEffect, useState } from 'react';
import './App.css';
// use effect to show whenever the array will change from completed todos to not completed
function TodoCounter(props) {
const { todo } = props;
const [completed, setCompleted] = useState(0);
const [notCompleted, setNotCompleted] = useState(0);
// filter between completed todos and not completed todos with cheackking the bolean status
function counttodos(props) {
const { todo } = props;
return {
completed: todo.filter((todo) => todo.isChecked).length,
notCompleted: todo.filter((todo) => !todo.isChecked).length,
};
}
//with the useeffect hook set the todos on completed or not completed if sth changes on the todos
useEffect(() => {
const { completed, notcompleted } = counttodos(todo);
setCompleted(completed);
setNotCompleted(notcompleted);
}, [todo]);
return (
<div>
<p>Completed: {completed}</p>
<p>Not Completed: {notCompleted}</p>
<p>Todos: {todo.length} </p>
</div>
);
}
export default TodoCounter;
Move the counttodos function out of the component, so it won't be re-created on render. Because you pass todos as a parameter to the function, and it's not wrapped with another object, you can use it directly without destructuring it:
// filter between completed todos and not completed todos with cheackking the bolean status
function counttodos(todos) {
return {
completed: todos.filter(todo => todo.isChecked).length,
notCompleted: todos.filter(todo => !todo.isChecked).length,
};
}
In the component itself, call counttodos, and use the computed values directly without storing it a state (see this comment by #HenryWoody):
function TodoCounter({ todo }) {
//with the useeffect hook set the todos on completed or not completed if sth changes on the todos
const { completed, notcompleted } = counttodos(todo);
return (
<div>
<p>Completed: {completed}</p>
<p>Not Completed: {notCompleted}</p>
<p>Todos: {todo.length} </p>
</div>
);
}
Related
After onClick method to splice array, data seems to delete but page isn't updating. How to reRender or update the page to reflect the changes?
Home.js:
import React from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
function handleDelete(id) {
var index = HomeData.map(function (e) {
return e.id;
}).indexOf(id);
HomeData.splice(index, 1);
}
return (
<>
<section className="home_section">
<div className="home_container">
{HomeData.map((item) => {
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
Data:
const HomeData = [
{
id: 1,
heading: 'This is first Heading'
},
{
id: 2,
heading: 'This is Second Heading'
},
]
export default HomeData;
I have tried using useNavigate from react-router-dom and history, but it didn't work.
In React functional components you can use a hook called useState. With this hook you can get and set the data however you want it.
const [data, setData] = useState(homeData);
Mutating state however is a big no-no in the React ecosystem because of the fact that it heavily practices the concept of immutability. Splice mutates the state by deleting or adding to the element itself.
Instead of mapping and splicing you can use filter with the setter. Filter is immutable, because it creates a shallow copy. You want to create a shallow copy, but without the item that has the id given as a parameter in your function. This would translate to the following code:
setData(homeData.filter(home => home.id !== id));
Now all you have to do is map through the state "data" instead of the homeData directly.
Maybe you can utilize state for this, can use useState hooks
It will be something like this:
import React, {useState} from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [data,setData] = useState(HomeData)
function handleDelete(id) {
const newData = data.filter((e) => e.id !== id)
setData(newData)
}
return (
<>
<section className="home_section">
<div className="home_container">
[don't forget to use the state here] >>> {data.map((item) => {
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
Issue
In the current implementation the code is mutating an object that ins't part of any React state, so React isn't aware that anything needs to be rerendered.
Things to keep in mind:
Array.prototype.splice does an in-place mutation of the array it operates over.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place. To access part of an array without modifying it, see slice().
React components rerender for one of three reasons:
A local component state update is enqueued, component and sub-ReactTree rerender
A passed props value is updated, component and sub-ReactTree rerender
The parent component rerendered (because state and/or props updated)
Solution
To correctly render and update the HomeData array it necessarily should be part of a React component state. When updating React state, all state, and sub-state, necessarily needs to be a new object reference. This is because React uses a shallow reference equality check. It's far more common to use Array.prototype.filter to filter an existing array and return a new array reference.
Home Example:
import React from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [homeData, setHomeData] = React.useState(HomeData); // <-- initialize state
const handleDelete = (id) => {
setHomeData(data => data.filter(el => el.id !== id)); // <-- filter and return new array
};
return (
<section className="home_section">
<div className="home_container">
{homeData.map((item) => ( // <-- map homeData state
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading}</h1>
<button
button="button" // <-- should be explicit with button type
onClick={handleDelete}
>
Delete
</button>
</div>
))}
<button
className="submit_btn"
type="submit" // <-- should be explicit with button type
>
Submit
</button>
</div>
</section>
);
}
export default Home;
You should use the useState hooks to update the view
import React, { useState } from "react"; //imported useState
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [homeData, setHomeData] = useState(HomeData); //Added here
function handleDelete(id) {
const newData = homeData.filter((e) => e.id !== id)
setHomeData(newData)
}
return (
<>
<section className="home_section">
<div className="home_container">
{homeData.map((item) => { //changed state here
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
I wanna change the title by clicking the button but it doesn't change, can I have an explanation why is that happens?
import './ExpenseItem.css';
import ExpenseDate from './ExpenseDate';
import Card from './Card';
function ExpenseItem(props){
let title = props.expenseTitle;
function clickedFunc(){
title = "Update!";
}
return(
<Card className='expense-item'>
<ExpenseDate expenseDate={props.expenseDate}></ExpenseDate>
<div className='expense-item__description'>
<h2>{title}</h2>
<div className='expense-item__price'>
₹{props.expenseAmount}
</div>
</div>
<button onClick={clickedFunc}>Click</button>
</Card>
);
}
export default ExpenseItem;
This is not how data is handled with React.
The title should be stored in a state variable (see useState).
Once the data is stored in a state variable, you will have to set it with setState. When setState is called in React, the component holding the state variable re-renders. This will in turn cause your ExpenseItem component to re-render because it is a child component of whatever higher level component passed it props.
In your parent component, you should see something like:
require { useState } from 'react';
const ParentComponent = (props) => {
const [title, setTitle] = useState('Original Title');
...
...
...
return (
<div className="ParentComponent">
<ExpenseItem
title={title}
setTitle={setTitle}
expenseAmount={expenseAmount}
/>
</div>
)
}
Then, in your clickedFunc() function:
function clickedFunc() {
props.setTitle("Update!");
}
I'm new to React and am attempting to set up a Bootstrap modal to show alert messages.
In my parent App.js file I have an error handler that sends a Modal.js component a prop that triggers the modal to show, eg:
On App.js:
function App() {
const [modalShow, setModalShow] = useState(false);
// Some other handlers
const alertModalHandler = (modalMessage) => {
console.log(modalMessage);
setModalShow(true);
}
return (
// Other components.
<AlertModal modalOpen={modalShow}/>
)
}
And on Modal.js:
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import "bootstrap/dist/css/bootstrap.min.css";
const AlertModal = (props) => {
const [isOpen, setIsOpen] = useState(false);
if (props.modalOpen) {
setIsOpen(true);
}
return (
<Modal show={isOpen}>
<Modal.Header closeButton>Hi</Modal.Header>
<Modal.Body>asdfasdf</Modal.Body>
</Modal>
);
};
export default AlertModal;
However, this doesn't work. I get the error:
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
If I change the Modal component to be a 'dumb' component and use the prop directly, eg:
const AlertModal = (props) => {
return (
<Modal show={props.modalOpen}>
<Modal.Header closeButton>Hi</Modal.Header>
<Modal.Body>asdfasdf</Modal.Body>
</Modal>
);
};
It does work, but I was wanting to change the show/hide state on the Modal.js component level as well, eg have something that handles modal close buttons in there.
I don't understand why is this breaking?
And does this mean I will have to handle the Modal close function at the parent App.js level?
Edit - full app.js contents
import React, { useState } from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.css';
import AddUserForm from './components/addUserForm';
import UserList from './components/userList';
import AlertModal from './components/modal';
function App() {
const [users, setUsers] = useState([]);
const [modalShow, setModalShow] = useState(false);
const addPersonHandler = (nameValue, ageValue) => {
console.log(nameValue, ageValue);
setUsers(prevUsers => {
const updatedUsers = [...prevUsers];
updatedUsers.unshift({ name: nameValue, age: ageValue });
return updatedUsers;
});
};
const alertModalHandler = (modalMessage) => {
console.log(modalMessage);
setModalShow(true);
}
let content = (
<p style={{ textAlign: 'center' }}>No users found. Maybe add one?</p>
);
if (users.length > 0) {
content = (
<UserList items={users} />
);
}
return (
<>
<div className="container">
<div className="row">
<div className="col-md-6 offset-md-3">
<AddUserForm onAddPerson={addPersonHandler} fireAlertModal={alertModalHandler}/>
</div>
</div>
<div className="row">
<div className="col-md-6 offset-md-3">
{content}
</div>
</div>
</div>
<AlertModal modalOpen={modalShow}/>
</>
);
}
export default App;
In your modal.js
you should put
if (props.modalOpen) {
setIsOpen(true);
}
in a useEffect.
React.useEffect(() => {if (props.modalOpen) {
setIsOpen(true);
}}, [props.modalOpen])
You should never call setState just like that. If you do it will run on every render and trigger another render, because you changed the state. You should put the setModalShow together with the if clause in a useEffect. E.g.:
useState(() => {
if (modalOpen) {
setIsOpen(true);
}
}, [modalOpen])
Note that I also restructered modalOpen out of props. That way the useEffect will only run when modalOpen changes.
If you already send a state called modalShow to the AlertModal component there is no reason to use another state which does the same such as isOpen.
Whenever modalShow is changed, it causes a re-render of the AlertModal component since you changed it's state, then inside if the prop is true you set another state, causing another not needed re-render when you set isOpen. Then, on each re-render if props.showModal has not changed (and still is true) you trigger setIsOpen again and again.
If you want control over the modal open/close inside AlertModal I would do as follows:
<AlertModal modalOpen={modalShow} setModalOpen={setModalShow}/>
Pass the set function of the showModal state to the modal component, and there use it as you see fit. For example, in an onClick handler.
modal.js:
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import "bootstrap/dist/css/bootstrap.min.css";
const AlertModal = (props) => {
const onClickHandler = () => {
props.setModalOpen(prevState => !prevState)
}
return (
<Modal show={props.modalOpen}>
<Modal.Header closeButton>Hi</Modal.Header>
<Modal.Body>asdfasdf</Modal.Body>
</Modal>
);
};
export default AlertModal;
I have the next code, where I import NextButton and GroupButton from TitleHeader,
those components are simple buttons
After that, I declared a simple array ButtonsArray and filled it with those components in the useEffect segment, in adition, I 'bind' the Button function to the button component.
Example :
<NextButton function={ShowSearchBar}/>
Then, my other component TitleHeader receives the array and render the components inside it using a map function
My issue is, if I use the const array ButtonsArray with the components loaded as props in TitleHeader, when press the NextButton in the UI to confirm everything is working something weird happens
The only job of NextButton is execute ShowSearchBar function whose have to switch a const from true to false and vice versa but it doest not work,
If i debug the program, when I press the button, the program enters to the ShowSearchBar function but ALWAYS allowFind is false
Note: if I declare the array directly in the TitleHeader params everything works fine
import React, { useState, useEffect } from "react";
import { TitleHeader, NextButton, GroupButton } from "../Common/TitleHeader";
export const ACATG001 = () => {
const [allowFind, setAllowFind] = useState(false);
const [allowGroup, setAllowGroup] = useState(false);
const [ButtonsArray, setButtonsArray] = useState([]);
useEffect(() => {
setButtonsArray([
<NextButton function={ShowSearchBar} />,
<GroupButton function={ShowGroupBar} />,
]);
}, []);
function ShowSearchBar() {
setAllowFind(!allowFind);
}
return (
<GeneralContainer>
//doesnt work (using a const type array and filled in UseEffect)
<TitleHeader
Title={t("TTER001")}
BarSize="300px"
Embedded={false}
ButtonsArray={ButtonsArray}
/>
//Works declaring the array and the items inline
<TitleHeader
Title={t("TTER001")}
BarSize="300px"
Embedded={false}
ButtonsArray={[
<NextButton function={ShowSearchBar} />,
<GroupButton function={ShowGroupBar} />,
]}
/>
</GeneralContainer>
);
};
Second JS TitleHeader
import React, { Component } from "react";
import { Button } from "primereact/button";
export class TitleHeader extends Component {
constructor() {
super();
}
componentDidMount() {}
render() {
let TitleDesing;
TitleDesing = (
<div className="Buttons-Group">
{this.props.ButtonsArray.map((component, index) => (
<React.Fragment key={index}>{component}</React.Fragment>
))}
</div>
);
return TitleDesing;
}
}
export const NextButton = (props) => {
return (
<Button
id="nextButton"
label="test"
tooltip="Next"
className="p-button-rounded p-button-text"
onClick={props.function}
>
<CgChevronRight size="20PX" color=" #d6f1fa" />{" "}
</Button>
);
};
If the update you do to a state depends only on its current value, always use the function callback version of the dispatcher, this will guarantee you don't use a stale value
function ShowSearchBar() {
setAllowFind((previousAllowFind) => !previousAllowFind)
}
I am trying to reducing my code complexity to express by defining just skeleton code bellow. have to trigger the toggleModel of the child component
import React, { useState } from "react";
import "./styles.css";
const ChildComponent = (props) => {
// .... some useStates
const toggleModel = () => {
// have to trigger this methoud once user clicks on button
// have to change some states here
};
return (
<div>
{props.children}
...... other things .......
</div>
);
};
export default function ParentComponet() {
return (
<div className="App">
Hello
<ChildComponent>
<button
type="button"
onClick={() => {
// here i have to trigger the toggleModel function of ChildComponent
}}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
i am rendering child component by sending children elements, have to trigger the toggleModel of the child component it will reduce my 70 % redundant code at our application. is there any way to achieve the same codesandbox. Thank you in advance
You can use useState and useEffect to pass state down and react to it.
import React, { useState } from "react";
import "./styles.css";
const ChildComponent = ({visible, children, setVisible}) => {
React.useEffect(() => {
const toggleModel = () => {
alert('Visible changes to ' + visible )
};
toggleModel()
}, [visible])
return <div>{children}</div>;
};
export default function ParentComponet() {
const [visible, setVisible] = React.useState(false)
return (
<div className="App">
Hello
<ChildComponent visible={visible} setVisible={setVisible}>
<button
type="button"
onClick={()=> setVisible(!visible)}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
https://codesandbox.io/s/objective-ramanujan-j3eqg
The alternative is use #yaiks answer.
You can take a look at this question here, it can help you.
But I would say it's not a good practice to call a child function from the parent. Usually what I would do is to "lift up" the method to the parent, and pass down to the child if possible.
Here is another way to call your ChilComponent's function - using forwardRef:
import React, { useState, useImperativeHandle, forwardRef } from "react";
import "./styles.css";
const ChildComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
toggleModel() {
alert("alert from ChildComponent");
}
}));
return <div>{props.children}</div>;
});
export default function ParentComponet() {
return (
<div className="App">
Hello
<ChildComponent ref={ChildComponent}>
<button
type="button"
onClick={() => ChildComponent.current.toggleModel()}
>
Toggle Model
</button>
</ChildComponent>
</div>
);
}
Sandbox: https://codesandbox.io/s/pensive-jones-lw0pf?file=/src/App.js
My answer is courtesy of #rossipedia: https://stackoverflow.com/a/37950970/1927991