react useEffect with function received as props from parent component - reactjs

I have a parent component
import React ,{useState} from 'react'
import ChildComponenet from './child'
export default function App() {
const [parentValue , setParentValue] = useState(0)
const someParentfunc = (input1,input2)=>{
setParentValue( parseInt(input1,10) +parseInt(input2,10))
}
return (
<div className="App">
Add : {parentValue}
<ChildComponenet someParentfunc={someParentfunc} />
</div>
);
}
now the child component :
import React ,{useState,useEffect} from 'react'
function ChildComponenet({someParentfunc}) {
const [input1 , setInput1] = useState(10)
const [input2 , setInput2] = useState(20)
useEffect(() => {
someParentfunc(input1,input2)
}, [input1,input2])
return (
<div>
<input type="text"
value={input1}
onChange={event => setInput1(event.target.value) }
/>
<input type="text"
value={input2}
onChange={event => setInput2(event.target.value) }
/>
</div>
)
}
export default ChildComponenet
the error is: React Hook useEffect has a missing dependency: 'someParentfunc'. Either include it or remove the dependency array.
should I add someParentfunc in the useEffect , will it be right approach :
useEffect(() => {
someParentfunc(input1, input2);
}, [input1, input2,someParentfunc]);
the working project link is : https://codesandbox.io/s/competent-kare-kce02?file=/src/child.js

someParentfunc is not using any value from it closures and so if you avoid it in in the dependency, there shouldn't be any problem. However for the purpose of ou code being extendable you should try to follow the patterns and add it in the dependenccy so that if doesn't cause any issues if the function is updated in future
To add it as a dependency you should make sure to use useCallback function so that the function is not re-created on each re-render
export default function App() {
const [parentValue , setParentValue] = useState(0)
const someParentfunc = useCallback((input1,input2)=>{
setParentValue( parseInt(input1,10) +parseInt(input2,10))
}, [setParentValue]);
return (
<div className="App">
Add : {parentValue}
<ChildComponenet someParentfunc={someParentfunc} />
</div>
);
}
Now you can add it to the useEffect dependenccy in child component
function ChildComponent({someParentfunc}) {
const [input1 , setInput1] = useState(10)
const [input2 , setInput2] = useState(20)
useEffect(() => {
someParentfunc(input1,input2)
}, [input1,input2, someParentfunc])
return (
<div>
<input type="text"
value={input1}
onChange={event => setInput1(event.target.value) }
/>
<input type="text"
value={input2}
onChange={event => setInput2(event.target.value) }
/>
</div>
)
}

Related

How to update the parent / list component from the child / detail component in ReactJS?

