First click in child component is undefined in parent component React - javascript

I want to implement a "delete contact when clicked" in a simple contact manager app I'm making to learn React but it is very buggy.
The first time I click the item to delete it does nothing and when clicked another time it deletes the previous one. I'm new learning react and don't know why is this happening, someone can help?
const { useState } = React;
function AddPersonForm(props) {
const [person, setPerson] = useState("");
function handleChange(e) {
setPerson(e.target.value);
}
function handleSubmit(e) {
props.handleSubmit(person);
setPerson("");
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add new contact"
onChange={handleChange}
value={person}
/>
<button type="submit">Add</button>
</form>
);
}
function PeopleList(props) {
const [person_, setPerson_] = useState("");
function handleCLick(e) {
setPerson_(e.target.textContent);
props.handleCLick(person_);
}
return (
<ul onClick={handleCLick}>
{props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
function ContactManager(props) {
const [contacts, setContacts] = useState(props.data);
function addPerson(name) {
setContacts([...contacts, name]);
}
function removePerson(name_) {
let filtered = contacts.filter(function (value) {
return value != name_;
});
setContacts(filtered);
}
return (
<div>
<AddPersonForm handleSubmit={addPerson} />
<PeopleList data={contacts} handleCLick={removePerson} />
</div>
);
}
const contacts = ["James Smith", "Thomas Anderson", "Bruce Wayne"];
ReactDOM.render(
<ContactManager data={contacts} />,
document.getElementById("root")
);

Issue
Having the onClick handler on the unordered list element seems a bit odd to me, but your issue a misunderstanding of when React state updates. React state updates are enqueued and asynchronously processed. In other words, when you enqueue the state update setPerson_(e.target.textContent) in handleClick in PeopleList, that person_ hasn't updated on the next line props.handleCLick(person_);. You are using state that hasn't updated yet.
Solution
Just send the click event object's value to the props.handleClick callback directly. You can also remove the local person_ state.
function PeopleList(props) {
function handleCLick(e) {
props.handleCLick(e.target.textContent);
}
return (
<ul onClick={handleCLick}>
{props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}

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.

Maintain the context data for each child component on React router change

I am beginner in React and working on React app where I am using the context to maintain the button state which can be in any one phase out of start, loading, stop.
I am passing the context to app component and have a React router to render the component on basis of route. I am rendering card component by looping through data where each card have one Button Component.
On button click of card1 the button1 should get in loading phase for 10-15 seconds depending on api response time. Once response comes it should be in stop phase. Similarly for button2 and button3 if clicked together. Now that seems to be working fine when I click on button1 and button2 instantly.
But when I click on 2 buttons together and move to another route and quickly come back I don't see my buttons to be in loading state though the api response is still pending. I should be seeing them in loading state and when response comes I should see them in start or stop phase.
I know I can use local or session storage but I don't want to due to some code restrictions.
Here is the stack blitz link : https://stackblitz.com/edit/node-3t59mt?file=src/App.js
Github Link: https://github.com/mehulk05/react-context-api
Button.jsx
import React, { useContext,useEffect, useState } from 'react'
import DbContext from '../Context/sharedContext'
function Button(props) {
console.log(props)
const {
requestStartDbObj,
setRequestStartDbObj
} = useContext(DbContext)
const [state, setstate] = useState(props.text?.status ?? "start")
useEffect(() => {
setstate(props.text?.status??"start")
return () => {
}
}, [state])
console.log(requestStartDbObj)
const start = ()=>{
setRequestStartDbObj({id:props.status.id, status:"loading"})
setstate("loading")
setTimeout(()=>{
setstate("stop")
setRequestStartDbObj({id:props.status.id, status:"stop"})
},5000)
}
return (
<div>
<button onClick={start}>{state}1</button>
</div>
)
}
export default Button
Card.jsx
function Card(props) {
const {
requestStartDbObj,
} = useContext(DbContext)
return (
<div>
<h1>{props.data.name}</h1>
<Button status={props.data} text={requestStartDbObj} />
</div>
)
}
Component1.jsx
function Component1() {
let data = [
{
id: 1,
name: "card1",
status: "start",
},
{
id: 2,
name: "card2",
status: "start",
},
{
id: 3,
name: "card3",
status: "start",
},
];
return (
<div>
<h1>Hello</h1>
{data.map((d, i) => (
<Card key={i} data={d} />
))}
</div>
);
}
ComponentWrapper.jsx
<h3>Wrpper</h3>
<Routes>
<Route path="/" element={<Component1 />} />
<Route path="about" element={<Component2 />} />
</Routes>
</div>
App.js
function App() {
return (
<div className="App">
<BrowserRouter>
<Link to="/">Home</Link> <br></br>
<Link to="/about">Comp 2</Link>
<DbProvider>
<ComponentWrapper/>
</DbProvider>
</BrowserRouter>
</div>
);
}
The issue is that your DbProvider context isn't the source of truth as to the status, it's not the component maintaining the requestStartDbObj state. Each Button is duplicating the state locally and using its own start function. Each Button is also replacing the requestStartDbObj state of the context, so when switching back to the home path all the buttons get the same initial state value. Upon navigating away from the home path the Button component is unmounted, so the state updates on timeout are lost.
You should move the start logic to the sharedContext so DbProvider maintains control over the state updates. start should consume an id argument so it can correctly update the status for that specific object.
DbProvider
const DbProvider = (props) => {
const [requestStartDbObj, setRequestStartDbObj] = useState({});
const { children } = props;
const start = (id) => {
setRequestStartDbObj((state) => ({
...state,
[id]: { status: "loading" }
}));
setTimeout(() => {
setRequestStartDbObj((state) => ({
...state,
[id]: { status: "stop" }
}));
}, 5000);
};
return (
<DbContext.Provider
value={{
requestStartDbObj,
start
}}
>
{children}
</DbContext.Provider>
);
};
Card
Only pass the id prop through to Button from data prop that was passed from Component1 when mapped.
function Card({ data }) {
return (
<div>
<h1>{data.name}</h1>
<Button id={data.id} />
</div>
);
}
Button
Use the id prop to pass to start function provided by the context. Also use the id to access the current status.
function Button({ id }) {
const { requestStartDbObj, start } = useContext(DbContext);
return (
<div>
<button onClick={() => start(id)}>
{requestStartDbObj[id]?.status || "start"}-{id}
</button>
</div>
);
}

Problem with an input tag in a function - React JS

So I'm trying to render an input tag conditionally so that is why I'm using a function instead of just putting it all into a return statement. The issue I'm encountering is whenever I type a single word into an input tag it looses focus and I have to click it again to re-gain focus. My code is like this:
function App() {
function InputTag() {
if (someCondition) {
return (
<input onChange={(e) => setState(e.target.value)} value={State} />
)
} else {
return (
<input disabled value={State} />
)
}
}
return (
<div>
<InputTag />
</div>
)
}
After some de-bugging I've found that this issue occurs when I'm using a function to return an input tag but in my case I have to use a function to return the input tag.
You can experience this issue at : https://codesandbox.io/s/xenodochial-bassi-3gmyk
InputTag is being redefined every time App renders. And App is re-rendering every time the onChange event is fired because its state is being updated.
One solution would be to move InputTag outside of App, passing the state and state setter as component props.
import { useState } from "react";
export default function App() {
const [State, setState] = useState("");
return (
<div className="App">
<InputTag State={State} setState={setState} />
</div>
);
}
function InputTag({ State, setState }) {
return <input onChange={(e) => setState(e.target.value)} value={State} />;
}
However, It's not so clear why you say you need to use a function to do some conditional rendering.
If you are only making the input disabled based on someCondition, you can easily do that in App's return statement.
import { useState } from "react";
export default function App() {
const [state, setState] = useState("");
const [someCondition, setSomeCondition] = useState(true);
return (
<div className="App">
<input
onChange={(e) => setState(e.target.value)}
value={state}
disabled={!someCondition}
/>
<button onClick={() => setSomeCondition(!someCondition)}>
someCondition: {someCondition.toString()}
</button>
</div>
);
}

Cannot delete Item from Todo-list in React

I have created a simple Todo list, adding item works but when I clicked on the 'delete' button, my Item is not deleting any item from the List. I would like to know what mistakes I am making in my code, Would appreciate all the help I could get. Thanks in Advance!
And ofcourse, I have tried Looking through google and Youtube, But just couldnot find the answer I am looking for.
Link: https://codesandbox.io/embed/simple-todolist-react-2019oct-edbjf
App.js:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import TodoForm from "./TodoForm";
import Title from "./Title";
class App extends React.Component {
// myRef = React.createRef();
render() {
return (
<div className="App">
<Title />
<TodoForm />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
----------------------
TodoForm.js:
import React from "react";
import ListItems from "./ListItems";
class TodoForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: [],
id: 0
};
}
inputValue = e => {
this.setState({ value: e.target.value });
};
onSubmit = e => {
e.preventDefault();
this.setState({
value: "",
id: 0,
items: [...this.state.items, this.state.value]
});
};
deleteItem = (itemTobeDeleted, index) => {
console.log("itemTobeDeleted:", itemTobeDeleted);
const filteredItem = this.state.items.filter(item => {
return item !== itemTobeDeleted;
});
this.setState({
items: filteredItem
});
};
// remove = () => {
// console.log("removed me");
// };
render() {
// console.log(this.deleteItem);
console.log(this.state.items);
return (
<div>
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="Enter task"
value={this.state.value}
onChange={this.inputValue}
/>
<button>Add Item</button>
</form>
<ListItems items={this.state.items} delete={() => this.deleteItem} />
</div>
);
}
}
export default TodoForm;
----------------------
ListItems.js
import React from "react";
const ListItems = props => (
<div>
<ul>
{props.items.map((item, index) => {
return (
<li key={index}>
{" "}
{item}
<button onClick={props.delete(item)}>Delete</button>
</li>
);
})}
</ul>
</div>
);
export default ListItems;
The problem is, you must pass a function to the onDelete, but you are directly calling the function
updating the delete item like so,
deleteItem = (itemTobeDeleted, index) => (event) => {
and update this line, (since the itemTobeDeleted was not reaching back to the method)
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
fixes the issue
Working sandbox : https://codesandbox.io/s/simple-todolist-react-2019oct-zt5w6
Here is the working example: https://codesandbox.io/s/simple-todolist-react-2019oct-xv3b5
You have to pass in the function into ListItems and in ListItems run it passing in the correct argument (the item).
Your solution is close; there are two fixes needed for your app to work as expected.
First, when rendering the ListItems component, ensure that the item is passed through to your deleteItem() function:
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
Next, your ListItems component needs to be updated so that the delete callback prop is called after an onclick is invoked by a user (rather than immediatly during rendering of that component). This can be fixed by doing the following:
{ props.items.map((item, index) => {
return (<li key={index}>{item}
{/*
onClick is specified via inline callback arrow function, and
current item is passed to the delete callback prop
*/}
<button onClick={() => props.delete(item)}>Delete</button>
</li>);
)}
Here's a working version of your code sandbox
first make a delete function pass it a ind parameter and then use filter method on your array in which you saved the added values like
function delete(ind){
return array.filter((i)=>{
return i!==ind;
})
}
by doing this elements without the key which you tried to delete will not be returned and other elements will be returned.

Is there any way to add an event listener in a method in a component A, and have the target in a separate component

I want to add an event listener to the component Table so that every time I save a form in the component Form by the method saveForm() I call a method called showData() in the component Table.
Form Component
let persons = [];
if (JSON.parse(localStorage.getItem("personsForms")) !== null)
persons = JSON.parse(localStorage.getItem("personsForms"));
saveForm(myForm) {
persons.push(myForm);
localStorage.setItem("personsForms", JSON.stringify(persons));
}
Table Component
let persons;
let localStoragePersons = JSON.parse(localStorage.getItem("personsForms"));
persons = localStoragePersons !== null ? localStoragePersons : [];
showData() {
let table = document.getElementById('editableTable');
let x = table.rows.length;
while (--x) {
table.deleteRow(x);
}
let i = 0;
...........
}
Assuming that Table component is the parent component of Form component.
Pass the method as a prop to Form Component and call it accessing to the props (this.props.parentMethod())
From React DOCS:
Remember: React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. This is often the most challenging part for newcomers to understand, so follow these steps to figure it out:
React's one-way data flow is meant to be top-down, parent-child. The way you're suggesting would be sibling-to-sibling, which would be side-to-side instead of top-down. This is an anti-pattern.
You can set up your component hierarchy like this:
See it that works for you.
function App() {
function doSomethingApp(formValue) {
console.log('Do something from App!');
console.log('Form value is: ' + formValue);
}
return(
<React.Fragment>
<Table/>
<Form
doSomethingApp={doSomethingApp}
/>
</React.Fragment>
);
}
function Table(props) {
function doSomethingTable(formValue) {
console.log('Do something from Table!');
console.log('Form value is: ' + formValue);
}
return(
<div>I am Table</div>
);
}
function Form(props) {
const [value,setValue] = React.useState('');
return(
<React.Fragment>
<div>I am Form</div>
<input type='text' value={value} onChange={()=>setValue(event.target.value)}/>
<div>
<button onClick={()=>props.doSomethingApp(value)}>Do something from App</button>
</div>
</React.Fragment>
);
}
ReactDOM.render(<App/>,document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
SNIPPET: ATTENTION, THIS IS AN ANTI-PATTERN
Although I wouldn't recommend this at all. This is a working example with the anti-pattern you requested.
Sibling1 has a useEffect which updates a state on the common parent App with its method showData. Whenever showData changes, the effect is run again and keeps it updated. showData is a memoized callback. You can add a depedency array to update this memoized callback whenever some other variable changes.
Source: Hooks API Reference
function App() {
const [sibling1Method,setSibling1Method] = React.useState({method: null});
return(
<React.Fragment>
<Sibling1
setSibling1Method={setSibling1Method}
/>
<Sibling2
sibling1Method={sibling1Method}
/>
</React.Fragment>
);
}
function Sibling1(props) {
const showData = React.useCallback((formValue) => {
console.log('A click from Sibling2 is triggering a Sibling1 Method');
},[]);
React.useEffect(()=>{
props.setSibling1Method({method: showData});
},[showData]);
return(
<div>Sibling 1 </div>
);
}
function Sibling2(props) {
return(
<React.Fragment>
<span>Sibling 2 </span>
<button onClick={()=>props.sibling1Method.method()}>Click</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>,document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories

Resources