Passing function props to children in React - reactjs

I want to use handleChange in the <Tab/> component but couldn't get it done.
const handleChange = (event, newValue) => {
setValue(newValue)
}
How can I access onChange prop on <Tab/> component?
<TabHeader value={value} onChange={handleChange}>
<Tab label="Tab1" />
<Tab label="Tab2" />
<Tab label="Tab2" />
</TabHeader>
Here's my <TabHeader/> component:
function TabHeader(props) {
const { children, value, onChange, ...other } = props
return (
<ul>
{children} <--- Tab components
</ul>
)
}

Have you tried cloneElement?
function TabHeader(props) {
const { children, onChange } = props;
const childWithProps = Children.map(children, (child) =>
isValidElement(child) ? cloneElement(child, { onChange }) : null
);
return <ul>{childWithProps}</ul>;
}
Working solution can be found here: https://codesandbox.io/s/suspicious-dawn-h56von?file=/src/App.js:514-754
Ref: How to pass props to {this.props.children}

Related

Passing array of object into functional component and mapping

I'm trying to use Material UI to create a reusable navigation tab, however, I am having trouble passing the object over to my functional component and mapping it out. Nothing displays when mapping.
I am fairly new to react hooks. Thanks in advance.
Class Component (passing state over to Navigation)
class MyWorkspace extends Component {
state = {
menuItem: [
{
name: "menu 01",
urlPath: "/home/menu01"
},
{
name: "menu 02",
urlPath: "/home/menu02"
},
{
name: "Reports",
urlPath: "/home/menu03"
},
],
}
}
render () {
return (
<div>
<Navigation menuItem />
</div>
)
}
Functional Component
export default function Navigation({ menuItem }) {
const [value, setValue] = React.useState(2);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const MenuList = () => {
return (
<>
{menuItem.map(item => {
return <Tab label={item.name} className="Nav-Tab" />;
})}
</>
)
}
return (
<div className="Nav-Title row">
<Tabs
className="Nav-Tab-List"
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
>
<MenuList />
</Tabs>
</div>
);
}
In the class component, you should assign a value to the prop being passed:
render () {
return (
<div>
<Navigation menuItem={this.state.menuItem} />
</div>
)
}
In function component, you should call MenuList() inside the render :
export default function Navigation({ menuItem }) {
const [value, setValue] = React.useState(2);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const MenuList = () => {
return (
<>
{menuItem.map(item => {
return <Tab label={item.name} className="Nav-Tab" />;
})}
</>
)
}
return (
<div className="Nav-Title row">
<Tabs
className="Nav-Tab-List"
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
>
{MenuList()} // call this or put the map here
</Tabs>
</div>
);
}
First you need to define state in constructor
Second destruct menuitem from state
const {menuItem} = this.state
Third pass props like this
<Navigation menuItem={menuItem} />
If you pass like this Navigation menuItem /> you will get boolean value true inside child component.
In MyWorkspace/render function, you don't actually pass the menuItem state.
<Navigation menuItem /> will pass menuItem as true value. Replace it with: <Navigation menuItem={this.state.menuItem} />
Navigation component code looks correct

How to pass a function to a child component and use as an useState?

