On my follow up question from here : How to pass data from child to parent component using react hooks
I have another issue.
Below is the component structure
export const Parent: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const createContent = (): JSX.Element => {
return (
<Authorization>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
<MyChildComponent/>
</<Authorization>
);
}
return (
<Button onClick={onSubmit} disabled={disabled}>My Button</Button>
{createContent()}
);
};
const Authorization: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{props.children}
</>
);
};
How do I pass the disabled state value from Authorization component to my child which is invoked by
{props.children}
I tried React.cloneElement & React.createContext but I'm not able to get the value disabled to the MyChildComponent. I could see the value for disabled as true once the errorMessage is set through the ErrorPanel in the Authorization component.
Do I need to have React.useEffect in the Authorization Component?
What am I missing here?
You need to use React.Children API with React.cloneElement:
const Authorization = ({ children }) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage) => {
return (
<>{<ErrorPanel message={errorMessage} setDisabled={setDisabled} />}</>
);
};
return (
<>
<PageLoader queryResult={apiQuery} renderPage={render} />
{React.Children.map(children, (child) =>
React.cloneElement(child, { disabled })
)}
</>
);
};
// |
// v
// It will inject `disabled` prop to every component's child:
<>
<ErrorPanel
disabled={disabled}
message={errorMessage}
setDisabled={setDisabled}
/>
<MyChildComponent disabled={disabled} />
</>
You can make use of React.cloneElement to React.Children.map to pass on the disabled prop to the immediate children components
const Authorization: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} setDisabled={setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{React.Children.map(props.children, child => {
return React.cloneElement(child, { disabled })
})}
</>
);
};
UPDATE:
Since you wish to update the parent state to, you should store the state and parent and update it there itself, instead of storing the state in child component too.
export const Parent: React.FC<Props> = (props) => {
const [disabled, setDisabled] = React.useState(false);
const createContent = (): JSX.Element => {
return (
<Authorization setDisabled={setDisabled}>
{<ErrorPanel message={errorMessage} disabled={disabled} setDisabled={setDisabled}/>}
<MyChildComponent disabled={disabled}/>
</<Authorization>
);
}
return (
<Button onClick={onSubmit} disabled={disabled}>My Button</Button>
{createContent()}
);
};
const Authorization: React.FC<Props> = (props) => {
const render = (errorMessage : JSX.Element): JSX.Element => {
return (
<>
{<ErrorPanel message={errorMessage} disabled={props.disabled} setDisabled={props.setDisabled}/>}
</>
);
};
return (
<>
<PageLoader
queryResult={apiQuery}
renderPage={render}
/>
{props.children}
</>
);
};
Related
I have this layout component , I am trying to pass a prop from layout to its children.
const Layout = ({ children }: Props) => {
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState(countries[0]);
console.log("The country idsz ...........",selected.id)
const country= selected.id
const modifiedChildren = React.Children.map(children, child => {
if (React.isValidElement(child)) {
//#ts-ignore
return React.cloneElement(child, { testProp : country });
}
return child;
});
return (
<>
<LayoutContent sidebar={open} countriesWithsrc ={countriesWithsrc} selected={selected} lected={setSelected} >
{modifiedChildren}
</LayoutContent>
</>
)
}
export default Layout;
How can I access the modifiedChildren in pages? An example is the following page.
const ComingSoonCarbon = () => {
return (
<Layout>
<div className="flex justify-center font-bold mt-80 text-xl text-[rgb(245,132,38,0.93)]">
<h1>Development ongoing.Coming soon #2022</h1>
</div>
</Layout>
)
}
export default ComingSoonCarbon
What I would like to be able to do is to initialize my context with a state and a function that updates that state.
For example, say I have the following:
export default function MyComponent () {
const MyContext = React.createContext()
const [myState, setMyState] = useState('1')
const contextValue = {
currentValue: myState,
setCurrentValue: (newValue) => setMyState(newValue)
}
return (
<MyContext.Provider value={contextValue}>
<MyContext.Consumer>
{e => <div onClick={() => e.setCurrentValue('2')}> Click me to change the value </div>}
{e.currentValue}
</MyContext.Consumer>
</MyContext.Provider>
)
}
The {e.currentValue} correctly outputs '1' at first, but when I click the button, nothing changes.
What I would expect is that e.setCurrentValue('2') would call setMyState('2'), which would update the state hook. This would then change the value of myState, changing the value of currentValue, and making '2' display.
What am I doing wrong?
You would want to return a fragment from the context as one JSX root.
Check here - https://playcode.io/931263/
import React, { createContext, useState } from "react";
export function App(props) {
const MyContext = React.createContext();
const [myState, setMyState] = useState("1");
const contextValue = {
currentValue: myState,
setCurrentValue: newValue => setMyState(newValue)
};
return (
<MyContext.Provider value={contextValue}>
<MyContext.Consumer>
{e => (
<>
<div onClick={() => e.setCurrentValue("2")}>
Click me to change the value
</div>
{e.currentValue}
</>
)}
</MyContext.Consumer>
</MyContext.Provider>
);
}
You're using e.currentValue outside of MyContext.Consumer context which does not have e, so it's throwing an error that e is not defined from e.currentValue.
You can wrap them up together under <MyContext.Consumer>{e => {}}</MyContext.Consumer>
function MyComponent() {
const MyContext = React.createContext();
const [myState, setMyState] = React.useState("1");
const contextValue = {
currentValue: myState,
setCurrentValue: (newValue) => setMyState(newValue),
};
return (
<MyContext.Provider value={contextValue}>
<MyContext.Consumer>
{(e) => (
<div>
<div onClick={() => e.setCurrentValue("2")}>
Click me to change the value
</div>
<div>{e.currentValue}</div>
</div>
)}
</MyContext.Consumer>
</MyContext.Provider>
);
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
So basically I have 2 pieces, the sidebar, then the opener. I'm trying to setup a ref that will connect the sidebar to the current opener. The opener is a functional component, and no matter what I do the current value is null. Am I missing something? I'm just trying to resize a component. My goal is to be able to resize the shown sidebar with the opener.
Here's part of the Render function.
render() {
const { selected, isSidebar, selectedType, search, active } = this.state;
const { pending, callback, resource } = this.props;
const pendingLengh = pending ? pending.length : 0;
const callbackLength = callback ? callback.length : 0;
const isResource = !resource || !Object.keys(resource).length;
return (
<div className="newPatientPage mainPage">
{this.renderMetadata()}
<SubTopBar
title="New Patient Processing"
noLeftSide={true}
subStatus={this.getStatus(pendingLengh, callbackLength)}
isBarcode={!isResource}
sideComponent={this.renderSideComponent()}
/>
{
active ?
<SnapshotSideBar
ref={this.sidebarRef}
patientResource={this.props.patientResource}
isShow={isSidebar}
settup={this.state.settup}
isScan={true}
handleCloseSidebar={this.handleCloseSidebar}
/> :
<NewPatientSideBar
ref={this.sidebarRef}
stepProps={this.state.stepProps}
selected={selected}
isShow={isSidebar}
handleCloseSidebar={this.handleCloseSidebar}
/>
}
<SidebarExtension sidebarToggle={this.toggleSidebar} sidebarReference={this.sidebarRef} sidebarState={isSidebar}/>
Here's the SidebarExtension component
const SidebarExtension = ({
sidebarToggle,
sidebarReference,
sidebarState,
...restProps
}) => {
const [xPos, setXPos] = useState(0);
const [width, setWidth] = useState();
const [openerPosition, setOpenerPosition] = useState(50);
const [isOpen, setIsOpen] = useState(false);
const toggleSidebar = () => {
sidebarToggle();
setIsOpen(!isOpen);
};
useEffect(() => {
setIsOpen(sidebarState);
}, [sidebarState])
if ((!isOpen && !sidebarState)) {
return (
<>
<div
className="resizeHandle"
style={{
right: "0Px",
}}
onClick={toggleSidebar}
>
<LeftCharvenSVG />
</div>
</>
);
}
return (
<>
<div
className="resizeHandle active"
onClick={toggleSidebar}
onMouseDown={startResize}
>
<LeftCharvenSVG />
</div>
</>
);
};
export default SidebarExtension;
Here's what the constructor looks like.
Main Constructor
From the docs https://reactjs.org/docs/forwarding-refs.html you need to wrap your functional component in React.forwardRef()
Example
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
In your case that would be:
const SidebarExtension = React.forwardRef(({
sidebarToggle,
sidebarReference,
sidebarState,
...restProps
}, ref) => {
const [xPos, setXPos] = useState(0);
const [width, setWidth] = useState();
const [openerPosition, setOpenerPosition] = useState(50);
const [isOpen, setIsOpen] = useState(false);
const toggleSidebar = () => {
sidebarToggle();
setIsOpen(!isOpen);
};
useEffect(() => {
setIsOpen(sidebarState);
}, [sidebarState])
if ((!isOpen && !sidebarState)) {
return (
<>
<div
className="resizeHandle"
style={{
right: "0Px",
}}
ref={ref}
onClick={toggleSidebar}
>
<LeftCharvenSVG />
</div>
</>
);
}
return (
<>
<div
className="resizeHandle active"
onClick={toggleSidebar}
onMouseDown={startResize}
>
<LeftCharvenSVG />
</div>
</>
);
});
export default SidebarExtension;
const ListView = () => {
return(
<ul>
<ListItem modal={<Modal />} />
</ul>
)
};
const ListItem = (props) => {
const [visible, setVisible] = useState(false);
const toggle = () => setVisible(!visible)
return (
<>
<li>
ListItem
</li>
<ModalWrapper toggle={toggle}>{props.modal}</ModalWrapper>
</>
)
}
const ModalWrapper = (props) => {
if(!props.visible) return null;
return (
<>
{props.children}
</>
)
}
const Modal = ({ toggle }) => {
/* I would like to use toggle() here. */
return (
<>
<div onClick={toggle} className="dimmer"></div>
<div className="modal">modal</div>
</>
)
}
I have a function toggle() in <ListItem /> as shown above.
I am struggling to use toggle() in <Modal />.
Is it possible or are there any suggestions?
You need to inject toggle to ModalWrapper children, be careful not to override toggle prop on Modal after it.
const ModalWrapper = ({ children, visible, toggle }) => {
const injected = React.Children.map(children, child =>
React.cloneElement(child, { toggle })
);
return <>{visible && injected}</>;
};
Refer to React.cloneElement and React.Children.map.
Demo:
I have a simple HOC which injects a react context as a prop in the wrappedcomponent.
function withTranslate(WrappedComponent) {
//we forward the ref so it can be used by other
return React.forwardRef((props, ref) => (
<TranslatorContext.Consumer>
{context => (<WrappedComponent {...props} translate={context} ref={ref} />)}
</TranslatorContext.Consumer>)
)
}
Now I want a secondary HOC which uses the same context, but changes some predefined props using this context. I succeed with following code:
export function withTranslatedProps(WrappedComponent,propsToBeTransLated) {
//propsToBetranslated is array with all props which will be given via keys
const translateProps=(translate,props)=>{
const ownProps=Object.assign({},props)
propsToBeTransLated.forEach(p=>{
if(ownProps.hasOwnProperty(p)){
ownProps[p]=translate(ownProps[p])
}
})
return ownProps
}
return React.forwardRef((props, ref) => {
console.log("render contextconsumer")
return (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent {...translateProps(context,props)} ref={ref} />
)}
</TranslatorContext.Consumer>)
})
}
But I almost exactly use the same HOC as withTranslate. Is there a better option (without repeating myself) to do this?
edit
I think i solved it:
const _translateProps=(propsToBeTransLated,translate,props)=>{
const ownProps=Object.assign({},props)
propsToBeTransLated.forEach(p=>{
if(ownProps.hasOwnProperty(p)){
ownProps[p]=translate(ownProps[p])
}
})
return ownProps
}
export function withTranslatedProps(WrappedComponent,propsToBeTransLated) {
//propsToBetranslated is array with all props which will be given via keys
let retrieveProps=propsToBeTransLated?_translateProps.bind(null,propsToBeTransLated):(context,props)=>({translate:context,...props})
return React.forwardRef((props, ref) => {
console.log("render contextconsumer")
return (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent {...retrieveProps(context,props)} ref={ref} />
)}
</TranslatorContext.Consumer>)
})
}
Anyone with other possibly better solutions?
You can reuse withTranslate HOC or use the same HOC adding options.
Reusing withTranslate HOC:
/* function that translate the propsToBeTransLated */
const translateProps = (propsToBeTransLated, translate, props) =>
propsToBeTransLated.reduce((translatedProps, prop) => {
if(props.hasOwnProperty(prop))
translatedProps[prop] = translate(props[prop]);
return translatedProps;
}, {});
export function withTranslatedProps(WrappedComponent, propsToBeTransLated = []) {
// HOC inside HOC
const addTranslationsToProps = WrappedComponentWithContext =>
React.forwardRef((props, ref) => (
<WrappedComponentWithContext
{...props}
{...translateProps(propsToBeTransLated, props.translate, props)}
ref={ref}
/>
)
);
// first call withTranslate to add the context
return addTranslationsToProps(withTranslate(WrappedComponent));
}
Adding options to withTranslate HOC
const translateProps = (propsToBeTransLated, translate, props) =>
propsToBeTransLated.reduce((translatedProps, prop) => {
if(props.hasOwnProperty(prop))
translatedProps[prop] = translate(props[prop]);
return translatedProps;
}, {});
export function withTranslate(WrappedComponent, options) {
const { propsToBeTransLated = [] } = options;
return React.forwardRef((props, ref) => (
<TranslatorContext.Consumer>
{context => (
<WrappedComponent
{...props}
{...translateProps(propsToBeTransLated, context, props)}
translate={context}
ref={ref}
/>
)}
</TranslatorContext.Consumer>
));
}