How to create a portal to a child component? - reactjs

I would like to create a portal to insert a content in a child component but I can't make it work. I tried to use a callback ref as shown on this answer but it doesn't work (the content of the portal is not displayed). Here is a simplified example of what I have:
const Parent = () => (
<Child
ref={(el) => {
if (!el) return;
const selector = el.querySelector(".title");
createPortal("My title", selector);
}}
/>
);
const Child = forwardRef((_, ref) => (
<div ref={ref}>
<h2 className="title"></h2>
<p>Test content</p>
</div>
));
I also tried with useRef(), but to no avail.
const Parent = () => {
const childRef = useRef();
const renderTitle = () => {
const selector = childRef.current.querySelector(".title");
return createPortal("My title", selector);
}
return (
<Child ref={childRef}>
{childRef.current && renderTitle()}
</Child>
);
}
Is there a way to do this?
I know the obvious solution is to pass the title as a prop but the title prop of my real <Child /> component displays a simple text and I need a different display. I don't want to change <Child /> as it can be used without <Parent/> and I use the latter to deal with other extra functionalities, which include building a custom title dynamically.
Here is a CodeSandbox, if you want to try.

Portals are not for these purposes. You can just pass Component to the child component. Or use the function to generate that name.
const Parent = () => (
<Child
titleComponent={<TitleComponent />}
/>
);
const Child = ((titleComponent) => {
return (
<div ref={ref}>
<h2 className="title">{titleComponent}</h2>
<p>Test content</p>
</div>
)
});

I managed to make it work by storing the selector in a state using useEffect:
const Parent = () => {
const childRef = useRef();
const [titleContainer, setTitleContainer] = useState(null);
useEffect(() => {
const titleContainerSelector = childRef.current.querySelector(".title");
setTitleContainer(titleContainerSelector);
}, []);
return (
<Child ref={childRef}>
{titleContainer && createPortal("My title", titleContainer)}
</Child>
);
}
It doesn't work when I try this on CodeSandbox but it works on my real components. I'm not sure why. For the record, here is the answer that gave me the solution.

Related

Get data from child to parent while calling child multiple times in parent in React

I know i can use a handler function to get data from child to parent component. Like below
const Parent = () => {
const [message, setMessage] = React.useState("Hello World");
const chooseMessage = (message) => {
setMessage(message);
};
return (
<div>
<h1>{message}</h1>
<Child chooseMessage={chooseMessage} />
</div>
);
};
const Child = ({ chooseMessage }) => {
let msg = 'Goodbye';
return (
<div>
<button onClick={() => chooseMessage(msg)}>Change Message</button>
</div>
);
};
What if i used <Child /> multiple times in the same component? how would i handle the data in this case.
const Parent = () => {
const [message, setMessage] = React.useState("Hello World");
const chooseMessage = (message) => {
setMessage(message);
};
return (
<div>
<h1>{message}</h1>
<Child chooseMessage={chooseMessage} />
<Child chooseMessage={chooseMessage} />
<Child chooseMessage={chooseMessage} />
<Child chooseMessage={chooseMessage} />
</div>
);
};
What is the best approach to handle this.
Thanks in advance...
There is not much context you added to your question, so I'll try to answer it as best as I can.
You want to have multiple information being passed from N number of children to the parent, and (I assume) you cannot create as many states in Parent because the number is dynamic. What you want to do is to have an array as the state in the parent, and let each child have an index on that array and let it manipulate the element in the array at that index only.
https://codesandbox.io/s/hardcore-mclean-f8dzmf?file=/src/Parent.jsx
This is a simple demonstration of what could be done. I only changed Parent and did not touch Child at all. I'll leave the code below:
Parent.jsx
const Parent = () => {
const [messages, setMessage] = useState([
"Hello 1",
"Hello 2",
"Hello 3",
"Hello 4"
]); // ⭐(1)⭐
const chooseMessage = (message, index) => {
const newMessages = [...messages]; // ⭐(2)⭐
newMessages[index] = message;
setMessage(newMessages);
};
return (
<div>
{messages.map((message, index) => (
<h2 key={index}>{message}</h2>
))}
<!-- ⭐(3)⭐ -->
<Child key="0" chooseMessage={(message) => chooseMessage(message, 0)} />
<Child key="1" chooseMessage={(message) => chooseMessage(message, 1)} />
<Child key="2" chooseMessage={(message) => chooseMessage(message, 2)} />
<Child key="3" chooseMessage={(message) => chooseMessage(message, 3)} />
</div>
);
};
At (1), you can see that the state is an array. In your application, I assume the number of children is going to be dynamic, so start with an empty array and add elements to it as the number of children increases.
At (2), you can see that the setState function is a little bit weird. Why am I doing const newMessages = [...messages] instead of just const newMessages = messages? This is a way to get react to be reactive to the changes in the elements in the array because by default, it does not react to the changes in the elements of an array state, on the changes on the array itself.
At (3), you can see the chooseMessage being passed is a little bit different. So for the purpose of the demo, I just set the number of children to 4 and manually set the indices to each child, and created anonymous functions to pass down to the children so the code in Child does not have to be changed. In practice, I assume you will need to dynamically create these children through some kind of .map function as I did with the <h2> tags.
Hope this helps.

