How to pass function as props from functional parent component to child - reactjs

Parent Component:
const initialValue_modalProps = [
{ show: false, response: "" }
];
const [modalProps, setModalProps] = useState(initialValue_modalProps)
const passedFunction = () => {
setModalProps(modalProps => initialValue_modalProps);
}
..
..
<div>
<Modal show={modalProps.show}
response={modalProps.response}
passedFunction={passedFunction}></Modal>
</div>
Child Component:
export default function ModalComp(props) {
const [modalOpen, setmodalOpen] = useState(true);
console.log('modalOpen', modalOpen);
if (props.show === false || modalOpen === false) {
return null;
}
return (<Modal isOpen={props.show}>
<ModalHeader>Deployment Status</ModalHeader>
<ModalBody>{props.response}</ModalBody>
<ModalFooter>
<Button onClick={() => {
setmodalOpen(modalOpen => false);
props.passedFunction();
}}>Close</Button>
</ModalFooter>
</Modal>)
}
Here I want to passedFunction function from Parent to child so that the Child component can execute it to reset the state in parent

You can take this as an reference with live example demo https://codesandbox.io/s/modal-6fvyx
function App() {
const [status, setState] = React.useState(false);
const [text, setText] = React.useState("");
const handleClick = () => {
setState(prevStatus => !prevStatus);
};
const handleChange = e => {
setText(e.target.value);
};
return (
<>
<button onClick={handleClick}>Open photo entry dialog</button>
<ChildComponent
isOpen={status}
text={text}
handleChange={handleChange}
handleClick={handleClick}
/>
</>
);
}
const ChildComponent = ({ isOpen, text, handleChange, handleClick }) => {
return (
<>
{isOpen && (
<Model
status={isOpen}
handleClick={handleClick}
text={text}
handleChange={handleChange}
/>
)}
</>
);
};

You need to remove the parentheses behind passedFunction, because otherwise you are executing the function first and passing the result to the child afterwards. Pass your function as it is via passedFunction={passedFunction}.
const ParentComponent = () => {
const initialModalProps = { ... };
const [modalProps, setModalProps] = useState(initialModalProps);
const passedFunction = () => {
setModalProps(initialModalProps);
}
return (
<div>
<Modal
show={modalProps.show}
response={modalProps.response}
passedFunction={passedFunction} />
</div>
);
};

Changed the child component to this. and its working
export default function ModalComp(props) {
//const [modalOpen, setmodalOpen] = useState(true);
if (props.show === false) {
return null;
}
return (<Modal isOpen={props.show}>
<ModalHeader>Deployment Status</ModalHeader>
<ModalBody>{props.response}</ModalBody>
<ModalFooter>
<Button onClick={props.passedFunction}>Close</Button>
</ModalFooter>
</Modal>)

Related

React - How to prevent parent re-render on prop change

I am making a calculator using react.
Every time I press a number button, the whole application re-renders, instead of the <Display />.
To prevent it, I tried 2 different approaches for App, But neither of them worked.
Here is the sandbox link.
Any help would be appreciated.
Put clickHandler inside of useCallback()
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = useCallback(
(val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
},
[screen]
);
return (
<div className="App">
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</div>
);
};
Put Display component inside of React.memo
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
const displayComponent = () => {
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
};
const MemoizedComponent = React.memo(displayComponent);
return (
<div className="App">
<MemoizedComponent />
</div>
);
};
And here's the ButtonList & Button component.
export const ButtonList = ({ clickHandler }) => {
const arr = [...Array.from(Array(10).keys()).reverse(), "AC"];
return (
<div className="buttons">
<div className="numbersWrapper">
{arr.map((item) => (
<Button
key={item}
clickHandler={clickHandler}
value={item.toString()}
/>
))}
</div>
</div>
);
};
export const Button = ({ value, clickHandler }) => {
return (
<button
name={value}
onClick={() => {
clickHandler(value); //where the clickEvent happens
}}
>
{value}
</button>
);
};
If you don't want a component re-render,You would have to define the click handler in another component that you would like to re-render.
So do it like this:
const App = () => {
console.log("render");
return (
<div className="App">
<childComponent />
</div>
);
};
export const childComponent = () => {
const [screen, setScreen] = useState("0");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
}
This way you prevent a particular component from re-rendering. But note that if you update a state or do anything from which causes re-renders from the parent component, It would equally re-render the child component.

On click of button not filtering array like intended

