I have a parent component which calls common grid component(child). The structure of the grid component is defined in the child component. The parent component just sends the data to the child component.
<Grid tableData={gridData} columnData={activeColumnDef} onselectedRows={isDisabledOnMultipleRowSelect}/>}
Child component:
const Grid = ({ tableData, columnData, ...props}) => {
const [gridApi, setGridApi] = useState(null);
const onGridReady = params => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
}
const refreshEntireGrid = () => {
gridApi.setRowData(gridrowData);
gridApi.refreshCells({force:true});
}
const getSelectedRowData = (event) => {
props.onselectedRows(event.api.getSelectedRows().length);
}
<div className="ag-theme-alpine-dark">
<AgGridReact
onGridReady={onGridReady}
defaultColDef={{
initialWidth: 270,
sortable: true,
filter: true,
resizable: true,
sizeColumnsToFit: true
}}
ref={gridRef}
applyColumnDefOrder={true}
headerCheckboxSelection={true}
suppressPaginationPanel={true}
columnDefs={gridcolumns}
rowData={gridrowData}
pagination={true}
paginationAutoPageSize={true}
rowSelection="multiple"
onSelect={(event) => getSelectedRowData(event)}
onSelectionChanged={getSelectedRowData}>
</AgGridReact>
</div>
In the parent component itself there are set of buttons. One of such buttons is RemoveButton which removes the selected rows from the grid and refreshButton that refreshes the grid. To capture the selected rows and the gridApi state, I have defined various functions.
I would like to achieve two things--
Either pass refreshEntireGrid function to the parent component so that when I click on the button in parent component this function is triggered.
Or find a way to pass the gridApi state to the parent object so that I can use it at multiple places. Instead of passing multiple methods from parent object as props, it would be more cleaner if I can pass gridApi to parent and use in whichever onClick method(in parent component) there is a need of it.
Is there a better solution instead of the two that I am thinking ? Please help ?
The solution is bit tricky. But quite easy if you understand below steps:
Create a callback in the parent component which takes in the data
needed as a parameter.
Pass this callback as a prop to the child
component.
Send data from the child component using the callback.
ParentComponent
function ParentComponent(props) {
let [gridValueFromChild, setGridValueFromChild] = useState(null);
let callback = valueFromChild => setGridValueFromChild(valueFromChild);
return (
<div>
<ChildComponent callbackFunc={callback} someValue={value} />
</div>
);
}
ChildComponent
function ChildComponent(props) {
const [gridApi, setGridApi] = useState(null);
const refreshEntireGrid = () => {
gridApi.setRowData(gridrowData);
gridApi.refreshCells({force:true});
// Send gridApi value to Parent
props.callbackFunc(gridApi)
}
// Incase you want to send data to parent at ComponentDidMount itself
useEffect(() => {
props.callbackFunc(gridApi)
}, [])
return (
<div>
...
</div>
);
}
If u want to pass gridApi value at the beginning itself or at any state change, You can use UseEffect hook and call callbackFunc there
Related
I have a parent Container, I plan to pass inside different child components that will accept callback.
Container:
const Container = ({children}) => {
const [selection, setSelection] = useState([]);
const setSelection = (returnObject) => {
setSelection(prev => [...selection, returnObject])
}
return(
<StyledContainer>
{children}
<Button>Search {selection}</Button>
</StyledContainer>
)
}
Container will have different children that all accept callback:
<Container><Child1 callback={}></Container>
<Container><Child2 callback={}></Container>
<Container><Child3 callback={}></Container>
Is there a way to pass component as a child to Container and for that Child to be using setSelection function as prop for Child's callback prop? (without Redux)
Yes you can override any props passed to the children as follows:
const Container = ({children}) => {
const [selection, setSelection] = useState([]);
const setSelection = (returnObject) => {
setSelection(prev => [...selection, returnObject])
}
return(
<StyledContainer>
{React.cloneElement(children, {callback: setSelection } }
<Button>Search {selection}</Button>
</StyledContainer>
)
}
Then you can use it as:
<Container><Child1/></Container>
<Container><Child2/></Container>
<Container><Child3/></Container>
And inside of each Child component, you can call callback() as need it.
React.cloneElement will create a copy from children passing additional props in this case only callback is passed as a prop you can pass as many as you need as a new object.
Details: https://reactjs.org/docs/react-api.html#cloneelement
I have the following code:
export default function Parent() {
const children1 = someArrayWithSeveralElements.map(foo => <SomeView />);
const children2 = someArrayWithSeveralElements.map(foo => <SomeCheckbox />);
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
For a given element foo, there is a SomeView component that is conditionally rendered based on the state of a SomeCheckbox. I'm having trouble figuring out a way to have the state from the checkbox affect the rendering of the sibling view component.
Normally the solution would be to just declare the state hook in the parent component and pass them down to each child, but since the siblings are rendered via foreach loops it's impossible to do so.
My current solution is to also generate the state hooks for each foo in a loop as well, but that feels a bit hacky since it's better to avoid creating hooks inside of loops (it's worth nothing that someArrayWithSeveralElements is not intended to change after mounting).
Is there a more elegant alternative to solve this?
The solution is what you side, you need to create a state in the parent component and pass it to the children. and this will work for single component or bunch of them, the difference is just simple: use array or object as state.
const [checkboxesStatus, setCheckboxesStatus] = useState({// fill initial data});
const children1 = someArrayWithSeveralElements.map(foo =>
<SomeView
visibile={checkBoxesStatus[foo.id]}
/>);
const children2 = someArrayWithSeveralElements.map(foo =>
<SomeCheckbox
checked={checkBoxesStatus[foo.id]}
onChange={// set new value to foo.id key}
/>)
export default function Parent() {
const [states, setStates] = React.useState([]);
const children1 = someArrayWithSeveralElements.map((foo, i) => <SomeView state={states[i]} />);
const children2 = someArrayWithSeveralElements.map((foo, i) => {
const onStateChange = (state) => {
setStates(oldStates => {
const newStates = [...(oldStates || [])]
newStates[i] = state;
return newStates;
})
}
return <SomeCheckbox state={states[i]} onStateChange={onStateChange} />;
});
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
Use states in the parent componet.
Note: the element of states may be undefined.
I am trying to pass data to child component(grid) from parent component. In parent component, I have created a state for the data which gets updated once the data fetch from an api is finished. Currently, I check if the state length is greater than 0 and only then I call child component.
const ParentComponent()=>{
const [gridData, setGridData] = useState([]);
useEffect(() => { getDataFunction (); }
const getDataFunction = async () => {
try { //apis to get data }
setGridData(apiData);
}
return (
{gridData.length > 0 && <ChildComponent Grid tableData={gridData}}
);
}
The above code works fine. It displays the data when there is some value in gridData. But I would like to display an empty table while the api is being called. Or if the api has no data, even then an empty table needs to be displayed.
Child component:
const Grid = React.forwardRef(({ tableData, columnData, ...props}, ref) => {
let activeColumn = [...columnData];
const onGridReady = params => {
setGridApi(params.api);
setGridColumnApi(params.columnApi);
setGridRowData(tableData);
params.api.applyTransaction({ add: tableData });
}
return (
<AgGridReact
onGridReady={onGridReady}
columnDefs={activeColumn}
pagination={true}
</AgGridReact>
);
});
If I just use gridData instead of gridData.length, then even after the value of the state is updated, the table is not updated. It will always be empty even if later the data is fetched from an api. How do I make child component update when gridData state changes ?
return (
<>
{gridData.length > 0 && <ChildComponent Grid tableData={gridData} />}
{gridData.length === 0 && <div>Hi there</div>}
</>
);
You simply render it conditionally based on length
I hope you have written useEffect code properly, bcoz the syntax seems incorrect.
useEffect(() => {
const getDataFunction = async () => {
try {
setGridData(apiData);
}
}
getDataFunction()
},[])
return (
<ChildComponent Grid tableData={gridData}
);
Use gridData.length > 0 line while rendering your child component
and for Empty Table while loading you can use Skeleton on loading state.
I have a common component for the table which is being used in different pages through the app. Now the selection of rows is being saved inside the table component itself. I want to access the selected rows of data from its parent component whenever button pressed
Here is an example
https://codesandbox.io/s/naughty-pond-3e5jp
What you can do is pass maintain some state for selectedRows in you parent component then pass a callback to Table component that'll be called every time a row is selected or unselected. Here I have made some changes to your sandbox, hope it helps :)
https://codesandbox.io/s/naughty-sun-3nhue?file=/src/App.js
#jiltender
i fix my problem by doing this to get row data outside the table
rowProps={row =>(console.log(row)
)}
add this on parent components where u call the Table
<Table
sortByProps={[]}
columns={columns}
rowProps={row =>(setSelectedItems(row))} /// pass row props
/>
and the table side
add this in
function props
` `rowProps = () => ({}) })
function Table({ columns, data, showSpinnerProp, hiddenColumnsProps,getCellProps, getRowProps, height, sortByProps, source,
setSelectedItems,rowProps = () => ({}) }) {
this will return u all the select data you can call it before return
useEffect(() => {
setSelectedItems = selectedFlatRows;
rowProps(selectedFlatRows)
}, [selectedFlatRows]);
}
Create a state variable outside of the component, something like
const [selectedRows, setSelectedRows] = useState([]);
And then pass this down to the component through the props. then on the button you can have some code like this
onPress = { () => props.setSelectedRows(selectedRows) }
Connect the parent component with the Table with useEffects. A pseudo example below.
function TableComponent ({columns, data.., setSelectedRows}){
const { ..., selectedFlatRows, state:{selectedRowIds} } = useTable({..})
useEffect(() =>{
setSelectedRows(selectedFlatRows)
}, [selectedFlatRows])
......
}
function ParentComponent (props){
const [selectedRows, setSelectedRows = useState ([])
useEffect(() =>{
// do your custom handle of the selection
},[selectedTickers])
return (
......
<Table
columns={columns}
....
setSelectedRows={setSelectedTickers}>
</Table>
......
)
}
The parent component contains an array of objects.
It maps over the array and returns a child component for every object, populating it with the info of that object.
Inside each child component there is an input field that I'm hoping will allow the user to update the object, but I can't figure out how to go about doing that.
Between the hooks, props, and object immutability, I'm lost conceptually.
Here's a simplified version of the parent component:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(()=>{
// makes an axios call and triggers setCategories() with the response
}
return(
categories.map((element, index) => {
return(
<Child
key = {index}
id = {element.id}
firstName = {element.firstName}
lastName = {element.lastName}
setCategories = {setCategories}
})
)
}
And here's a simplified version of the child component:
const Child = (props) => {
return(
<h1>{props.firstName}</h1>
<input
defaultValue = {props.lastName}
onChange={()=>{
// This is what I need help with.
// I'm a new developer and I don't even know where to start.
// I need this to update the object's lastName property in the parent's array.
}}
)
}
Maybe without knowing it, you have lifted the state: basically, instead of having the state in the Child component, you keep it in the Parent.
This is an used pattern, and there's nothing wrong: you just miss a handle function that allows the children to update the state of the Parent: in order to do that, you need to implement a handleChange on Parent component, and then pass it as props to every Child.
Take a look at this code example:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(() => {
// Making your AXIOS request.
}, []);
const handleChange = (index, property, value) => {
const newCategories = [...categories];
newCategories[index][property] = value;
setCategories(newCategories);
}
return categories.map((c, i) => {
return (
<Child
key={i}
categoryIndex={i}
firstName={c.firstName}
lastName={c.lastName}
handleChange={handleChange} />
);
});
}
const Child = (props) => {
...
const onInputChange = (e) => {
props.handleChange(props.categoryIndex, e.target.name, e.target.value);
}
return (
...
<input name={'firstName'} value={props.firstName} onChange={onInputChange} />
<input name={'lastName'} value={props.lastName} onChange={onInputChange} />
);
}
Few things you may not know:
By using the attribute name for the input, you can use just one handler function for all the input elements. Inside the function, in this case onInputChange, you can retrieve that information using e.target.name;
Notice that I've added an empty array dependecies in your useEffect: without it, the useEffect would have run at EVERY render. I don't think that is what you would like to have.
Instead, I guest you wanted to perform the request only when the component was mount, and that is achievable with n empty array dependecies;