Conditonal component still renders - ReactJS - reactjs

I'm learning react and wanted to try creating a loading component that shows a loading text until a condition is met i.e. the props has the correct information.
The problem is that the element is still loading even if the condition is not met:
Leading Component:
import React from 'react';
const Loading = ({ condition, children }) => (<div>{condition ? children :
'Loading'}</div>);
export default Loading;
Here is my render method for a component that uses the Loading Component:
return
(<Loading condition={props.data && props.data.result && props.data.result.length > 1}>
<div> { ViewHelper.getCatalogItems(props.data) }</div></Loading>);
Now my problem is I'm getting an error when calling { ViewHelper.getCatalogItems(props.data) } becauuse props.data is undefined however I was hoping that the Loading Component wouldn't call the function if the ternary condition in the LoadingComponent was false.
if I change ViewHelper.getData to just some string value, everything seems to work and 'Loading ' is displayed.
Thanks

The fact that Loading component doesn't use children doesn't mean that children aren't rendered. Otherwise there would be nothing to pass as props.children to parent component.
As can be seen in this example, child expression is evaluated despite children prop ignored in parent component.
A proper way to handle this and prevent eager children rendering is to use render prop recipe, which is also known as function as a child:
const Loading = ({ condition, children }) => (
<div>{condition && children ? children() : 'Loading'}</div>
);
...
<Loading condition={props.data && props.data.result && props.data.result.length > 1}>
{() => (
<div> { ViewHelper.getCatalogItems(props.data) }</div>
)}
</Loading>
Notice that since props.children is a function, it's used as children() in ternary expression.
Or use a HOC for components:
const withLoading = (Comp) =>
({ condition, ...props }) => (
<div>{condition ? <Comp {...props} /> : 'Loading'}</div>
)
);
...
const LoadingCatalogItemsComponent = withLoading(CatalogItemsComponent);

the children parameter received in the Loading component will have already rendered in the parent (try logging the content of children from Loading)
getCatalogItems will be called regardless of the conditional
cases where props.data need to be handled here. there are multiple ways to do this:
defaultProps or type checking in general
input validation for getCatalogItems
destructuring assignment
default function parameters