Can someone explain to me why the filter method isn't working as intended? I am trying to update my array of objects with filtered results based on the first name.
export default function App() {
const { data } = useDataFetcher();
const searchArray = data;
const [inputField, setInputField] = useState("");
const searchUser = () => {
searchArray.filter((el) => el.firstName.includes(inputField));
};
useEffect(() => {
searchUser();
}, [inputField]);
return (
<div className="App">
<h1>Xaxis Frontend Interview</h1>
<input onChange={(e) => setInputField(e.target.value)} />
<button onClick={(e) => searchUser()}> Search </button>
{searchArray.map((userData, i) => (
<Users
key={i}
firstName={userData.firstName}
lastName={userData.lastName}
email={userData.email}
bio={userData.bio}
/>
))}
</div>
);
}
You need to create a state which will contain the list of the users. Then, you want to update this state when inputField state changes.
export default function App() {
const { data } = useDataFetcher();
const searchArray = data;
const [users, setUsers] = useState([])
const [inputField, setInputField] = useState("");
useEffect(() => {
setUsers(data);
}, [data]);
useEffect(() => {
const filteredArray = users.filter((el) => el.firstName.includes(inputField));
setUsers(filteredArray)
}, [inputField]);
return (
<div className="App">
<h1>Xaxis Frontend Interview</h1>
<input onChange={(e) => setInputField(e.target.value)} />
{users.map((userData, i) => (
<Users
key={i}
firstName={userData.firstName}
lastName={userData.lastName}
email={userData.email}
bio={userData.bio}
/>
))}
</div>
);
}
Or if you want to update the list of users by clicking a button:
export default function App() {
const { data } = useDataFetcher();
const searchArray = data;
const [users, setUsers] = useState([])
const [inputField, setInputField] = useState("");
useEffect(() => {
setUsers(data);
}, [data]);
const searchUser = () => {
const filteredArray = users.filter((el) => el.firstName.includes(inputField));
setUsers(filteredArray)
};
return (
<div className="App">
<h1>Xaxis Frontend Interview</h1>
<input onChange={(e) => setInputField(e.target.value)} />
<button onClick={() => searchUser()}> Search </button>
{users.map((userData, i) => (
<Users
key={i}
firstName={userData.firstName}
lastName={userData.lastName}
email={userData.email}
bio={userData.bio}
/>
))}
</div>
);
}

React hooks set functions not working from child component