Page freezes when creating component from props

I am creating the Screens component in one file then passing it into the Modal component. Inside Modal I am trying to use Screens. Screens takes an index prop to tell it what to display. If I display props.screens (the Screens component) it works but I don't see a way to pass in props. If I try to display (screens) => () everything freezes.
File where Screens is defined...
const Screens = (props) => {
let index = props !== undefined ? props.index.idx : 0;
const [contentData, setContentData] = useState({});
let [indexState, setIndexState] = useState(index);
console.log(`indexState: ${indexState}`)
const onChange = (e) => {
const newData = {
...contentData,
[e.target.id]: e.target.value
}
setContentData(newData)
};
const Screen1 = () => {
return (
<>
<input type="text" id="screen1_input1" onChange={onChange} />
<br />
one
</>
)
};
const Screen2 = () => {
return (
<>
<input type="text" id="screen2_input1" onChange={onChange} />
<br />
<input type="text" id="screen2_input2" onChange={onChange} />
<br />
two
</>
)
};
const content = [
Screen1,
Screen2
];
return (
<>
<h2 className="screens">
{content[indexState]()}
</h2>
</>
);
}
This gets passed into as props.screens.
Modal.js
...
const screens = props.screens;
const Screens = (screens) => (<Screens index={{idx:0}}/>);
...
return (
{screens()} // works but doesn't let me pass in props
{Screens} // everything freezes
)
Really I just need a way to use Screens in Modal in a way that lets me dynamically send it props.
There is a lot of ways you could go about this, but here's just one:
// Modal.jsx
const Modal = ({ componentToRender }) => (
...JSX before component
{componentToRender()}
..JSX after component
)
// File where Modal is being rendered
const SomeComponent = () => <Modal componentToRender={() => <Screen id={1} />} />
There's a very large number of other ways to implement this same exact thing, you're more or less implementing a higher order component by the way you describe Modal.
Your original implementation in the second instance would work just fine, but you need to call Screens to get the returned component from the function:
...
const Screens = (screens) => (<Screens index={{idx:0}}/>);
...
return (
{Screens()}
)

how to add components into reusuable component? react

I'm not sure my title make sense :/ sorry for my poor description.
anyway what I was trying was.. making a reusuable component.
and the props of the reusuable component is another component.
here's what I did:
const Accordion = ({ title, content }) => {
return (
<Wrapper>
<Title>{title}</Title>
<Content>{content}</Content>
</Wrapper>
);
};
const ParentComponent = () => {
const title = () => <div>title</div>;
const content = () => {
return (<div><h2>...text...</h2><div>...text..</div></div>)
}
return <Accordion title={title} content={content} />;
};
it seems nice to me, but it does not work 🤯
the title and the content(...text...) was not showing at all.
it works in this way though, this is not what I want 🤯🤯
<Accordion title='title text' content='content context' />;
thanks for your help.
Just do this:
You need to render it like <element />
const Accordion = ({ title, content }) => {
return (
<Wrapper>
<Title><title /></Title>
<Content><content /></Content>
</Wrapper>
);
};

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