Updated answer: as estus points out below, render props are probably a good way to do this. Here's an example:
import React from "react";
import ReactDOM from "react-dom";
const Loading = ({ condition, render }) => {
if (condition) {
return render();
} else {
return "Loading";
}
};
const Thing = ({ data }) => {
console.log(data);
return data.map(d => <li>{d}</li>);
};
class App extends React.Component {
state = {
data: [1, 2, 3]
};
render() {
return (
<Loading
condition={this.state.data.length > 1}
render={() => {
return <Thing data={this.state.data} />;
}}
/>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox here.

As #estus said, HOCs and render props are two popular methods. Moving:
<div>{ ViewHelper.getCatalogItems(props.data)}</div>
into its own component (which is passes the prop, e.g., <CatalogItems {...props} />) will also stop you from getting errors. As long as the code isn't in the actual render method; otherwise, it's fired, regardless of whether React would have actually rendered it.
Example:
const Loading = ({ condition, children }) => (
<div>{condition ? children : "Loading in 3 seconds"}</div>
);
// now that it's in its own component the code isn't run until the component actually renders
const CatalogItems = ({ data }) => data.result.map(item => item);
class App extends React.Component {
state = {
data: null
};
// dummy API call
componentDidMount() {
setTimeout(
() => this.setState({ data: { result: ["cat ", "dog ", "mouse "] } }),
3000
);
}
render() {
const props = this.state; // let's just pretend these were inherited props
return (
<Loading
condition={
props.data && props.data.result && props.data.result.length > 1
}
data={props.data}
>
<CatalogItems {...props} />
</Loading>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id='root'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.1/umd/react-dom.development.js"></script>

Related

react-router-dom v6 useNavigate passing value to another component

The version of react-router-dom is v6 and I'm having trouble with passing values to another component using Navigate.
I want to pass selected rows to another page called Report. But, I'm not sure I'm using the right syntax for navigate method and I don't know how to get that state in the Report component.
Material-ui Table: I'm trying to use redirectToReport(rowData) in onClick parameter.
function TableRows(props){
return (
<MaterialTable
title="Leads"
columns={[
...
]}
data = {props.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Generate a report based on selected leads.',
icon: 'addchart',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
props.redirect(rowData)
}
}]}
/>
)}
LeadTable component
export default function LeadTable(props) {
let navigate = useNavigate();
const [leads, setLeads] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl(url) {
const response = await fetch(url);
const json = await response.json();
setLeads(json[0]);
setLoading(false);
}
useEffect(() => {
fetchUrl("http://localhost:5000/api/leads");
}, []);
function redirectToReport(rowData) {
navigate('/app/report', { state: rowData }); // ??? I'm not sure if this is the right way
}
return(
<div>
<TableRows leads={leads} redirect={redirectToReport}></TableRows>
</div>
)}
Report component
export default function ReportPage(state) {
return (
<div>
{ console.log(state) // This doesn't show anything. How to use the state that were passed from Table component here?}
<div className = "Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);}
version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.
Your navigate('/app/report', { state: rowData }); looks correct to me.
react-router-v6
If you need state, use navigate('success', { state }).
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Your ReportPage needs to be rendered under the same Router that the component doing the push is under.
Route props are no longer passed to rendered components, as they are now passed as JSX literals. To access route state it must be done so via the useLocation hook.
function ReportPage(props) {
const { state } = useLocation();
console.log(state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
If the component isn't able to use React hooks then you still access the route state via a custom withRouter Higher Order Component. Here's an example simple withRouter HOC to pass the location as a prop.
import { useLocation, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
// other hooks
return (
<WrappedComponent
{...props}
{...{ location, /* other hooks */ }}
/>
);
};
Then access via props as was done in pre-RRDv6.
class ReportPage extends Component {
...
render() {
console.log(this.props.location.state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
}
2 things (just a suggestion):
Rather than a ternary use &&
{location && <div>{location.state.name}</div>}
Why are you checking location and rendering location.state.name? I would use the check on the data you are fetching or make sure the data returns null or your value.
On Sabaoon Bedar's Answer, you can check if there is any data or not before showing it :
Instead of this <div>{location.state.name}</div>
Do this { location != null ? <div>{location.state.name}</div> : ""}
if you want to send data with usenavigate in functional component you can use like that
navigate(`/take-quiz/${id}`, { state: { quiz } });
and you can get it with uselocation hook like this
const location = useLocation();
location.state.quiz there is your data
But you cannot get this data in props it;s tricky part ;)!!
on SABAOON BEDAR answer,
from component A: navigate('/', {state:"whatever"}
in component B: console.log(location.state) //output = whatever

Unable to use hook in HOC for functional component

Using HOC to render functional component ie. SampleComponent here does work for me.
const SampleComponent: FC = () => {
return (<div>Hello World</div>);
};
export default HOC({ component: SampleComponent });
And HOC is->
const HOC = ({ component: Component }) => {
return (() => <Component/>);
}
However I want to render this component conditionally , something like this-
<div>
{!id ? ( <SomeOtherComponent prop1={'hello'} prop2={'world'} /> ) : ( <Component /> )}
</div>
Here id is coming as a response from graphql query hook, which again i am unable to use in HOC function.
For starters, hooks are meant to be a replacement for HOC.
However, you can definitely use hooks in a HOC which is a function component. Only thing, you need to ensure is that you are using hook in a component that is rendered and not a function.
For instance use a hook like below is incorrect since you do not render HOC component but use it like const X = HOC(Component);
const HOC = ({ component: Component }) => {
const id = useQuery(query);
return (() => <Component/>);
}
The correct way to do it would be
const HOC = ({ component: Component }) => {
return () => {
const id = useQuery(query);
return (<div>
{!id ? ( <SomeOtherComponent prop1={'hello'} prop2={'world'} /> ) : ( <Component /> )}
</div>)
}
}
Working sample demo
However when you implement it like above, you are likely to receive a ESLINT warning because ESLINT is not intelligent enough yet to detect how the component is used.
You can disable the warning for such scenarios

React.memo - why is my equality function not being called?

I have a parent component that renders a collection of children based on an array received via props.
import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import { Content } from 'components-lib';
import Child from '../Child';
const Parent = props => {
const { items } = props;
return (
<Content layout='vflex' padding='s'>
{items.map(parameter => (
<Child parameter={parameter} key={shortid.generate()} />
))}
</Content>
);
};
Parent.propTypes = {
items: PropTypes.array
};
export default Parent;
Every time a new item is added, all children are re-rendered and I'm trying to avoid that, I don't want other children to be re-rendered I just want to render the last one that was added.
So I tried React.memo on the child where I'll probably compare by the code property or something. The problem is that the equality function never gets called.
import React from 'react';
import PropTypes from 'prop-types';
import { Content } from 'components-lib';
const areEqual = (prevProps, nextProps) => {
console.log('passed here') // THIS IS NEVER LOGGED!!
}
const Child = props => {
const { parameter } = props;
return <Content>{parameter.code}</Content>;
};
Child.propTypes = {
parameter: PropTypes.object
};
export default React.memo(Child, areEqual);
Any ideas why?
In short, the reason of this behaviour is due to the way React works.
React expects a unique key for each of the components so it can keep track and know which is which. By using shortid.generate() a new value of the key is created, the reference to the component changes and React thinks that it is a completely new component, which needs rerendering.
In your case, on any change of props in the parent, React will renrender all of the children because the keys are going to be different for all of the children as compared to the previous render.
Please reference this wonderful answer to this topic
Hope this helps!
I was having the same issue and the solution turned out to be just a novice mistake. Your child components have to be outside of the parent component. So instead of:
function App() {
const [strVar, setStrVar] = useState("My state str");
const MyChild = React.memo(() => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello"); //Never called
});
return (
<MyChild/>
)
}
Do it like this:
const MyChild = React.memo(({strVar}) => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello");
});
function App() {
const [strVar, setStrVar] = useState("My state str");
return (
<MyChild strVar = {strVar}/>
)
}
Another possibility for unexpected renders when including an identifying key property on a child, and using React.memo (not related to this particular question but still, I think, useful to include here).
I think React will only do diffing on the children prop. Aside from this, the children prop is no different to any other property. So for this code, using myList instead of children will result in unexpected renders:
export default props => {
return (
<SomeComponent
myLlist={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
// And then somewhere in the MyComponent source code:
...
{ myList } // Instead of { children }
...
Whereas this code (below), will not:
export default props => {
return (
<SomeComponent
children={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
And that code is exactly the same as specifying the children prop on MyComponent implicitly (except that ES Lint doesn't complain):
export default props => {
return (
<SomeComponent>
{props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)}
</SomeComponent>
)
}
I don't know the rest of your library but I did some changes and your code and (mostly) seems to work. So, maybe, it can help you to narrow down the cause.
https://codesandbox.io/s/cocky-sun-rid8o

React - Forwarding multiple refs

I have a SideNav component that contains dynamically created links that need to scroll to a corresponding header in an adjacent html table (InfoTable). I've tried multiple different ways of accomplishing this to no avail.
export default class Parent extends Component {
state = {
categories: [],
}
scrollToHeader = (tableRefString) => {
// Function to pass to SideNav to access refs in InfoTable
this[tableRefString].scrollIntoView({ block: 'start' });
}
render() {
return (
<div>
<SideNav
categories={this.state.categories}
scrollToHeader={this.scrollToHeader} />
<InfoTable
categories={this.state.categories} />
</div>
);
}
}
export default class InfoTable extends Component {
render() {
return (
<div>
<table>
<tbody>
{this.props.categories.map(category => (
<>
// Forward the ref through InfoTableHeader to be set on the parent DOM node of each InfoTableHeader
<InfoTableHeader />
{category.inputs.map(input => <InfoTableRow />)}
</>
))}
</tbody>
</table>
</div>
);
}
}
In order to click a link on SideNav and scroll to the corresponding header on InfoTable, I believe that I need to forward refs that are dynamically created on Parent based on names in my categories array and set these refs to the DOM nodes for each header in InfoTable. From there I would pass a function to SideNav that could access the refs in Parent in order to scroll to the header.
How can I go about forwarding multiple refs at once to my InfoTable component?
Is there a cleaner way to accomplish what I'm trying to do? I've looked into React.findDOMNode() but refs seem to be the better option.
I know there is an already accepted answer, and while I find #nicholas-haley's solution acceptable.
I think a better way to go about it would be to use the built-in useImperativeHandle hook.
IMPORTANT: The React Hooks Api is available as of
react#16.8.0 and later
react-native#0.59.0 and later
The React hooks API Docs state:
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with `forwardRef
This note is followed by the following example:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
Thus, in my opinion, a much cleaner solution would be to delegate the needed refs through the useImperativeHandle hook.
This way there is no need for a special ref syntax, and the component can simply return a specific type of a FatherRef; Example:
// LabelInput.js
function LabelInput(props, ref) {
const labelRef = useRef();
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
get input() {
return inputRef.current;
},
get label() {
return labelRef.current;
},
// ... whatever else one may need
}));
return (
<div>
<label ref={labelRef} ... />
<input ref={inputRef} ... />;
</div>
)
}
LabelInput = forwardRef(LabelInput);
function MyScreen() {
const labelInputRef = useRef();
const onClick = useCallback(
() => {
// labelInputRef.current.focus(); // works
// labelInputRef.current.input.focus(); // works
// ... etc
},
[]
);
return (
...
<LabelInput ref={labelInputRef} ... />
....
)
}
I had a similar situation where I needed multiple refs to be forwarded to the child of my Parent component.
I still have not found an elegant solution, however you might try passing your refs as an object, and destructuring within the forwardRef callback:
// Parent
ref={{
ref1: this.ref1,
ref2: this.ref2
}}
// Child
export default React.forwardRef((props, ref) => {
const { ref1, ref2 } = ref;
return (
<Child1
{...props}
ref={ref1}
/>
<Child2
{...props}
ref={ref2}
/>
);
});
I'm not a big fan of the naming here (I'd prefer ref to be called refs), but this works in case you're in a pinch.
EDIT:
In 2020 I believe #samer-murad's answer is the best solution to this problem.
I actually just picked this up from react-hook-form, but they presented a nice way to share ref and assign multiple refs at once:
<input name="firstName" ref={(e) => {
register(e) // use ref for register function
firstNameRef.current = e // and you can still assign to ref
}} />
I still don't understand what goes on in that ref property, but the following React source code typeof type.render === 'function' + the other answers led me to try passing a function of hoisted refs from parent, and it works!
class Child extends React.Component {
render() {
return (
<div>
<div
ref={this.props.pa}
style={this.props.style}
onClick={async () => {
this.storeAuth(...this.props.storableAuth);
this.props.clearStore();
}}
/>
<div
ref={this.props.fwd}
style={this.props.style}
onClick={async () => {
this.props.onStart();
const res = await this.getUserInfo(verbose, auth, storedAuth);
if (res === "login?") this.props.onPromptToLogin();
if (res) this.props.onFinish(); //res.isAnonymous
}}
/>
</div>
);
}
}
export default React.forwardRef((props, getRefs) => {
const { pa, fwd } = props.getRefs();
return <Child fwd={fwd} pa={pa} {...props} />;
});
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.pa = React.createRef();
this.fwd = React.createRef();
}
render() {
return (
<Child
getRefs={() => {
return {
pa: this.pa,
fwd: this.fwd
};
}}
storableAuth={this.state.storableAuth}//[]
clearAuth={() => this.setState({ storableAuth: null })}
/>
);
}
}
This is not exactly what the author of this question asked, but this title could feet to this question as well: how do I allow developers using my React component to pass a ref if I pass ref internally in my component also (mean, passing multiple refs that will get a ref for this element),
this is the solution i came with:
import { useState, useRef } from "react";
export default function App() {
const [, render] = useState({});
const reRender = () => render({});
const divRef = useRef();
console.log("App", divRef);
return <Component reRender={reRender} passPropsToDiv={{ ref: divRef }} />;
}
const Component = ({ passPropsToDiv, reRender }) => {
const div1Ref = useRef();
const { ref: extraRef = null, ...passPropsToDivNoRef } = passPropsToDiv ?? {};
extraRef.current = div1Ref.current;
console.log("Component", div1Ref);
return (
<div className="App">
<div ref={div1Ref} {...passPropsToDivNoRef}>
i will have a ref
</div>
<button onClick={reRender}>reRender</button>
</div>
);
};
codesandbox: https://codesandbox.io/s/react-use-pass-multiple-refs-legm7p?file=/src/App.js

Linking React components using props.childern [duplicate]

I'm trying to find the proper way to define some components which could be used in a generic way:
<Parent>
<Child value="1">
<Child value="2">
</Parent>
There is a logic going on for rendering between parent and children components of course, you can imagine <select> and <option> as an example of this logic.
This is a dummy implementation for the purpose of the question:
var Parent = React.createClass({
doSomething: function(value) {
},
render: function() {
return (<div>{this.props.children}</div>);
}
});
var Child = React.createClass({
onClick: function() {
this.props.doSomething(this.props.value); // doSomething is undefined
},
render: function() {
return (<div onClick={this.onClick}></div>);
}
});
The question is whenever you use {this.props.children} to define a wrapper component, how do you pass down some property to all its children?
Cloning children with new props
You can use React.Children to iterate over the children, and then clone each element with new props (shallow merged) using React.cloneElement.
See the code comment why I don't recommend this approach.
const Child = ({ childName, sayHello }) => (
<button onClick={() => sayHello(childName)}>{childName}</button>
);
function Parent({ children }) {
// We pass this `sayHello` function into the child elements.
function sayHello(childName) {
console.log(`Hello from ${childName} the child`);
}
const childrenWithProps = React.Children.map(children, child => {
// Checking isValidElement is the safe way and avoids a
// typescript error too.
if (React.isValidElement(child)) {
return React.cloneElement(child, { sayHello });
}
return child;
});
return <div>{childrenWithProps}</div>
}
function App() {
// This approach is less type-safe and Typescript friendly since it
// looks like you're trying to render `Child` without `sayHello`.
// It's also confusing to readers of this code.
return (
<Parent>
<Child childName="Billy" />
<Child childName="Bob" />
</Parent>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="container"></div>
Calling children as a function
Alternatively, you can pass props to children via render props. In this approach, the children (which can be children or any other prop name) is a function which can accept any arguments you want to pass and returns the actual children:
const Child = ({ childName, sayHello }) => (
<button onClick={() => sayHello(childName)}>{childName}</button>
);
function Parent({ children }) {
function sayHello(childName) {
console.log(`Hello from ${childName} the child`);
}
// `children` of this component must be a function
// which returns the actual children. We can pass
// it args to then pass into them as props (in this
// case we pass `sayHello`).
return <div>{children(sayHello)}</div>
}
function App() {
// sayHello is the arg we passed in Parent, which
// we now pass through to Child.
return (
<Parent>
{(sayHello) => (
<React.Fragment>
<Child childName="Billy" sayHello={sayHello} />
<Child childName="Bob" sayHello={sayHello} />
</React.Fragment>
)}
</Parent>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="container"></div>
For a slightly cleaner way to do it, try:
<div>
{React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>
Edit:
To use with multiple individual children (the child must itself be a component) you can do. Tested in 16.8.6
<div>
{React.cloneElement(this.props.children[0], { loggedIn: true, testPropB: true })}
{React.cloneElement(this.props.children[1], { loggedIn: true, testPropA: false })}
</div>
Try this
<div>{React.cloneElement(this.props.children, {...this.props})}</div>
It worked for me using react-15.1.
Use {...this.props} is suggested in https://reactjs.org/docs/jsx-in-depth.html#spread-attributes
Pass props to direct children.
See all other answers
Pass shared, global data through the component tree via context
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language. 1
Disclaimer: This is an updated answer, the previous one used the old context API
It is based on Consumer / Provide principle. First, create your context
const { Provider, Consumer } = React.createContext(defaultValue);
Then use via
<Provider value={/* some value */}>
{children} /* potential consumers */
</Provider>
and
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
All Consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant Consumers is not subject to the shouldComponentUpdate method, so the Consumer is updated even when an ancestor component bails out of the update. 1
Full example, semi-pseudo code.
import React from 'react';
const { Provider, Consumer } = React.createContext({ color: 'white' });
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: { color: 'black' },
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
class Toolbar extends React.Component {
render() {
return (
<div>
<p> Consumer can be arbitrary levels deep </p>
<Consumer>
{value => <p> The toolbar will be in color {value.color} </p>}
</Consumer>
</div>
);
}
}
1 https://facebook.github.io/react/docs/context.html
Passing Props to Nested Children
With the update to React Hooks you can now use React.createContext and useContext.
import * as React from 'react';
// React.createContext accepts a defaultValue as the first param
const MyContext = React.createContext();
functional Parent(props) {
const doSomething = React.useCallback((value) => {
// Do something here with value
}, []);
return (
<MyContext.Provider value={{ doSomething }}>
{props.children}
</MyContext.Provider>
);
}
function Child(props: { value: number }) {
const myContext = React.useContext(MyContext);
const onClick = React.useCallback(() => {
myContext.doSomething(props.value);
}, [props.value, myContext.doSomething]);
return (
<div onClick={onClick}>{props.value}</div>
);
}
// Example of using Parent and Child
import * as React from 'react';
function SomeComponent() {
return (
<Parent>
<Child value={1} />
<Child value={2} />
</Parent>
);
}
React.createContext shines where React.cloneElement case couldn't handle nested components
function SomeComponent() {
return (
<Parent>
<Child value={1} />
<SomeOtherComp>
<Child value={2} />
</SomeOtherComp>
</Parent>
);
}
The best way, which allows you to make property transfer is children like a function pattern
https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9
Code snippet: https://stackblitz.com/edit/react-fcmubc
Example:
const Parent = ({ children }) => {
const somePropsHere = {
style: {
color: "red"
}
// any other props here...
}
return children(somePropsHere)
}
const ChildComponent = props => <h1 {...props}>Hello world!</h1>
const App = () => {
return (
<Parent>
{props => (
<ChildComponent {...props}>
Bla-bla-bla
</ChildComponent>
)}
</Parent>
)
}
You can use React.cloneElement, it's better to know how it works before you start using it in your application. It's introduced in React v0.13, read on for more information, so something along with this work for you:
<div>{React.cloneElement(this.props.children, {...this.props})}</div>
So bring the lines from React documentation for you to understand how it's all working and how you can make use of them:
In React v0.13 RC2 we will introduce a new API, similar to
React.addons.cloneWithProps, with this signature:
React.cloneElement(element, props, ...children);
Unlike cloneWithProps, this new function does not have any magic
built-in behavior for merging style and className for the same reason
we don't have that feature from transferPropsTo. Nobody is sure what
exactly the complete list of magic things are, which makes it
difficult to reason about the code and difficult to reuse when style
has a different signature (e.g. in the upcoming React Native).
React.cloneElement is almost equivalent to:
<element.type {...element.props} {...props}>{children}</element.type>
However, unlike JSX and cloneWithProps, it also preserves refs. This
means that if you get a child with a ref on it, you won't accidentally
steal it from your ancestor. You will get the same ref attached to
your new element.
One common pattern is to map over your children and add a new prop.
There were many issues reported about cloneWithProps losing the ref,
making it harder to reason about your code. Now following the same
pattern with cloneElement will work as expected. For example:
var newChildren = React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, { foo: true })
});
Note: React.cloneElement(child, { ref: 'newRef' }) DOES override the
ref so it is still not possible for two parents to have a ref to the
same child, unless you use callback-refs.
This was a critical feature to get into React 0.13 since props are now
immutable. The upgrade path is often to clone the element, but by
doing so you might lose the ref. Therefore, we needed a nicer upgrade
path here. As we were upgrading callsites at Facebook we realized that
we needed this method. We got the same feedback from the community.
Therefore we decided to make another RC before the final release to
make sure we get this in.
We plan to eventually deprecate React.addons.cloneWithProps. We're not
doing it yet, but this is a good opportunity to start thinking about
your own uses and consider using React.cloneElement instead. We'll be
sure to ship a release with deprecation notices before we actually
remove it so no immediate action is necessary.
more here...
I needed to fix accepted answer above to make it work using that instead of this pointer. This within the scope of map function didn't have doSomething function defined.
var Parent = React.createClass({
doSomething: function() {
console.log('doSomething!');
},
render: function() {
var that = this;
var childrenWithProps = React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, { doSomething: that.doSomething });
});
return <div>{childrenWithProps}</div>
}})
Update: this fix is for ECMAScript 5, in ES6 there is no need in var that=this
Method 1 - clone children
const Parent = (props) => {
const attributeToAddOrReplace= "Some Value"
const childrenWithAdjustedProps = React.Children.map(props.children, child =>
React.cloneElement(child, { attributeToAddOrReplace})
);
return <div>{childrenWithAdjustedProps }</div>
}
Full Demo
Method 2 - use composable context
Context allows you to pass a prop to a deep child component without explicitly passing it as a prop through the components in between.
Context comes with drawbacks:
Data doesn't flow in the regular way - via props.
Using context creates a contract between the consumer and the provider. It might be more difficult to understand and replicate the requirements needed to reuse a component.
Using a composable context
export const Context = createContext<any>(null);
export const ComposableContext = ({ children, ...otherProps }:{children:ReactNode, [x:string]:any}) => {
const context = useContext(Context)
return(
<Context.Provider {...context} value={{...context, ...otherProps}}>{children}</Context.Provider>
);
}
function App() {
return (
<Provider1>
<Provider2>
<Displayer />
</Provider2>
</Provider1>
);
}
const Provider1 =({children}:{children:ReactNode}) => (
<ComposableContext greeting="Hello">{children}</ComposableContext>
)
const Provider2 =({children}:{children:ReactNode}) => (
<ComposableContext name="world">{children}</ComposableContext>
)
const Displayer = () => {
const context = useContext(Context);
return <div>{context.greeting}, {context.name}</div>;
};
None of the answers address the issue of having children that are NOT React components, such as text strings. A workaround could be something like this:
// Render method of Parent component
render(){
let props = {
setAlert : () => {alert("It works")}
};
let childrenWithProps = React.Children.map( this.props.children, function(child) {
if (React.isValidElement(child)){
return React.cloneElement(child, props);
}
return child;
});
return <div>{childrenWithProps}</div>
}
Cleaner way considering one or more children
<div>
{ React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
If you have multiple children you want to pass props to, you can do it this way, using the React.Children.map:
render() {
let updatedChildren = React.Children.map(this.props.children,
(child) => {
return React.cloneElement(child, { newProp: newProp });
});
return (
<div>
{ updatedChildren }
</div>
);
}
If your component is having just one child, there's no need for mapping, you can just cloneElement straight away:
render() {
return (
<div>
{
React.cloneElement(this.props.children, {
newProp: newProp
})
}
</div>
);
}
Parent.jsx:
import React from 'react';
const doSomething = value => {};
const Parent = props => (
<div>
{
!props || !props.children
? <div>Loading... (required at least one child)</div>
: !props.children.length
? <props.children.type {...props.children.props} doSomething={doSomething} {...props}>{props.children}</props.children.type>
: props.children.map((child, key) =>
React.cloneElement(child, {...props, key, doSomething}))
}
</div>
);
Child.jsx:
import React from 'react';
/* but better import doSomething right here,
or use some flux store (for example redux library) */
export default ({ doSomething, value }) => (
<div onClick={() => doSomething(value)}/>
);
and main.jsx:
import React from 'react';
import { render } from 'react-dom';
import Parent from './Parent';
import Child from './Child';
render(
<Parent>
<Child/>
<Child value='1'/>
<Child value='2'/>
</Parent>,
document.getElementById('...')
);
see example here: https://plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p=preview
Got inspired by all the answers above and this is what I have done. I am passing some props like some data, and some components.
import React from "react";
const Parent = ({ children }) => {
const { setCheckoutData } = actions.shop;
const { Input, FieldError } = libraries.theme.components.forms;
const onSubmit = (data) => {
setCheckoutData(data);
};
const childrenWithProps = React.Children.map(
children,
(child) =>
React.cloneElement(child, {
Input: Input,
FieldError: FieldError,
onSubmit: onSubmit,
})
);
return <>{childrenWithProps}</>;
};
Here's my version that works with single, multiple, and invalid children.
const addPropsToChildren = (children, props) => {
const addPropsToChild = (child, props) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, props);
} else {
console.log("Invalid element: ", child);
return child;
}
};
if (Array.isArray(children)) {
return children.map((child, ix) =>
addPropsToChild(child, { key: ix, ...props })
);
} else {
return addPropsToChild(children, props);
}
};
Usage example:
https://codesandbox.io/s/loving-mcclintock-59emq?file=/src/ChildVsChildren.jsx:0-1069
Further to #and_rest answer, this is how I clone the children and add a class.
<div className="parent">
{React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>
Maybe you can also find useful this feature, though many people have considered this as an anti-pattern it still can be used if you're know what you're doing and design your solution well.
Function as Child Components
I think a render prop is the appropriate way to handle this scenario
You let the Parent provide the necessary props used in child component, by refactoring the Parent code to look to something like this:
const Parent = ({children}) => {
const doSomething(value) => {}
return children({ doSomething })
}
Then in the child Component you can access the function provided by the parent this way:
class Child extends React {
onClick() => { this.props.doSomething }
render() {
return (<div onClick={this.onClick}></div>);
}
}
Now the fianl stucture will look like this:
<Parent>
{(doSomething) =>
(<Fragment>
<Child value="1" doSomething={doSomething}>
<Child value="2" doSomething={doSomething}>
<Fragment />
)}
</Parent>
The slickest way to do this:
{React.cloneElement(this.props.children, this.props)}
According to the documentation of cloneElement()
React.cloneElement(
element,
[props],
[...children]
)
Clone and return a new React element using element as the starting
point. The resulting element will have the original element’s props
with the new props merged in shallowly. New children will replace
existing children. key and ref from the original element will be
preserved.
React.cloneElement() is almost equivalent to:
<element.type {...element.props} {...props}>{children}</element.type>
However, it also preserves refs. This means that if you get a child
with a ref on it, you won’t accidentally steal it from your ancestor.
You will get the same ref attached to your new element.
So cloneElement is what you would use to provide custom props to the children. However there can be multiple children in the component and you would need to loop over it. What other answers suggest is for you to map over them using React.Children.map. However React.Children.map unlike React.cloneElement changes the keys of the Element appending and extra .$ as the prefix. Check this question for more details: React.cloneElement inside React.Children.map is causing element keys to change
If you wish to avoid it, you should instead go for the forEach function like
render() {
const newElements = [];
React.Children.forEach(this.props.children,
child => newElements.push(
React.cloneElement(
child,
{...this.props, ...customProps}
)
)
)
return (
<div>{newElements}</div>
)
}
You no longer need {this.props.children}. Now you can wrap your child component using render in Route and pass your props as usual:
<BrowserRouter>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/posts">Posts</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
<hr/>
<Route path="/" exact component={Home} />
<Route path="/posts" render={() => (
<Posts
value1={1}
value2={2}
data={this.state.data}
/>
)} />
<Route path="/about" component={About} />
</div>
</BrowserRouter>
For any one who has a single child element this should do it.
{React.isValidElement(this.props.children)
? React.cloneElement(this.props.children, {
...prop_you_want_to_pass
})
: null}
When using functional components, you will often get the TypeError: Cannot add property myNewProp, object is not extensible error when trying to set new properties on props.children. There is a work around to this by cloning the props and then cloning the child itself with the new props.
const MyParentComponent = (props) => {
return (
<div className='whatever'>
{props.children.map((child) => {
const newProps = { ...child.props }
// set new props here on newProps
newProps.myNewProp = 'something'
const preparedChild = { ...child, props: newProps }
return preparedChild
})}
</div>
)
}
Is this what you required?
var Parent = React.createClass({
doSomething: function(value) {
}
render: function() {
return <div>
<Child doSome={this.doSomething} />
</div>
}
})
var Child = React.createClass({
onClick:function() {
this.props.doSome(value); // doSomething is undefined
},
render: function() {
return <div onClick={this.onClick}></div>
}
})
I came to this post while researching for a similar need, but i felt cloning solution that is so popular, to be too raw and takes my focus away from the functionality.
I found an article in react documents Higher Order Components
Here is my sample:
import React from 'react';
const withForm = (ViewComponent) => {
return (props) => {
const myParam = "Custom param";
return (
<>
<div style={{border:"2px solid black", margin:"10px"}}>
<div>this is poc form</div>
<div>
<ViewComponent myParam={myParam} {...props}></ViewComponent>
</div>
</div>
</>
)
}
}
export default withForm;
const pocQuickView = (props) => {
return (
<div style={{border:"1px solid grey"}}>
<div>this is poc quick view and it is meant to show when mouse hovers over a link</div>
</div>
)
}
export default withForm(pocQuickView);
For me i found a flexible solution in implementing the pattern of Higher Order Components.
Of course it depends on the functionality, but it is good if someone else is looking for a similar requirement, it is much better than being dependent on raw level react code like cloning.
Other pattern that i actively use is the container pattern. do read about it, there are many articles out there.
In case anyone is wondering how to do this properly in TypeScript where there are one or multiple child nodes. I am using the uuid library to generate unique key attributes for the child elements which, of course, you don't need if you're only cloning one element.
export type TParentGroup = {
value?: string;
children: React.ReactElement[] | React.ReactElement;
};
export const Parent = ({
value = '',
children,
}: TParentGroup): React.ReactElement => (
<div className={styles.ParentGroup}>
{Array.isArray(children)
? children.map((child) =>
React.cloneElement(child, { key: uuidv4(), value })
)
: React.cloneElement(children, { value })}
</div>
);
As you can see, this solution takes care of rendering an array of or a single ReactElement, and even allows you to pass properties down to the child component(s) as needed.
Some reason React.children was not working for me. This is what worked for me.
I wanted to just add a class to the child. similar to changing a prop
var newChildren = this.props.children.map((child) => {
const className = "MenuTooltip-item " + child.props.className;
return React.cloneElement(child, { className });
});
return <div>{newChildren}</div>;
The trick here is the React.cloneElement. You can pass any prop in a similar manner
Render props is most accurate approach to this problem. Instead of passing the child component to parent component as children props, let parent render child component manually. Render is built-in props in react, which takes function parameter. In this function you can let parent component render whatever you want with custom parameters. Basically it does the same thing as child props but it is more customizable.
class Child extends React.Component {
render() {
return <div className="Child">
Child
<p onClick={this.props.doSomething}>Click me</p>
{this.props.a}
</div>;
}
}
class Parent extends React.Component {
doSomething(){
alert("Parent talks");
}
render() {
return <div className="Parent">
Parent
{this.props.render({
anythingToPassChildren:1,
doSomething: this.doSomething})}
</div>;
}
}
class Application extends React.Component {
render() {
return <div>
<Parent render={
props => <Child {...props} />
}/>
</div>;
}
}
Example at codepen
There are lot of ways to do this.
You can pass children as props in parent.
example 1 :
function Parent({ChildElement}){
return <ChildElement propName={propValue} />
}
return <Parent ChildElement={ChildComponent}/>
Pass children as Function
example 2 :
function Parent({children}){
return children({className: "my_div"})
}
OR
function Parent({children}){
let Child = children
return <Child className='my_div' />
}
function Child(props){
return <div {...props}></div>
}
export <Parent>{props => <Child {...props} />}</Parent>
I did struggle to have the listed answers work but failed. Eventually, I found out that the issue is with correctly setting up the parent-child relationship. Merely nesting components inside other components does not mean that there is a parent-child relationship.
Example 1. Parent-child relationship;
function Wrapper() {
return (
<div>
<OuterComponent>
<InnerComponent />
</OuterComponent>
</div>
);
}
function OuterComponent(props) {
return props.children;
}
function InnerComponent() {
return <div>Hi! I'm in inner component!</div>;
}
export default Wrapper;
Example 2. Nested components:
function Wrapper() {
return (
<div>
<OuterComponent />
</div>
);
}
function OuterComponent(props) {
return <InnerComponent />
}
function InnerComponent() {
return <div>Hi! I'm in inner component!</div>;
}
export default Wrapper;
As I said above, props passing works in Example 1 case.
The article below explains it https://medium.com/#justynazet/passing-props-to-props-children-using-react-cloneelement-and-render-props-pattern-896da70b24f6

Resources