Passing down function from parent to child through React functional component - javascript

I am trying to pass down the functions setDir and handleSort() down from SearchAppointments (parent) to Hooks(child), but I keep getting errors saying that they are not functions.
I tried to debug it by looking the typeof handleSort in the useEffecthook of the child component, though it console.logged two statements: 1) underfined 2) function. Not sure what is wrong.
const SearchAppointments = React.memo(() => {
const [orderDir, setDir] = useState("");
const handleSort = (e) => {
let value = e.target.value;
setDir(value);
let order;
let filterData = data;
if (orderDir === 'asc') {
order = 1;
} else {
order = -1;
}
};
return (
<>
<div>
<Hooks handleSort={handleSort} setDir={setDir} />
</div>
</>
);
});
const Hooks = React.memo(({ handleSort, setDir }) => {
useEffect(() => {
console.log(typeof handleSort);
}, []);
return (
<div>
<div>
<button type="button" onClick={() => setDir("success")}>
Set Dir
</button>
<button type="button" value='asc' onClick={handleSort}>
Handle Sort (Asc)
</button>
<button type="button" value='dsc' onClick={handleSort}>
Handle Sort (Dsc)
</button>
</div>
</div>
);
});

try and use memo like this:
const comparator = (previous, next) => {
if (something) {
return true
}
return false
}
const Hooks = React.memo(({ handleSort, setDir }) => {
useEffect(() => {
console.log(typeof handleSort);
}, []);
return (
<div>
<div>
<button type="button" onClick={() => setDir("success")}>
Set Dir
</button>
<button type="button" value='asc' onClick={handleSort}>
Handle Sort (Asc)
</button>
<button type="button" value='dsc' onClick={handleSort}>
Handle Sort (Dsc)
</button>
</div>
</div>
);
}, comparator);
define a function and pass that as the second argument. memo function (I've called it comparator, then returns true or false depending on when you want the component to update. think of it as the new shouldComponentUpdate in the lifecycle methods. but use with care as your application might not update if you use carelessly

Related

How to apply styling to a specific item in an array

I'm trying to make a simple todo in react. I want to be able to click in the button next to the todo text and mark it as complete, with a line passing through it, so I guess the point of the button would be to toggle between the two stylings. But I don't know how to apply the styling to that specific todo. Here's my code so far:
import React, { useState } from 'react';
function App() {
const [todos, setTodos] = useState([])
const toggleComplete = (i) => {
setTodos(todos.map((todo, k) => k === i ? {
...todo, complete: !todo.complete
} : todo))
}
const handleSubmit = (event) => {
event.preventDefault()
const todo = event.target[0].value
setTodos((prevTodos) => {
return [...prevTodos, {
userTodo: todo, completed: false, id: Math.random().toString()
}]
})
}
return (
<div>
<form onSubmit={handleSubmit}>
<input placeholder='name'></input>
<button type='submit'>submit</button>
</form>
<ul>
{todos.map((todos) => <li key={todos.id}>
<h4>{
todos.completed ? <s><h4>{todos.userTodo}</h4></s> : <h4>{todos.userTodo}</h4>}
</h4>
<button onClick={toggleComplete}>Mark as complete</button>
</li>)}
</ul>
</div>
);
}
export default App;
You can see that the toggleComplete function takes a parameter i which is the id of the todo, so you should call it like onClick={() => toggleComplete(todos.id)}.
However this still didn't work since you are assigning random numbers as strings as id to the todos then iterating over the array.
As Alex pointed out, there's a bug in your code regarding the completed toggle, so I fixed it and here's a working version of the code you can take a look at and improve:
import React, { useState } from "react";
export default function App() {
const [todos, setTodos] = useState([]);
const toggleComplete = (i) => {
setTodos(
todos.map((todo, k) => {
return k === i
? {
...todo,
completed: !todo.completed
}
: todo;
})
);
};
const handleSubmit = (event) => {
event.preventDefault();
const todo = event.target[0].value;
setTodos((prevTodos) => {
return [
...prevTodos,
{
userTodo: todo,
completed: false,
id: prevTodos.length
}
];
});
};
return (
<div>
<form onSubmit={handleSubmit}>
<input placeholder="name"></input>
<button type="submit">submit</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.completed ? (
<s>
<p>{todo.userTodo}</p>
</s>
) : (
<p>{todo.userTodo}</p>
)}
<button onClick={() => toggleComplete(todo.id)}>
Mark as complete
</button>
</li>
))}
</ul>
</div>
);
}
There are 2 problems in your code as i see:
typo in the toggleComplete function
Fix: the following code complete: !todo.complete shopuld be completed: !todo.completed as this is the name of the key that you're setting below on handleSubmit.
the toggleComplete function receives as an argument the javascript event object and you are comparing it with the key here:
(todo, k) => k === i
(see more here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event)
Fix: You can modify the lines of code for the todo render as follows:
{todos.map((todo, index) => <li key={todo.id}>
<React.Fragment>{
todo.completed ? <del><h4>{todo.userTodo}</h4></del> : <h4>{todo.userTodo}</h4>}
</React.Fragment>
<button onClick={() => {toggleComplete(index)}}>Mark as complete</button>
</li>)}