Hello i have an component where i set a prop darkMode in a useState. I want to give this to the child component with onDarkmodeChange={darkMode}. This child gave this prop also to his child. In the toggle switch i want to use the prop onDarkmodeChange. But how am i supposed to use this? I tink i am doing something wrong because darkMode is not updated anymore.
export function PageLayout({ children }) {
const [darkMode, setDarkMode] = React.useState(false)
return (
<Header onDarkModeChange={darkMode} />
)
}
export function Header({ onDarkModeChange }) {
return (
<ToggleSwitch {... { onDarkModeChange }} />
)
}
export function ToggleSwitch({ onDarkModeChange }) {
const [isToggled, setIsToggled] = React.useState(onDarkModeChange)
return (
<label className={styles.component}>
<input className={styles.input} type='checkbox' checked={isToggled} onChange={() => setIsToggled(!isToggled)} />
<span className={cx(styles.switch, isToggled && styles.isActive)} />
</label>
)
}
I think it's not necessary to have the isToggled state since it's going to have the same value that the darkMode state. Also, I would move all the logic to the parent so that both children can access and manipulate that state, in case that it's necessary. Try this:
export function PageLayout({ children }) {
const [darkMode, setDarkMode] = React.useState(false)
const switchDarkMode = () => {
setDarkMode(currentDarkMode => !currentDarkMode)
}
return (
<Header darkMode={darkMode} switchDarkMode={switchDarkMode} />
)
}
export function Header({ darkMode, switchDarkMode }) {
return (
<ToggleSwitch darkMode={darkMode} switchDarkMode={switchDarkMode} />
)
}
export function ToggleSwitch({ darkMode, switchDarkMode }) {
return (
<label className={styles.component}>
<input className={styles.input} type='checkbox' checked={darkMode} onChange={switchDarkMode} />
<span className={cx(styles.switch, darkMode && styles.isActive)} />
</label>
)
}

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

Assigning useRef through render props

Using Render props pattern I wanted to see if there was a way to make this work using the current setup. I have a Parent component that uses an Example component as a wrapper to render some children inside it. I wanted to pass off a ref from inside of Example to one of the children in the render prop. Is this possible ?
const Example = ({ children }) => {
const ref = useRef(null);
const [open, SetOpen] = useState(false);
const [controls] = useCustomAnimation(open, ref);
return (
<div>
{children({ ref })}
</div>
);
};
const Parent = () => {
return (
<div>
<Example>
{ref => {
<motion.div
ref={ref}
>
{console.log('ref= ', ref)}
....... more children
</motion.div>;
}}
</Example>
</div>
);
};
Yes, your current file is almost exactly correct. I setup an example, but here is the gist:
const Example = ({ children }) => {
const ref = useRef(null);
return <div>{children({ ref })}</div>;
};
const Parent = () => {
return (
<div>
<Example>
{({ ref }) => {
console.log(ref);
return <input type="text" ref={ref} />;
}}
</Example>
</div>
);
};
Note: You need to destructure the object you are passing into the children function.

React Hook : Send data from child to parent component