I am beginner and practicing on Library Management System in react. So I have components named BookDetails.js, BookList.js. BookDetails contains the form for entering Title and Description. So How can I pass the data entered from BookDetails to BookList and to dispaly from App.
import React, { useState } from 'react'
import BookList from './BookList'
const BookDetails = (props) => {
const [bookdetails, setbookDetails] = useState('')
const [desc, setDesc] = useState('')
const titleChangehandler = (e) => {
setbookDetails(e.target.value)
}
const descriptionChangehandler = (e) => {
setDesc(e.target.value)
}
const submitHandler = (e) => {
e.preventDefault()
return (
<div className='bookdetails'>
<form className='form_bookdetails' onSubmit={submitHandler}>
<div>
<label>Enter Title:</label>
<input type='text' value={bookdetails} onChange={titleChangehandler}></input>
</div>
<div>
<label>Enter Description:</label>
<input type='text' value={desc} onChange={descriptionChangehandler}></input>
</div>
<div>
<button type='submit'>Add Details</button>
</div>
</form>
</div>
)
}
}
export default BookDetails
BookList.js
import React from 'react'
import './BookList.css'
import BookDetails from './BookDetails'
const BookList = () => {
return (
<div className="booklist">
<header>BookList</header>
<BookDetails />
</div>
)
}
export default BookList
You need to use props. BookList state will have an update function that it will pass to the BookDetail via props. Example (CodeSandbox) with Todo with title & description.
BookDetail will invoke this method on every save which then would update the original list.
TodoList.js
export default function TodoList() {
const [todo, setTodo] = React.useState(null);
const [todoList, setTodoList] = React.useState([]);
React.useEffect(() => {
getTodos();
}, []);
function getTodos() {
console.log("===> fetch all todos!!");
fetchTodos().then((todos) => {
setTodoList(todos);
});
}
function editTodo(todo) {
console.log("===> set todo => ", todo);
setTodo(todo);
}
function handleUpdate(updatedTodo) {
// update Todo
const updatedTodos = todoList.map((el) =>
el.id === updatedTodo.id ? updatedTodo : el
);
setTodoList(updatedTodos);
setTodo(null);
}
return (
<div>
<ul>
{todoList.map((item) => (
<li key={item.id}>
{item.title}, {item.description}
<button onClick={() => editTodo(item)}>edit</button>
</li>
))}
</ul>
{todo && <TodoDetail todo={todo} updateTodo={handleUpdate} />}
</div>
);
}
TodoDetail.js
import React from "react";
export default function TodoDetail(props) {
const [todo, setTodo] = React.useState(props.todo);
console.log("todo =>", todo);
function handleChange(key, value) {
console.log("===> todo changed!");
setTodo({
...todo,
[key]: value
});
}
function handleSubmit() {
// api PUT on todo
console.log("===> todo edit submit!!");
props.updateTodo(todo);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="title">
<input
value={todo.title}
onChange={(e) => handleChange("title", e.target.value)}
/>
<input
value={todo.description}
onChange={(e) => handleChange("description", e.target.value)}
/>
</label>
<button type="submit">submit</button>
</form>
</div>
);
}
You can store the list of books in your BookList component like
const [bookList, setBookList] = useState([])
This way your BookList component has access to the books. You can then create a function to add books to the list
function addBook(book) {
setBookList([...bookList, book])
}
Then pass the addBook() function to the BookDetails component to use it on submit.
<BookDetails addBook={addBook}
Now BookDetails can access the function as a prop
props.addBook("pass new book here")

onChange setState is rerendering all components?

I have a webpage with multiple forms. Here's a bare minimum example of the structure:
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<FormA form={formA} />
<FormB form={formB} />
</>
);
}
function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
}
function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
}
I think this should be the encapsulated logic of my form page. The problem is that when the onChange event is called for a field of any form, all forms get re-rendered. I assumed that setState should re-render only the components with the affected dependency change. Am I missing something?
Any state change in Example component will trigger re render to its child components (FormA, FormB). If you want to avoid that. wrap FormA and FormB in React.memo. That way you can prevent unwanted re-renders
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<MemFormA form={formA} />
<MemFormB form={formB} />
</>
);
}
const MemFormA = React.memo(function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
})
const MemFormB = React.memo(function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
})
Anytime state of <Example /> is updated, <Example /> re-renders, which in turn also re-renders <FormA /> and <FormB />. This is expected.
You should look into using React.memo() for FormA and FormB if you want them to only re-render when the props passed to them is changed.

Dynamically add component in react with hooks