Callback set in the state variable of Context Provider will be undefined

I have referred other questions asked here by OPs but none seems to work for me. I have one layout and in that layout there is one toolbar which I am using to submit forms. Now to make that happen I using one FormProvider which is a Context.Provider (wraps layout component) with a state variable which stores function callback to submit a form. Now in the form component (which is loaded conditionally) I am using this setState func from context to assign form submit callback and in the toolbar using state variable from context to call that as a function. The problem I am facing is I always get state variable from context undefined. These are the snippets from my code.
FormProvider
type FormContextProps = {
setOnSubmit: (callable: Function | undefined) => void
assignOnSubmit: Dispatch<any>
setOnCancel: (callable: Function | undefined) => void
submit: (e: any) => void
cancel: () => void
}
const initAuthContextPropsState = {
setOnSubmit: () => { },
assignOnSubmit: () => { },
setOnCancel: () => { },
submit: (e: any) => { },
cancel: () => { },
}
const FormContext = createContext<FormContextProps>(initAuthContextPropsState)
const useTForm = () => {
return useContext(FormContext)
}
const FormProvider: FC = ({ children }) => {
const [onSubmit, assignOnSubmit] = useState<Function | undefined>()
const [onCancel, assignOnCancel] = useState<Function | undefined>()
const setOnSubmit = (callable: Function | undefined) => {
console.log('setOnSubmit', callable)
assignOnSubmit(callable)
console.log('setOnSubmit after', onSubmit)
}
const setOnCancel = (callable: Function | undefined) => {
assignOnCancel(callable)
}
useEffect(() => {
console.log("changed onSubmit"); // this hook is called only on first render
}, [onSubmit])
const submit = (e: any) => {
console.log('form submited', onSubmit) // this is always undefined when I click on save button on toolbar
if (onSubmit) onSubmit(e)
}
const cancel = () => {
if (onCancel) onCancel()
}
return (
<FormContext.Provider value={{ setOnSubmit, assignOnSubmit, setOnCancel, submit, cancel }}>
{children}
</FormContext.Provider>
)
}
Toolbar
const FormToolbar: FC = () => {
const {classes} = useLayout()
const {submit, cancel} = useTForm()
const submitForm = (e: any) => submit(e)
return (
<div className='toolbar' id='kt_toolbar'>
{/* begin::Container */}
<div
id='kt_toolbar_container'
className={clsx(classes.toolbarContainer.join(' '), 'd-flex flex-stack')}
>
<DefaultTitle />
{/* begin::Actions */}
<div className='py-1 d-flex align-items-center'>
{/* begin::Button */}
<button
className='btn btn-sm btn-primary me-4'
onClick={submitForm}
>
Save
</button>
<button
className='btn btn-sm btn-primary'
onClick={cancel}
>
Cancel
</button>
{/* end::Button */}
</div>
{/* end::Actions */}
</div>
{/* end::Container */}
</div>
)
}
EditForm.tsx
const EditForm: React.FC<Props> = () = {
const form = useRef() as React.MutableRefObject<HTMLFormElement>
const { setOnSubmit, assignOnSubmit } = useTForm()
useLayoutEffect(() => {
setOnSubmit(() => { form?.current.dispatchEvent(new Event('submit')) });
console.log('Form changed', form)
}, [form])
return (
<form onSubmit={formik.handleSubmit} ref={form}>
// ...
</form>
);
}
Main Component
function StaffManagement({ user, selectedLanguageId, idMenu }: Props) {
const [editing, setEditing]: [any, Function] = useState(null)
return (
<div className='row'>
<div className="col-lg-4">
<ModuleItemList
className='card-xxl-stretch mb-xl-3'
edit={setEditing}
/>
</div>
<div className="col-lg-8">
{editing && <EditForm
userId={user.id}
menuId={idMenu}
/>}
</div>
</div>
)
}
When using setState, we can do it in two ways,
setState(newState); // directly pass the new state
setState((currentState) => newState); // return the new state from a callback fn
That mean setState can accept a plain state value or a callback function which will return the new state that need to be set as the new state.
When you say,
const setOnSubmit = (callable: Function | undefined) => {
// callable => () => { form?.current.dispatchEvent(new Event('submit')) }
assignOnSubmit(callable)
}
Here React thinks you used the setState(assignOnSubmit) in the 2nd way I mentioned above, so react will call your callback and execute the form?.current.dispatchEvent(new Event('submit')). Since your callable function returns nothing, undefined will assigned to your onSubmit state.
So if you really need to store this function in a state, you have to do it as,
const setOnSubmit = (callable: Function | undefined) => {
assignOnSubmit(() => callable) // React will call the callback and return callable
}
Few Other Tips
Also do not use useLayoutEffect for this task. You can use useEffect and imrpove the performance of your application.
memoize the provider data, otherwise you will trigger unwanted re renders.
const data = useMemo(() => ({
setOnSubmit, assignOnSubmit, setOnCancel, submit, cancel
}), [submit, cancel, setOnCancel, assignOnSubmit, setOnSubmit])
return (
<FormContext.Provider value={data}>
{children}
</FormContext.Provider>
)
State updates are asynchronous. So you can't expect to console.log log the latest state
const setOnSubmit = (callable: Function | undefined) => {
console.log('setOnSubmit', callable)
assignOnSubmit(() => callable)
console.log('setOnSubmit after', onSubmit) // this won't log the latest as assignOnSubmit is async
}
you need to make <FormProvider as a parent of your Toolbar and EditForm to make your context working properly. Based on your code I don't see where you put the <FormProvider, so I'm guessing that you need to put it on your Main Component
Main Component
function StaffManagement({ user, selectedLanguageId, idMenu }: Props) {
const [editing, setEditing]: [any, Function] = useState(null)
return (
<FormProvider>
<div className='row'>
<div className="col-lg-4">
<ModuleItemList
className='card-xxl-stretch mb-xl-3'
edit={setEditing}
/>
</div>
<div className="col-lg-8">
{editing && <EditForm
userId={user.id}
menuId={idMenu}
/>}
</div>
</div>
</FormProvider>
)

How to pass variable in a function call?

In my functional component I'm calling two functions, which are doing nearly the same thing:
const App = () => {
const handleOnClickFirst = () => {
setValue('first')
}
const handleOnClickSecond = () => {
setValue('second')
}
return (
<div>
{anything === true
? <Button onClick={handleOnClickFirst} />
: <Button onClick={handleOnClickSecond} />}
</div>
)
}
So there should be simply only
const handleOnClick = (value) => {
setValue(value)
}
But how do I pass the value in onClick?
As we know with JSX you pass a function as the event handler. So we can wrap our handler with another function and call handler with arguments in this wrapper function
onClick={(event) => handleOnClick(value)}
or for old versions we can do
onClick={function(event){ handleOnClick(value) }}
If you don't need events you can just pass it like this
onClick={() => handleOnClick(value)}
Also if it is a Class base component we use bind to pass method context and arguments
class App extends React.Component {
handleOnClick(val) {
console.log(`${val}`);
}
render() {
return (
<button onClick={this.handleOnClick.bind(this, "test")}>
click me
</button>
);
}
}
You could do
onClick={() => handleOnClick(value)}
edit:
More information here: https://reactjs.org/docs/faq-functions.html
You can do this :
<button onClick={(event)=>handleOnClick(<pass your paramter here>)}></button>
Try this:
const App = () => {
const handleOnClick = (passedInValue) => {
setValue(passedInValue)
}
return (
<div>
{anything === true
? <Button onClick={() => handleOnClick("first")} />
: <Button onClick={() => handleOnClick("second")} />}
</div>
)
}

React, handle modal from component

How can I catch the click on some buttom from a modal, to return true or false to the component that is calling the modal?
handleSubmitSaveConfigurations = async (row) => {
const { scadaValidation } = this.props;
const result = await scadaValidation(11);
if (result.statusCode !== 200) {
// Opens the modal to ask if you really want to save
this.setState({openSimpleModal: true});
this.setState({contentSimpleModal: this.warningModal()});
// Here I have to catch if the modal click yes or no.
// In case yes, do nothing and continue with the code
// But in case "no" returns false and stops
}
// If result.statusCode === 200 returns true
return true;
}
warningModal = () => (
<div>
Do you want to save?
<Button id="btnClose" onClick={() => this.handleModalClickClose()}>No</Button>
<Button id="btnSave" onClick={() => this.handleModalClickClose()}>Yes</Button>
</div>
);
handleModalClickClose = () => this.setState({ openSimpleModal: false });
You could pass a handler to be executed inside your modal.
const Modal = ({ callback }) =>{
const handleClick = arg => callback(arg)
return(
<div>
<button onClick={() => handleClick('button1')}>A</button>
<button onClick={() => handleClick('button2')}> B</button>
</div>
)
}
And expect to receive this value inside the component which is calling Modal
const TheOneWhoCalls = () =>{
const onModalClick = arg => console.log(arg)
return <Modal callback={onModalClick} />
}
You can create a function on the parent component, and inside the modal, u only use it.
https://reactjs.org/docs/lifting-state-up.html#lifting-state-up
Parent:
constructor () {
this.state: {test: false}
}
setStateTest (value) {
this.setState(value)
}
render () {
return <modal handlerSetParentStateTest = {setStateTest}></modal>
}
Modal:
// this will set the parent state
this.props.handlerSetParentStateTest(true);
I want to share my solution, for sure I will need it in the future. it the implementation of #Dupocas
const Modal = ({ callback }) => {
const handleClick = arg => callback(arg)
return (
<div>
Wanna save?
<Button id="btnCloseModal" onClick={() => handleClick(0)}>No</Button>
<Button id="btnGuardarConfirm" onClick={() => handleClick(1)}>Sí</Button>
</div>)
};
class TableDisplayReportRecord extends Component<Props, State> {
constructor {...}
handleValidate = async (row) => {
const { scadaValidation } = this.props;
const verify = await scadaValidation();
if (verify.statusCode !== 200) {
this.setState({openSimpleModal: true});
const onModalClick = arg => {
this.setState({openSimpleModal: false});
//do nothing
if (arg === 0) return false;
//if user selected "Yes", call the function that I need
else this.handleSubmitSave(row);
};
this.setState({contentSimpleModal:
<Modal
callback={onModalClick}
/>
})
}
}
handleSubmitSave = async (row) => {...}
...
}

onClick function for array of buttons not working

Here the List get's 'n' values from the app.component. I am trying to write onclick function for each of the buttton. Here, is the code
onChildButtonClick = (val) => {
console.log(val);}
function makeButton(data) {
return (
<button onClick = {this.onChildButtonClick.bind(null, data.name)} >
{data.name} </button>
);}
const List = (props) => {
const {projectnames} = props
return (
<tr>
<div> {
projectnames.map(makeButton, this)
}
</div>
</tr>
)
}
When I try to execute it throws me an error onChildButtonClick is not defined.
As your code stands above, you're referencing this.onChildButtonClick which is looking within the scope of the function. But your function onChildButtonClick is outside of that scope.
Edit:
I haven't had a chance to test it, but try this out:
function makeButton(data) {
const onChildButtonClick = (val) => {
console.log(val);
}
return (
<button onClick = {onChildButtonClick.bind(this, data.name)} >
{data.name} </button>
);
}
const List = (props) => {
const {projectnames} = props
return (
<tr>
<div> {
projectnames.map(makeButton, this)
}
</div>
</tr>
)
}

Categories

Resources