I'm looking for the easiest solution to pass data from a child component to his parent.
I've heard about using Context, pass trough properties or update props, but I don't know which one is the best solution.
I'm building an admin interface, with a PageComponent that contains a ChildComponent with a table where I can select multiple line. I want to send to my parent PageComponent the number of line I've selected in my ChildComponent.
Something like that :
PageComponent :
<div className="App">
<EnhancedTable />
<h2>count 0</h2>
(count should be updated from child)
</div>
ChildComponent :
const EnhancedTable = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Click me {count}
</button>
)
};
I'm sure it's a pretty simple thing to do, I don't want to use redux for that.
A common technique for these situations is to lift the state up to the first common ancestor of all the components that needs to use the state (i.e. the PageComponent in this case) and pass down the state and state-altering functions to the child components as props.
Example
const { useState } = React;
function PageComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1)
}
return (
<div className="App">
<ChildComponent onClick={increment} count={count} />
<h2>count {count}</h2>
(count should be updated from child)
</div>
);
}
const ChildComponent = ({ onClick, count }) => {
return (
<button onClick={onClick}>
Click me {count}
</button>
)
};
ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
You can create a method in your parent component, pass it to child component and call it from props every time child's state changes, keeping the state in child component.
const EnhancedTable = ({ parentCallback }) => {
const [count, setCount] = useState(0);
return (
<button onClick={() => {
const newValue = count + 1;
setCount(newValue);
parentCallback(newValue);
}}>
Click me {count}
</button>
)
};
class PageComponent extends React.Component {
callback = (count) => {
// do something with value in parent component, like save to state
}
render() {
return (
<div className="App">
<EnhancedTable parentCallback={this.callback} />
<h2>count 0</h2>
(count should be updated from child)
</div>
)
}
}
To make things super simple you can actually share state setters to children and now they have the access to set the state of its parent.
example:
Assume there are 4 components as below,
function App() {
return (
<div className="App">
<GrandParent />
</div>
);
}
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
return (
<>
<div>{name}</div>
<Parent setName={setName} />
</>
);
};
const Parent = params => {
return (
<>
<button onClick={() => params.setName("i'm from Parent")}>
from Parent
</button>
<Child setName={params.setName} />
</>
);
};
const Child = params => {
return (
<>
<button onClick={() => params.setName("i'm from Child")}>
from Child
</button>
</>
);
};
so grandparent component has the actual state and by sharing the setter method (setName) to parent and child, they get the access to change the state of the grandparent.
you can find the working code in below sandbox,
https://codesandbox.io/embed/async-fire-kl197
IF we Have Parent Class Component and Child function component this is how we going to access child component useStates hooks value :--
class parent extends Component() {
constructor(props){
super(props)
this.ChildComponentRef = React.createRef()
}
render(){
console.log(' check child stateValue: ',
this.ChildComponentRef.current.info);
return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
}
}
Child Component we would create using
React.forwardRef((props, ref) => (<></>))
. and
useImperativeHandle(ref, createHandle, [deps])
to customizes the instance value that is exposed to parent components
const childComponent = React.forwardRef((props, ref) => {
const [info, setInfo] = useState("")
useEffect(() => {
axios.get("someUrl").then((data)=>setInfo(data))
})
useImperativeHandle(ref, () => {
return {
info: info
}
})
return (<> <h2> Child Component <h2> </>)
})
I had to do this in type script. The object-oriented aspect would need the dev to add this callback method as a field in the interface after inheriting from parent and the type of this prop would be Function. I found this cool!
Here's an another example of how we can pass state directly to the parent.
I modified a component example from react-select library which is a CreatableSelect component. The component was originally developed as class based component, I turned it into a functional component and changed state manipulation algorithm.
import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';
const MultiSelectTextInput = (props) => {
const components = {
DropdownIndicator: null,
};
interface Option {
readonly label: string;
readonly value: string;
}
const createOption = (label: string) => ({
label,
value: label,
});
const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
console.group('Value Changed');
console.log(value);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
props.setValue(value);
};
const handleInputChange = (inputValue: string) => {
props.setInputValue(inputValue);
};
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
if (!props.inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
console.group('Value Added');
console.log(props.value);
console.groupEnd();
props.setInputValue('');
props.setValue([...props.value, createOption(props.inputValue)])
event.preventDefault();
}
};
return (
<CreatableSelect
id={props.id}
instanceId={props.id}
className="w-100"
components={components}
inputValue={props.inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={handleChange}
onInputChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="Type something and press enter..."
value={props.value}
/>
);
};
export default MultiSelectTextInput;
I call it from the pages of my next js project like this
import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";
const NcciLite = () => {
const [value, setValue] = useState<any>([]);
const [inputValue, setInputValue] = useState<any>('');
return (
<React.Fragment>
....
<div className="d-inline-flex col-md-9">
<MultiSelectTextInput
id="codes"
value={value}
setValue={setValue}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</div>
...
</React.Fragment>
);
};
As seen, the component modifies the page's (parent page's) state in which it is called.
I've had to deal with a similar issue, and found another approach, using an object to reference the states between different functions, and in the same file.
import React, { useState } from "react";
let myState = {};
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
myState.name=name;
myState.setName=setName;
return (
<>
<div>{name}</div>
<Parent />
</>
);
};
export default GrandParent;
const Parent = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Parent")}>
from Parent
</button>
<Child />
</>
);
};
const Child = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Child")}>
from Child
</button>
</>
);
};

Resources