This is a follow up question to this question:
Call child method from parent
I am using React > 16.8 with function components and hooks.
I got a parent component which manages a component to display items and add a new item. The list of items is at the parent component. The way the items are added are by a "+" button which opens a new modal window (which is a component of its own), and inside there's a form that the user can insert the details of the new items.
const registerFormRef = useRef();
<Modal
isOpen={isFormOpen}
onCancel={() => setIsFormOpen(false)}
onSubmit={() => { registerFormRef.current.onSubmitForm(); setIsFormOpen(false) }}
titleText="Register Tenant">
<AddItem onAddItem={AddNewItem} ref={registerFormRef}></RegisterTenant>
</Modal>
The AddNewItem is a callback which adds the new item to the list. The modal has an "OK" button which serves as a submit button. It belongs to the parent modal component, not the AddItem child.
The method in the child component:
useImperativeHandle(ref, () => (
{
onSubmitForm()
{
setIsLoading(true);
const newItem = {
name: formSettings["name"].value,
description: formSettings["description"].value,
area: "",
category: ""
}
props.onAddItem(newItem);
setIsLoading(false);
}
}));
I had an issue of getting the information from the child component which holds the form to the parent component, since the submit button as I said, belongs to the modal, I had to somehow call the callback from inside the child form. I have used the accepted answer in the linked question above. It works, but the comment says it's not a good practice passing information like that. Is there another way of passing the information from the child form to the parent component?
The correct way is to store the form data in the parent i.e the component rendering the modal. To do that you could define a state and provide an onChange handler to it. Once you do that on any change in input the AddItem component must notify its parent by calling the onChange method
const App = () => {
const [data, setData] = useState();
const handleChange=(newData) => {
setData(newData);
}
const onSubmit = () => {
// use data to do whatever you want with the formData
console.log(data);
setIsFormOpen(false)
}
...
return (
<Modal
isOpen={isFormOpen}
onCancel={() => setIsFormOpen(false)}
onSubmit={onSubmit}
titleText="Register Tenant">
<AddItem onAddItem={AddNewItem} handleChange={handleChange} ref={registerFormRef}></RegisterTenant>
</Modal>
)
}
Related
I am constructing some node objects in a function(prepareNodes) to pass to React Flow within a functional component A (lets say), and I have defined a custom node component(CardNode) stateless, which has a button. On button click it should trigger the function(prepareNodes) defined within Component A.
function ComponentA = ({ selectedNodes }) => {
const reactFlowWrapper = useRef(null);
const [elements, setElements] = useState([]);
const [edges, setEdges] = useState([]);
const prepareNode = async (nodeid) => {
//some service calls to fetch data and constuct nodes
setElements([ ...nodes]);
setEdges([...edges]);
}
return (
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={elements}
edges={edges}
//some properties
>
</ReactFlow>
</div>
</ReactFlowProvider>
)
};
export default ComponentA;
function CardNode({ data }) {
const renderSubFlowNodes = (id) => {
console.log(id);
//prepareNode(id)
}
return (
<>
<Handle type="target" position={Position.Top} />
<div className="flex node-wrapper">
<button className="btn-transparent btn-toggle-node" href="#" onClick={() => renderSubFlowNodes(data['id']) }>
<div>
<img src={Icon}/>
</div>
</button>
</div>
<Handle type="source" position={Position.Bottom}/>
</>
);
}
export default CardNode;
I looked for some references online, and most of them suggest to move this resuable function out of the component, but since this function carries a state that it directly sets to the ReactFlow using useState hook, I dont think it would be much of a help.
Other references talks about using useCallback or useRefs and forwardRef, useImperativeHandle especially for functional component, Which I did not quite understand well.
Can someone suggest me a solution or a work around for this specific use-case of mine.
You can add an onClick handler to the each node, and within the node view you call this handler on click.
In the parent Component within the onClick handler you can call prepareNode as needed.
useEffect(() => {
setElements(
elements.map(item => {
...item,
onClick: (i) => {
console.log(i);
prepareNode();
},
})
)},
[]);
The classical approach is to have a parent object that defines prepareNode (along with the state items it uses) and pass the required pieces as props into the components that use them.
That "parent object" could be a common-ancestor component, or a Context (if the chain from the parent to the children makes it cumbersome to pass the props all the way down it).
I have a component , which has a form and modal. On click of submit, the modal pops up and on confirmation a call to back end is dispatched.
Initially the modal is hidden by using a state (displayModal).
I am trying to test the API call by finding the button inside display modal. But can't find it as it is not on DOM (display modal is false).
How Can I set the state on jest test.
const MyTypeComponent: FunctionComponent<MyType> = ({
props1,
props2,
ownProp1,
ownProp2
}) => {
//There are two use effects to do something
//set the modal state
const [displayModal, setdisplayModalOpen] = useState(false);
const updateStatusquantityService = () => {
//Call an API
};
const InventoryStatusquantityFormSubmitted = (form) => {
if (!form.isValid) {
return;
}
//If form valid, display the modal;
};
return (
<>
<Modal
isOpen={displayModal}
setIsOpen={setdisplayModalOpen}
id={"InventoryStatusTypeModal"}
>
//Usual Modal stuff and then button
<Button id={"statusquantityUpdateBtn"} variant="primary" label="Update" onClick={() => updateStatusquantityService()}/>
</Modal>
<Form>
//On form submit, call InventoryStatusquantityFormSubmitted() and display the modal
</Form>
</>
);
};
export default connect(
(state: RootState) => ({
//map states to props
}),
(dispatch: ThunkDispatch) => ({
//map props 1 and props 2
})
)(InventoryStatusquantity);
When I am trying to trigger a click even on modal button 'statusquantityUpdateBtn' by finding it as below, I am getting an empty value as modal is not visible due to it's value.
it('Should submit status types form11', () => {
const submitButtonOnModal = wrapper.find('#statusquantityUpdateBtn').
});
I am trying to update the state by using
wrapper.instance().setdisplayModalOpen(true)
But getting error wrapper.instance().setdisplayModalOpen is not a function.
I am mounting with simple mount command:
export const mountWithMemoryRouter = (element: JSX.Element) => {
return mount(<MemoryRouter>{element}</MemoryRouter>);
};
wrapper = mountWithMemoryRouter(
<Theme>
<Provider store={store}>
<MyTypeComponent
{...defaultProps}
ownProp1={null}
ownProp2={null}
/>
</Provider>
</Theme>
);
Those state hooks are scoped to the function, so nothing outside the function can access them. That's why you're getting "is not a function" errors. It's akin to
function x() {
const y = 0
}
x().y // Error
I don't see in your code anything that calls setdisplayModalOpen(true) in order to show the modal.
Assuming you provided only partial code (but that it's written on your computer), and there is some button or something that runs setdisplaymodalOpen(true), (I'm assuming there's a form submit button) then if I were needing to test this, I would instead use React Testing Library and have something like
import { render, screen, fireEvent, waitFor } from 'react-testing-library'
import MyComponent from './components/one-to-test'
test('does whatever', async () => {
render(<MyComponent/>)
const showModalBtn = screen.getByText('Text of Button You Click to Display Modal')
fireEvent.click(showModalBtn)
await waitFor(() => expect(screen.getByText('Update')).not.toBeNull())
// You are now assured the modal is visible and can continue with the rest of your test
})
In this test, you first instruct React Testing Library to render the component that can show/hide the modal (i.e., the form). (Assuming there's a button you click to display the modal), you get that button, and then you simulate a click of that button, and then your test waits for the modal to be visible (in this case, it waits until the "Update" button contained in the modal is visible).
Then you can continue with testing your modal (like clicking the Update button with another fireEvent.click(updateBtn).
If you want to mock out your API, then you could also add
jest.mock('./my/api/library', () => ({
whateverApiCall: jest.fn(() => whateverItShouldReturn)
})
Now when you click the form submit button, it will call your mocked API function that returns whatever you defined it to return, and assuming it doesn't throw/reject, your modal will display, and you continue as described above.
I'm new to react and redux.
I have a container which initialize a table component with a list of items, and onclick function.
In the table component I have checkbox for each row. When I click the checkbox I want to select the row (change its style and add selected property to its element model).
When I click on the checkbox I call the onclick property function, then find the item on the list by its id, and change its selected property. The view is not refreshing.
I understand that a component is a "stupid" component that only binds the props and rendering.
What am I doing wrong?
// People container
<Table items={this.props.people} columns={this._columns} onRowSelect={this.selectRow} />
this.selectRow(id){
const selectedLead =_.find(this.props.leads.docs, (lead)=>{
return lead._id == id;
})
selectedLead.selected = !selectedLead.selected;
}
// Table Component - inside render()
{this.props.items.map((item, idx) => {
console.log(item.selected);
return <div style={styles.row(item.selected)}>etc...</div>
})}
Thanks :)
A React Component has props and state.
The difference is, that the Component will never change it props. But it can change it's state. This is why a Component will provide you the setState(...) Method, but no setProps(...) Method.
With that said, your approach to change the selected field in this.props is fundamentally not correct. (There also seems to be another problem in your code where you change the selected field in this.props.leads, but provide this.props.people to the table instead of this.props.leads)
Let me give you a basic example as to how I would solve your problem in Pure React (without a state library like Redux):
const Row = ({ item, onClick }) => (
<tr style={styles.row(item.selected)} onClick={() => onClick(item.id)}>...</tr>
)
const Table = ({ items, onRowClick }) => (
<table>
{items.map(item => <Row item={item} onClick={onRowClick} />)}
</table>
)
class PeopleTable extends React.PureComponent {
constructor(props) {
super(props)
this.state = { people: props.people }
}
componentWillReceiveProps(nextProps) {
if (nextProps.people !== this.state.people) {
this.setState({ people: nextProps.people })
}
}
setItemSelectedState(id) {
this.setState((prevState) => {
const people = prevState.people.map(item => ({
...item,
selected: item.id === id ? !item.selected : item.selected,
})
return { people }
})
}
handleRowClick = (id) => this.setItemSelectedState(id)
render() {
return (<Table items={people} onRowClick={this.handleRowClick} />)
}
}
The things to notice here are:
Row and Table are stateless components. They only take props and return jsx. Sometimes they are also referred to as presentational components.
PeopleTable keeps track of the selected state of each item. This is why it needs state and must be a class.
Because we can't change a components props, we have to keep a reference to props.people in this.state.
componentWillReceiveProps makes sure that if our components receives another list of people, the state is updated accordingly.
setItemSelectedState goes to the root of your problem. Instead of search and update of the item (like in your this.selectRow(id) method), we create a complete new list of people with map and call setState. setState will trigger a rerender of the component and because we created a new people list, we can use the !== check in componentWillReceiveProps to check if people has changed.
I hope this answer was helpful to your question.
I am trying to think the react way but I can't find a solution on how to invoke the .submit() method of the form component.
I have a material-ui Dialog where I have to pass the buttons via actions property. From this action component, I would like to invoke the .submit() method of the Form component, which is a child of the dialog.
Do I have to pass the formRef up to the Dialog to pass it then to the Actions, and how would I do that? Or is there any React way I am missing out on?
class FormDialog extends React.Component {
render() {
return (
<Dialog actions={<Actions />} >
<Form />
</Dialog>
)
}
}
const Actions = (props) => {
return (
<FlatButton
label="Submit"
onTouchTap={() => formRef.submit()}
/>
)
}
const Form = () => {
let formRef;
return (
<AutoForm
ref={ref => formRef = ref}
onSubmit={doc => db.save(doc)}
>
</AutoForm>
)
}
Any buttons inside a form that submit the form should be type="submit", and clicking on any of them will trigger the <form />'s onSubmit handler. There's no need to pass around a reference.
There's a few React-way notes here:
If you have to pass things "up" and then back "down" a component tree, you're probably not approaching the problem correctly.
Components should never call methods on other components.
Unless you really know when they're needed, refs to DOM elements should only be referenced inside the component owning the ref.
I have a form that has a submit button.
That form calls a function onclick that sets the state of something from false to true.
I then want to pass this state back to the parent so that if it is true it renders componentA but if it is false it renders componentB.
How would I do that in react?
I know I need to use state or props but not sure how to do it. also is this contradicting the one-way flow react principle??
ComponentA code:
<form onSubmit={this.handleClick}>
handleClick(event) {
this.setState({ decisionPage: true });
event.preventDefault();
};
Parent component that controls what it displays:
return (
<div>
{this.props.decisionPage ?
<div>
<LoginPage />
</div>
:
<div>
<Decision showThanks={this.props.showThanks}/>
</div>
}
</div>
)
Move handleClick to the parent and pass it to the child component as a prop.
<LoginPage handleClick={this.handleClick.bind(this)}/>
Now in the child component:
<form onSubmit={this.props.handleClick}>
This way submitting the form will update the state in parent component directly. This assumes you don't need to access updated state value in child component. If you do, then you can pass the state value back from the parent to the child as a prop. One-way data flow is maintained.
<LoginPage handleClick={this.handleClick.bind(this)} decisionPage={this.state.decisionPage}/>
Pass State as a Prop
I have recently learned a method that works great for changing state in a <Parent /> component from a <Child /> component.
This might not be the exact answer for this question, but it is surely applicable to this situation and other similar situations.
It works like this:
set the default STATE in the <Parent /> component - Then add the 'setState' attribute to the <Child />
const Parent = () => {
const [value, setValue] = useState(" Default Value ");
return (
<Child setValue={setValue} />
)
}
Then change the state(in Parent) from the Child component
const Child = props => {
return (
<button onClick={() => props.setValue(" My NEW Value ")}>
Click to change the state
</button>
)
}
When you click the button, the state in the <Parent /> component will change to whatever you set the state to in the <Child /> component, making use of "props".. This can be anything you want.
I Hope this helps you and other devs in the future.
In Parent Component:
getDatafromChild(val){
console.log(val);
}
render(){
return(<Child sendData={this.getDatafromChild}/>);
}
In Child Component:
callBackMethod(){
this.props.sendData(value);
}
Simple Steps:
Create a component called Parent.
In Parent Component create a method that accepts some data and sets
the accepted data as the parent's state.
Create a component called Child.
Pass the method created in Parent to child as props.
Accept the props in parent using this.props followed by method
name and pass child's state to it as argument.
The method will replace the parent's state with the child's state.
Here is an example of how we can pass data from child to parent (I had the same issue and use come out with this )
On parent, I have a function (which I will call from a child with some data for it)
handleEdit(event, id){ //Fuction
event.preventDefault();
this.setState({ displayModal: true , responseMessage:'', resId:id, mode:'edit'});
}
dishData = <DishListHtml list={products} onDelete={this.handleDelete} onEdit={(event, id) => this.handleEdit(event, id)}/>;
At the child component :
<div to="#editItemDetails" data-toggle="modal" onClick={(event)=>this.props.onEdit(event, listElement.id) }
className="btn btn-success">
In React you can pass data from parent to child using props. But you need a different mechanism to pass data from child to parent.
Another method to do this is to create a callback method. You pass the callback method to the child when it's created.
class Parent extends React.Component {
myCallback = (dataFromChild) => {
//use dataFromChild
},
render() {
return (
<div>
<ComponentA callbackFromParent={this.myCallback}/>
</div>
);
}
}
You pass the decisionPage value from the child to the parent via the callback method the parent passed.
class ComponentA extends React.Component{
someFn = () => {
this.props.callbackFromParent(decisionPage);
},
render() {
[...]
}
};
SomeFn could be your handleClick method.
if your parent component is a functional component you can now use the use context way. Which involves passing the ref to the object and the ref to the stateChanging method. What this will allow you to do is change state from parrent in child and also ref tht state while remaining synced with Parent State. You can learn more about this in a youtubeVideo by codedamn titled 'React 16.12 Tutorial 20: Intro to Context API' and 'React 16.12 Tutorial 21: useContext'
This works exactly what I wanted. But in case of set of data with say 50 records with (customer_id, customer_name) as values to be updated from child to parent, then this lags. Do the setState using React.useEffect in child component
i have same problem and so performed this code :
in Parent
const PARENT = () => {
const [value, setValue] = useState("....");
return (
)
}
in Child
const CHILD = props => {
return (
<button onClick={() => props.setValue("....")}>
Click to change the state
</button>
)
}