EDIT: This problem was caused because I was attempting to use forwardRef in conjunction with Redux connect. Take a look at this post for the solution.
I am attempting to access a DOM element in a parent component by using React.forwardRef. The problem is that ref.current never gets defined, even after the first render.
In this parent component, I want to access a DOM element from one of the child components:
const MyFunctionComponent = () => {
const bannerRef = useRef<HTMLDivElement>(null);
let bannerIndent = useRef<string>();
useEffect(() => {
// bannerRef.current is ALWAYS null. Why?
if (bannerRef.current) {
// this conditional block never runs, so bannerIndent is never defined
const bannerWidth = bannerRef.current.getBoundingClientRect().width;
bannerIndent.current = `${bannerWidth}px`;
}
}, []);
return (
<>
<Banner ref={bannerRef} />
<ComponentTwo bannerIndent={bannerIndent.current} />
</>
)
}
The component which receives the forwarded ref:
const Banner = React.forwardRef<HTMLDivElement, Props> ((props, ref) => (
<div ref={ref} />
));
The component which needs some data derived from the forwarded ref:
const ComponentTwo = (props: {bannerIndent?: string}) => (
<StyledDiv bannerIndent={props.bannerIndent} />
)
// styled.div is a styled-component
const StyledDiv = styled.div<{bannerIndent?: string}>`
... // styles, some of which depend on bannerIndent
`
Answers to existing SO questions which discuss forwardRef always being null or undefined point out that the ref will only be defined after the first render. I don't think that's the issue here, since the useEffect should run after the first render, yet bannerRef.current is still null.
The logic for setting the value of bannerIndent with a useEffect hook works correctly if used inside of the Banner component with a normal ref. However, I need to put the ref in the parent component so that bannerWidth (generated using the forwarded ref) can be passed to a sibling of the Banner component.
Help would be greatly appreciated :D
Related
I have a react child component (FinalReport.js) that is rendering twice, except that on the first render, one of the props is undefined for some reason, which is throwing an error. Of course I could add error handling but that doesn't seem like best practice.
The Parent Component contains user inputs which are saved as a useState Hook (esrData) upon pressing a 'Save' button. The first child component (Airload.js) contains more inputs and calls an API, and saves the result as a useStateHook (airLoadRes). Both hooks are defined in the parent component and passed as props. The child component in question (FinalReport.js) ONLY renders once both hooks become available, and then passes the hooks along. Why is FinalReport rendering twice and why is airLoadRes undefined on the first render? Strict Mode is not being used. Any help is appreciated!
Parent Component
const GenerateEnergySavings = () => {
const [esrData, setEsrData] = useState();
const [airLoadRes, setAirLoadRes] = useState();
...
return( ...
// Child Component 2
{(esrData && airLoadRes != undefined) ?
<PDFViewer height='1000px' width='1000px'>
<FinalReport esrData={esrData} airLoadRes={airLoadRes} />
</PDFViewer> : ''}
...
// Child Component 1 (API)
<Airload airLoadRes={airLoadRes} setAirLoadRes={setAirLoadRes} />
Child Component 1
EDIT: I should mention this is a bootstrap modal
const Airload = ({ airLoadRes, setAirLoadRes }) => {
...
// Airload API
const getAirLoadCalc = async () => {
console.log(airloadData)
await Axios.post('https://localhost:44418/airload', airloadData)
.then(res => {
setAirLoadRes(res.data)
console.log(res)
setKey(6)
}).catch(err => {
alert(err)
})
}
}
Child Component 2
// This is rendering twice!! ONLY airLoadRes comes in as undefined on first render
export const FinalReport = ({ esrData, airLoadRes }) => {
console.log(esrData)
console.log(airLoadRes)
...
This code (const [airLoadRes, setAirLoadRes] = useState();) initialize airLoadRes as undefined.
That's why it is undefined on first render.
React does render on each change of the state, context, or properties. So, I guess FinalReport is rendered twice because of changes on esrData state. Or other state which you possibly have in the code.
I am constructing some node objects in a function(prepareNodes) to pass to React Flow within a functional component A (lets say), and I have defined a custom node component(CardNode) stateless, which has a button. On button click it should trigger the function(prepareNodes) defined within Component A.
function ComponentA = ({ selectedNodes }) => {
const reactFlowWrapper = useRef(null);
const [elements, setElements] = useState([]);
const [edges, setEdges] = useState([]);
const prepareNode = async (nodeid) => {
//some service calls to fetch data and constuct nodes
setElements([ ...nodes]);
setEdges([...edges]);
}
return (
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={elements}
edges={edges}
//some properties
>
</ReactFlow>
</div>
</ReactFlowProvider>
)
};
export default ComponentA;
function CardNode({ data }) {
const renderSubFlowNodes = (id) => {
console.log(id);
//prepareNode(id)
}
return (
<>
<Handle type="target" position={Position.Top} />
<div className="flex node-wrapper">
<button className="btn-transparent btn-toggle-node" href="#" onClick={() => renderSubFlowNodes(data['id']) }>
<div>
<img src={Icon}/>
</div>
</button>
</div>
<Handle type="source" position={Position.Bottom}/>
</>
);
}
export default CardNode;
I looked for some references online, and most of them suggest to move this resuable function out of the component, but since this function carries a state that it directly sets to the ReactFlow using useState hook, I dont think it would be much of a help.
Other references talks about using useCallback or useRefs and forwardRef, useImperativeHandle especially for functional component, Which I did not quite understand well.
Can someone suggest me a solution or a work around for this specific use-case of mine.
You can add an onClick handler to the each node, and within the node view you call this handler on click.
In the parent Component within the onClick handler you can call prepareNode as needed.
useEffect(() => {
setElements(
elements.map(item => {
...item,
onClick: (i) => {
console.log(i);
prepareNode();
},
})
)},
[]);
The classical approach is to have a parent object that defines prepareNode (along with the state items it uses) and pass the required pieces as props into the components that use them.
That "parent object" could be a common-ancestor component, or a Context (if the chain from the parent to the children makes it cumbersome to pass the props all the way down it).
Let's say I have a parent component in React with 3 separate child components of the same component class (meaning, within class Parent I have 3 Child components). How do I access each child's state within the Parent component?
My initial thoughts are to have a separate variable for each Child's state that I want to access (I only want to access the filled variable for each child). But I feel that this is certainly a sloppy solution to something that is already in place with React, so would appreciate any pointers.
Example code below for illustration purposes.
const Parent = (props) => {
return (
<div>
<Child/>
<Child/>
<Child/>
</div>
);
}
const Child = (props) => {
const [filled, setFilled] = useState(false);
return (
<div></div>
);
}
Perhaps the better question is how do I access the particular child? And once accessed, how do I access its filled state (callback function)? I've read about useRef, is that where I should be looking here?
If what you are trying to do is report back to the parent the childs's state, you can do that by passing down from the parent, via props, a function to report that state back, as such:
const Parent = (props) => {
const reportChildState = (value) =>{
//do something with the child filled state
}
return (
<div>
<Child reportState={reportChildState}/>
<Child reportState={reportChildState}/>
<Child reportState={reportChildState}/>
</div>
);
}
const Child = (props) => {
//in here you can call props.reportChildState(filled)
const [filled, setFilled] = useState(false);
return (
<div></div>
);
}
As far as I'm aware there isnt a way to access a child components state from the parent. The only solutions are to either pass the state object into the child as a prop, using the context API or using a thirst party state management such as redux.
I wouldnt use useref for accessing a child's state.
You actually wouldn't directly access the children to retrieve their states. What you want here is a Context that encompases the parent component. Check out the React Context API. In short, you can create a "context" that contains states that need to be shared between multiple components. Once the context is made, a Provider is also created with that context. This provider is a component that accepts a value prop. This prop contains an object of all the state values and setter functions within the context. Child components of a Provider component can use the useContext hook to retrieve the values and functions from the value prop of the Provider.
Code example:
MyContext.js
import React, {createContext, useState} from 'react';
// Create the context and give it default values
export const MyContext = createContext(defaultValuesObject);
// We create a component that wraps around the provider, and is stateful. The states and their setters are placed into the provider, which is then returned.
const MyProvider = (props) => {
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const dataToShare = {
firstName,
setFirstName,
lastName,
setLastName,
}
// Return the context provider with the data already inside, and fill the children.
return (
<MyContext.Provider value={dataToShare}>
{props.children}
</MyContext.Provider>
)
}
We have the provider now. In your parent component's code, use the useContext hook to give the component access to the values and functions stored inside the provider component.
MyParentComponent.jsx
import React, {useContext} from 'react';
import MyProvider, { MyContext } from 'MyContext'
const MyComponent = (props) => {
// useContext returns the value stored in the provider so we can use it and the functions inside. The context is maintained inside the states in the provider.
const providerValue = useContext(MyContext)
// OR
const { firstName, setFirstName, lastName, setLastName } = useContext(MyContext)
return (
<div>
// Provide the first child component with the values and functions from the context
<MyChildComponentOne someProp={providerValue} />
// Provide the second child component with the values and functions from the context
<MyChildComponentTwo someProp={providerValue} />
// Provide the second third component with the values and functions from the context
<MyChildComponentThree someProp={providerValue} />
<div/>
)
}
Now, we still won't be able to use the context without a provider, which MUST wrap around the component(s) calling useContext for that specific context. Assuming the ParentComponent is used inside of App.jsx:
App.jsx
...imports and whatever other code you have in here
/// The jsx for the App component or whatever component calls MyParentComponent
return <div>
<MyProvider>
<MyParentComponent>
</MyProvider>
</div>
To re-iterate, you are taking the state OUT of the child/parent components, and putting them INTO the Provider created by the Context. Child components of the Provider can call useContext and gain access to the data and functions in the Provider's value prop.
There is no direct way to pass information from the child to the parent, only the other way around.
But that means you can also pass functions from the parent to the child! One common pattern for achieving what you need would be to pass the state set function to the child, so it can alter the parent's state. Like so:
const Parent = (props) => {
const [childStates, setChildStates] = useState({ child1: '', child2: '', child3: '' })
return (
<div>
<Child
state={childStates.child1}
setState={(val) => setChildStates((prev) => ({ ...prev, child1: val }))}
/>
<Child
state={childStates.child2}
setState={(val) => setChildStates((prev) => ({ ...prev, child2: val }))}
/>
<Child
state={childStates.child3}
setState={(val) => setChildStates((prev) => ({...prev, child3: val }))}
/>
</div>
);
}
I am trying to get the size of a div (css width: 100%) and set the zoom factor of a component according to the div's size. The problem is on first run of the App, the div doesn't exist in the DOM yet, since return has not created it. So I tried to put it in a useEffect to check if it gets created. However for some reason this doesn't work, when I put the querySelector into a constant because I thought the constant would be recreated once the component renders again because useEffect should trigger a re-render. When I put it directly into the deps, I get a warning about exhaustive deps.
React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
How do I properly extract the querySelector into a variable to have the app react to it being rendered?
const App = () => {
const querySelector = document.querySelector(".area-main"); //doesn't exist on 1st render but should be recreated on second render?
const [zoom, setZoom] = useState(1);
useEffect(() => {
if(querySelector && querySelector.clientWidth > 1000){
setZoom(1.5);
}
console.log(querySelector); // null
}, [querySelector]); //chould change on re-render from null to element but doesn't trigger
// the following works but complains about complex dependency tells me to extract (how?)
//}, [document.querySelector(".area-main")]);
useEffect(() => {
console.log(zoom); //prints out 1 once
}, [zoom]);
return(
<div className=".area-main">
<Component zoom={zoom} />
</div>
);
}
According to the comment, I tried using layout effect but that doesn't change
const querySelector = document.querySelector(".canvas-main");
React.useLayoutEffect(() => {
console.log(querySelector); //prints out null once
}, [querySelector]);
To get a reference to the div element, you'll want to use a react ref. Then you can access that ref in a useLayoutEffect and do what you need to do. useLayoutEffect is similar to useEffect, except if you set state it will cause a synchronous rerender, and that way you won't have a flicker on the screen as you reposition things.
const App () => {
const [zoom, setZoom] = useState(1);
const divRef = useRef();
useLayoutEffect(() => {
if (divRef.current.clientWidth > 1000) {
setZoom(1.5)
}
}, []);
return (
<div ref={divRef} className=".area-main">
<Component zoom={zoom} />
</div>
);
}
For more information on refs, see this page
Let's say I want to create a UI component for an "accordion" (a set of collapsible panels). The parent component controls the state of which panels are open, while the child panels should be able to read the context to determine whether or not they're open.
const Accordion = ({ children }) => {
const [openSections, setOpenSections] = useState({})
const isOpen = sectionId => Boolean(openSections[sectionId])
const onToggle = sectionId => () =>
setOpenSections({ ...openSections, [sectionId]: !openSections[sectionId] })
const context = useMemo(() => createContext(), [])
// Can't tell children to use *this* context
return (
<context.Provider value={useMemo(() => ({ isOpen, onToggle }), [isOpen, onToggle])}>
{children}
</context.Provider>
)
}
const AccordionSection = ({ sectionId, title, children }) => {
const { isOpen, onToggle } = useContext(context)
// No way to infer the right context
return (
<>
<button onClick={onToggle(sectionId)}>{isOpen(sectionId) ? 'Close' : 'Open'}</button>
{isOpen && children}
</>
)
}
The only way I could think of accomplishing this would be to have Accordion run an effect whenever children changes, then traverse children deeply and find AccordionSection components, while not recursing any nested Accordion components -- then cloneElement() and inject context as a prop to each AccordionSection.
This seems not only inefficient, but I'm not even entirely sure it will work. It depends on children being fully hydrated when the effect runs, which I'm not sure if that happens, and it also requires that Accordion's renderer gets called whenever deep children change, which I'm not sure of either.
My current method is to create a custom hook for the developer implementing the Accordion. The hook returns a function which returns the isOpen and onToggle functions which have to manually be passed to each rendered AccordionSection. It works and is possibly more elegant than the children solution, but requires more overhead as the developer needs to use a hook just to maintain what would otherwise be state encapsulated in Accordion.
React.createContext will return an object that holds 2 components:
Provider
Consumer
These 2 components can share data, the Consumer can "grab" the context data from the nearest Provider up the tree (or use the useContext hook instead of rendering a Consumer).
You should create the context object outside the parent component and use it to render a Consumer inside your children components (or use the useContext hook).
Simple example:
const myContext = createContext();
const Accordion = ({ children }) => {
// ...
return (
<myContext.Provider value={...} >
{children}
</myContext.Provider>
)
}
const AccordionSection = (...) => {
const contextData = useContext(myContext);
// use the data of your context as you wish
// ...
}
Note that i used the useContext hook instead of rendering the Consumer, its up to you if you want to use the hook or the Consumer.
You can see more examples and get more details from the docs