I've a isView and setIsView in the ParentComponent and passing them down to the ChildComponent as props and trying to do show/hide conditional rendering but setIsView seems not to be working and isView value in the props remains same.
const ParentComponent = props => {
const [isView, setIsView] = useState(true);
const onChange = selectedOption => {
selectedOption === 'Report'
? setIsView(true)
: setIsView(false);
};
return (
<div>
<ChildComponent
isView={isView}
onChange={onChange}
/>
</div>
);
};
const ChildComponent = props => {
const {isView, onChange} = props;
return (
<div>
<RadioButton
onChange={() => onChange('Not-Report')}
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
Edit: changed onChange={onChange('Not-Report')} to onChange={() => onChange('Not-Report')} as suggested by some. still not working.
Try feeding the onChange method as a callback function instead.
const ChildComponent = props => {
const {isView, onChange} = props;
return (
<div>
<RadioButton
onChange={() => onChange('Not-Report')} // <- Here
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
Update child component onChange function as follows:
<RadioButton
onChange={() => onChange('Not-Report')}
/>
If you pass onChange only, it will be regarded with the function that has event as a parameter rather than the prop's onChange function.
To make it work like your way,
const ChildComponent = ({isView, onChange}) => {
const onRadioChange = () => {
onChange('Not-Report')}
}
return (
<div>
<RadioButton
onChange={onRadioChange}
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
i am wrote this code
ParentComponent
const ParentComponent = (props) => {
const [isView, setIsView] = useState(true);
const onChange = (selectedOption) => {
console.log("selectedOption = ", selectedOption);
selectedOption === "Report" ? setIsView(true) : setIsView(false);
};
return (
<div>
<ChildComponent isView={isView} onChange={onChange} />
</div>
);
};
ChildComponent
const ChildComponent = (props) => {
const { isView, onChange } = props;
return (
<div>
<input
type="radio"
checked={isView}
onClick={() => {
onChange("Not-Report");
}}
/>
isView = {isView ? "true" : "false"}
</div>
);
};
i change onChange to onClick and use checked
Work Demo

How can I edit a todo in my react app using hooks?

I'm trying to figure out how to edit a todo item in my react app using hooks, but I can't seem to figure out how to write the code.
Most of the solutions I've seen online are using class components and it's not written with the same logic as my app.
Here is my current code
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = todo => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = id => {
const removedArr = [...todos].filter(todoId => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = id => {
let updatedTodos = todos.map(todo => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = e => {
setTodos(e.target.value);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{todos.map(todo => (
<div>
<div
key={todo.id}
className={todo.isComplete ? 'complete' : ''}
key={todo.id}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</div>
))}
</>
);
}
Here is the code from the other component
function TodoForm(props) {
const [input, setInput] = useState('');
const handleChange = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 10000),
text: input,
complete: false
});
setInput('');
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder='todo...'
value={input}
onChange={handleChange}
name='text'
/>
<button onClick={handleSubmit}>add todo</button>
</form>
);
}
So right now everything works where I can add todos and delete todos + cross out todos. Only thing missing is being able to edit them.
I saw some suggestions about updating the text value with an input form, but I'm not too sure how I'd implement that in my editTodo function.
Similar to your removeTodo handler, you want to pass the todo.id to completeTodo.
<div className={todo.isComplete ? "complete" : ""} key={todo.id} onClick={() => completeTodo(todo.id)}>
Then you would update a bool value in the todo object.
const completeTodo = (id) => {
let updatedTodos = todos.map(todo => {
if(todo.id === id){
todo.isComplete = true
}
return todo
})
setTodos(updatedTodos)
};
Edit: add styling strikethrough
You'll then conditionally add a css style based on isComplete boolean
CSS
.complete {
text-decoration: line-through;
}
To be able to click on the Remove button, place it outside the todo div in your map function.
{todos.map((todo, isComplete) => (
<>
<div
key={todo.id}
onClick={completeTodo}
className={isComplete ? 'complete' : ''}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</>
))}
As discussion with you in another question here it is:
TodoList.js
import React, { useState } from "react";
import TodoForm from "./TodoForm";
import Todo from "./Todo";
function TodoList({ onClick }) {
const [todos, setTodos] = useState([]);
//Track is edit clicked or not
const [editId, setEdit] = useState(false);
//Save input value in input box
const [inputValue, setInputValue] = useState("");
const handleEditChange = (id, text) => {
setEdit(id);
setInputValue(text);
};
const addTodo = (todo) => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = (id) => {
const removedArr = [...todos].filter((todoId) => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = (id) => {
let updatedTodos = todos.map((todo) => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = (id, text) => {
let editTodos = todos.map((todo) => {
if (todo.id === id) {
todo.text = text;
}
return todo;
});
setTodos(editTodos);
setEdit(false);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{/* I want to move this code below into a new component called Todo.js */}
<Todo
todos={todos}
completeTodo={completeTodo}
removeTodo={removeTodo}
editTodo={editTodo}
handleEditChange={handleEditChange}
editId={editId}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</>
);
}
export default TodoList;
Todo.js
// I want to move this code into this component
import React, { useState } from "react";
import { FaWindowClose, FaRegEdit } from "react-icons/fa";
const Todo = ({
todos,
completeTodo,
removeTodo,
editTodo,
editId,
handleEditChange,
inputValue,
setInputValue
}) => {
return todos.map((todo) => (
<div className="todo-row">
{editId === todo.id ? (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
) : (
<div
key={todo.id}
className={todo.isComplete ? "complete" : ""}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
)}
{editId === todo.id ? (
<button onClick={() => editTodo(todo.id, inputValue)}>Edit todo</button>
) : (
<>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
<FaRegEdit onClick={() => handleEditChange(todo.id, todo.text)} />
</>
)}
</div>
));
};
export default Todo;
Make sure you read and understand code first. Logic is pretty simple what you do in completeTodo. You just need to update text part. Tricky part is to open in input. So logic is like track if user click on id set that id. And check if id is there open input with that id value other wise normal one.
Here is demo of this POC: https://codesandbox.io/s/nostalgic-silence-idm21?file=/src/Todo.js:0-1059

React Hooks Prevent Button to re-render when typing in field

Editing my question to make it a bit clearer
I don’t want the button to re-rendering when I type in the field and when I click on the button I want to update a state object
Here I have 2 components
const mainState = {
title: '',
};
const ButtonComponent = ({ confirmTitleName }) => {
return (
<>
<TestReRender label={'Button Container'}/>
<button style={{backgroundColor: 'red', outline: 'none'}} onClick={() => confirmTitleName('confirmTitleName >>>')}>CLICK ME</button>
</>
)
};
const InputComponent = ({ state, setState }) => {
return (
<>
<TestReRender label={'Input Container'}/>
<input
type="text"
value={state}
onChange={(e) => setState(e.target.value)}
/>
</>
)
};
Then I have created a component made up of the previous two
const InputAndButtonComponent = memo(({ confirmTitleName }) => {
const [state, setState] = useState('');
const Btn = () => <ButtonComponent confirmTitleName={() => confirmTitleName(state)}/>;
return (
<>
<InputComponent state={state} setState={setState} />
<Btn />
</>
)
});
The last component InputAndButtonComponent is then imported in the Main component
const Main = () => {
const [confirmTitle, setConfirmTitle] = useState(mainState);
const confirmTitleName = useCallback((value) => {
setConfirmTitle((prevState) => (
{
...prevState,
title: value
}
))
}, []);
return (
<main className={styles.CreateWorkoutContainer}>
<>
<TestReRender label={'Main Container'}/>
<div>
<InputAndButtonComponent confirmTitleName={confirmTitleName} />
</div>
</>
</main>
)
};
Now the problem is that when I write the component InputAndButtonComponent as follow it re-renders when I type in the input field
const InputAndButtonComponent = memo(({ confirmTitleName }) => {
const [state, setState] = useState('');
return (
<>
<InputComponent state={state} setState={setState} />
// This re-renders when typing
<ButtonComponent confirmTitleName={() => confirmTitleName(state)}/>;
</>
)
});
But the original version does not re-render when I type in the field
const InputAndButtonComponent = memo(({ confirmTitleName }) => {
const [state, setState] = useState('');
// This makes the <Btn /> below not re-rendering. I don't understand why
const Btn = () => <ButtonComponent confirmTitleName={() => confirmTitleName(state)}/>;
return (
<>
<InputComponent state={state} setState={setState} />
// This does not re-render
<Btn />
</>
)
});

Resources