i have 3 components: Form (parent), Picklist and ParagraphBox (children); based on the select of the picklist, i render ParagraphBox and also a "+" button. What i would like to achieve is on the click of the plus button, render another ParagraphBox, just under the first. I would also like the remove functionality.
My ParagraphBox component has a title and a content, and i want to give the adding a progressive number:
e.g Paragraph 1
Content: ....
Paragraph 2
Content: ....
And so on
Here's my ParagraphBox component:
import React, { useState, useEffect } from 'react';
export default function ParagraphBox(props) {
const [paragrafo, setParagrafo] = useState({})
useEffect(() => {
console.log('paragrafo ', paragrafo)
props.onChange(paragrafo)
}, [paragrafo])
const onChange = (e) => {
const titolo = e.target.name
const contenuto = e.target.value
setParagrafo({
...paragrafo,
[titolo]: contenuto
})
}
return (
<div className = "paragraph-box">
<label>
{props.labelInputBox}
<div>
<input type="text" name="titolo" value={paragrafo.titolo || ''} onChange={onChange}/>
</div>
{props.labelTextArea}
<div>
<textarea id="subject" name="contenuto" placeholder="Inserisci contenuto.." style={{height: "45x", width: "400px"}} value={paragrafo.contenuto || ''} onChange={onChange} />
</div>
</label>
</div>
)
}
Here is my Form component:
import React, { useState, useEffect, useRef } from 'react';
import './Form.css'
import createDocument from '../pdfTool';
import finalita from '../icons/finalita.PNG';
import Picklist from './Picklist.js';
import ParagraphBox from './ParagraphBox';
export default function Form() {
const [flagImg, setFlagImg] = useState(false)
const [flagPar, setFlagPar] = useState(false)
const [paragArray, setParagArray] = useState([
{titolo: '', contenuto: ''}
])
const handleChange = (e) => {
console.log('e ', e)
console.log('e.titolo PARENT ', e.titolo)
console.log('e.contenuto PARENT ', e.contenuto)
setParagArray({
...paragArray,
[e.titolo]: e.contenuto
})
}
useEffect(() => {
console.log('rendering useEffect')
console.log('flagPar: ', flagPar)
console.log('flagImg: ', flagImg)
console.log('paragArray ', paragArray)
}, [flagPar, flagImg, paragArray])
const handleSubmit = (evt) => {
evt.preventDefault(); //usato per evitrare il refresh del browser
}
const addParag = (parag) => {
console.log('paragArray PARENT ', paragArray)
}
const onSelect = (selectedValue) => {
console.log('valore selezionato nella picklist: ' + selectedValue)
if(selectedValue === 'Layout 2') {
setFlagImg(true)
setFlagPar(true)
}
}
return(
<div>
<Picklist onSelect={onSelect} label="Seleziona un layout di contratto: " pickVals={["Seleziona...", "Layout 1", "Layout 2", "Layout 3"]}/>
{flagImg ? (
<form onSubmit={handleSubmit}>
<Picklist onSelect={onSelect} label="Seleziona Immagine: " pickVals={["Seleziona...", "Immagine 1", "Immagine 2", "Immagine 3"]} />
</form>
) : null}
{flagPar ? (
<div>
<ParagraphBox labelInputBox="Paragfrafo 1" labelTextArea="Contenuto Paragrafo" onChange={handleChange}/>
<div id = "add-paragraph">
<button type="button" onClick={addParag}>+</button>
<input type="submit" value="Submit" />
</div>
</div>
) : null}
</div>
)
Thanks in advance for your time
I know this is old...but I just faced the same issue, so here it goes: JSX is just syntactic sugar for regular JavaScript. Therefore you can just create the component manually and make it available as part of your hook, i.e.:
custom hook:
import React, { useState } from 'react';
import Advise from '../../components/Advise/Advise';
const useAdvise = () => {
const [ showAdvise, setShowAdvise ] = useState(false)
const [ adviseMsg, setAdviseMsg ] = useState('')
const [ loading, setLoading ] = useState(false)
const hideAdvise = () => {
setShowAdvise(false)
}
const adviseComponent = React.createElement(Advise, {show:showAdvise, showSpinner:loading, hideAdvise:hideAdvise, children:adviseMsg})
return {
adviseComponent,
setShowAdvise,
setAdviseMsg,
setLoading
}
};
export default useAdvise;
component where I want it:
import useAdvise from '../hooks/useAdvise/useAdvise'
const Page = () => {
const {adviseComponent, setShowAdvise, setAdviseMsg, setLoading} = useAdvise()
return(
<div>
{adviseComponent}
</div>
)
}
hope it helps (cheers from Br as well)

How do I get the text value of an input using only function components in React?

import Layout from "components/Layout"
import { useState } from "react";
export async function getServerSideProps(context) {
const res = await fetch(`${process.env.NEXT_API_URL}/kana-terms/all`)
const data = await res.json()
return {props: {data}}
}
function checkAnswer(event) {
if (event.key === "Enter") {
console.log("Enter key was pressed");
}
}
export default function Hiragana(props) {
const [remainingTerms, setRemainingTerms] = useState(props.data);
return (
<Layout>
<h1>Hiragana</h1>
<div className="bg-light border w-100">
<h2>{remainingTerms[0].hiraganaText}</h2>
<input type="text" onKeyUp={(event) => {checkAnswer(event, )}} />
</div>
</Layout>
)
}
I want to pass the text value of the <input> element to the checkAnswer() function.
How do I do that in React using only function components?
All the answers I can find through Google use class components.
I'm also using Next.js... if that matters.
Put the input value into state, then pass the stateful value into the checkAnswer call:
const [value, setValue] = useState('');
and
<input
type="text"
value={value}
onChange={e => { setValue(e.currentTarget.value); }}
onKeyUp={(event) => {checkAnswer(event, value)}}
/>

Pass a variable in a functional component to a class component

I've a ToysPage.js with a class component and a SearchFeature.js with a functional component (child of ToysPage).
I made a searchBar in SearchFeature.js with hooks and with console.log I can see that it works. But how can I pass the const filteredToys to the state toysFiltered in ToysPage.js?
ToysPage.js
import React, { Component } from "react";
export default class ToysPage extends Component {
state = {
toys: undefined,
toysFiltered: undefined,
};
//Here I call all the toys through Axios and insert them in toys and toysFiltered
return (
<>
<div className="container">
<div className="row">
<SearchSide
toysFiltered={this.state.toysFiltered}/>
<ToysSide toys={this.state.toysFiltered} />
</div>
</div>
</>
);
}
}
function SearchSide({ toysFiltered }) {
return (
<>
<div className="col-3">
<SearchFeature toysFiltered={toysFiltered} />
</div>
</>
);
}
SearchFeature.js
import React, { useState, useMemo } from "react";
export default function SearchFeature({ toysFiltered }) {
const [query, setQuery] = useState("");
const [filteredToys, setFilteredToys] = useState(toysFiltered);
useMemo(() => {
const result = toysFiltered.filter((toy) => {
return toy.titulo.toLowerCase().includes(query.toLowerCase());
});
setFilteredToys(result);
}, [toysFiltered, query]);
return (
<div className="form-group">
<label>Search a Toy</label>
<input
type="text"
className="form-control"
placeholder="Search"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
</div>
);
}
You have some state in a child component, SearchFeature, that you would like in the parent component ToysPage. The usual pattern for this is to lift the state out of SearchFeature, which you have partially done.
You're almost doing this correctly. You pass a list of filtered toys into SearchFeature, but that list is only being used as a default value for a duplicate state variable in SearchFeature, and changes don't flow up to the parent:
// filteredToys has the real state. toysFiltered is just a default value.
const [filteredToys, setFilteredToys] = useState(toysFiltered);
Instead, leave the state variable in the parent, and send changes up from the search component.
Also, useMemo shouldn't have side effects. I've changed it to useEffect to be safe. There is a way to do this with useMemo, but that's out of the scope of the question.
in ToysPage.js
Add a setter function:
<SearchSide
toysFiltered={this.state.toysFiltered}
setToysFiltered={
newFilterList => this.setState({...this.state, toysFiltered: newFilterList})}
/>
Pass it through from SearchSide into SearchFeature:
function SearchSide({ toysFiltered, setToysFiltered }) {
return (
<>
<div className="col-3">
<SearchFeature toysFiltered={toysFiltered} setToysFiltered={setToysFiltered} />
</div>
</>
);
}
SearchFeature.js
export default function SearchFeature({ toysFiltered, setToysFiltered }) {
const [query, setQuery] = useState("");
useEffect(() => {
const result = toysFiltered.filter((toy) => {
return toy.titulo.toLowerCase().includes(query.toLowerCase());
});
setToysFiltered(result);
}, [setToysFiltered, query]);
// ...
}

Resources