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.
Related
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
I'm working on reactjs project
where I fetching data from firestore and set the app.js state to the fetched data, and I pass this state to a child of app.js to display it but it's undefined at first then it consoles the right state.
How can I make the child component render only after its props is correct?!
fetchDataFromFirestore = async () => {
let dataRefFromFirestore = database.doc('items/fruitsDataJsonFile');
(await dataRefFromFirestore).onSnapshot((snapshot) => {
let fetchedItems = snapshot.data();
this.setState({
fetchedItems: fetchedItems.data
},
console.log('DONEEE ADIING'))
})
}
You can use a concept called "Conditional Rendering".
It will be like
{!!this.state.fetchedItems?.length &&
<YourChildComponent fetchedItems={this.state.fetchedItems}>
Then, your child component will be rendered only when the state has array data.
Similarly, your child component will have props called fetchedItems with full data.
Reference: https://reactjs.org/docs/conditional-rendering.html
I'm trying to pass props from my Parent component to child component. Here are the important snippets:
Snippet 1: This is the object that contains the prop (integer).
const cardProps = {
cardProps0: 0,
Snippet 2: This is the Card component within the parent component that carries the prop to child component
return (
<MyCardLink source={cardProps.cardProps0} />
Snippet 3: This is the child component (MyCardLink)
useEffect((props) => {
axios
.get(
'http://newsapi.org/v2/everything?q=economy&apiKey=XXXXXXXXXXXXXXXX'
)
.then((res) => {
console.log(res);
setNews(res.data.articles[props.source]);
})
.catch((err) => {
console.log(err);
});
}, []);
The goal is that [prop.source] contains a number value from a list of an array served by an API. If I just place a number value in the child component (MyCardLink) in place of [props.source] on the setNews function then it renders the component no problem.
My problem is when I pass the prop from parent component to child component and use [prop.source], nothing renders and all I get from the console log is:
Cannot read property 'source' of undefined.
Am I doing something wrong?
Instead of passing props into your useEffect, you need to add into your MyCardLink component's parameters as:
const MyCardLink = (props) => {
// your component's defintion
}
Additionally you can destructure as the following:
const MyCardLink = (props) => {
const { source } = props
// ... rest
}
Then simply you can use in your useEffect without props as:
useEffect(() => {
axios.get(
'http://newsapi.org/v2/everything?q=economy&apiKey=XXXXXXXXXXXXXXXX'
)
.then((res) => {
console.log(res);
setNews(res.data.articles[source]);
})
.catch((err) => {
console.log(err);
}
);
}, []);
Based on your other question from the comment section what I would do is:
Change the initial value of the state from "" to null as const [news, setNews] = useState(null).
Also I would use && for null check and render <Card /> component only if it has value as news && <Card className={classes.root}> in your return.
The reason behind this is your API response is arriving asynchronously.
use use props in component below:
const MyCardLink =(props)=>{
...
...
...
}
export default MyCardLink;
I have a prop being passed from a parent component to a child component which changes based on the user's input.
I want to trigger a data fetch in the child component when that prop changes before the child component is rendered. How can I do it?
I tried in the following manner by using useEffects(()=>{},[props.a, props.b]) but that is always called after the render. Please help!
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function parentComponent() {
const [inputs, setInputs] = useState({ a: "", b: "" });
return (
<>
<input
value={inputs.a}
onChange={(event) => {
const value = event.target.value;
setInputs((prevState) => {
return { ...prevState, a: value };
});
}}
/>
<input
value={inputs.b}
onChange={(event) => {
const value = event.target.value;
setInputs((prevState) => {
return { ...prevState, b: value };
});
}}
/>
<ChildComponent a={inputs.a} b={inputs.b} />
</>
);
}
function ChildComponent(props) {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState({});
useEffect(() => {
console.log("updating new data based on props.a: " + props.a);
setData({ name: "john " + props.a });
return () => {};
}, [props.a, props.b]);
useEffect(() => {
console.log("data successfully changed");
console.log(data);
if (Object.keys(data).length !== 0) {
setIsLoading(false);
}
return () => {};
}, [data]);
function renderPartOfComponent() {
console.log("rendering POC with props.a: " + props.a);
return <div>data is: {data.name}</div>;
}
return (
<div className="App">{isLoading ? null : renderPartOfComponent()}</div>
);
}
In the console what I get is:
rendering POC with props.a: fe
rendering POC with props.a: fe
updating new data based on props.a: fe
rendering POC with props.a: fe
rendering POC with props.a: fe
data successfully changed
Object {name: "john fe"}
rendering POC with props.a: fe
rendering POC with props.a: fe
If you know how I can make the code more efficient, that would be a great help as well!
Here's the codesandbox link for the code: https://codesandbox.io/s/determined-northcutt-6z9f8?file=/src/App.js:0-1466
Solution
You can use useMemo, which doesn't wait for a re-render. It will execute as long as the dependencies are changed.
useMemo(()=>{
doSomething() //Doesn't want until render is completed
}, [dep1, dep2])
You can use function below:
// utils.js
const useBeforeRender = (callback, deps) => {
const [isRun, setIsRun] = useState(false);
if (!isRun) {
callback();
setIsRun(true);
}
useEffect(() => () => setIsRun(false), deps);
};
// yourComponent.js
useBeforeRender(() => someFunc(), []);
useEffect is always called after the render phase of the component. This is to avoid any side-effects from happening during the render commit phase (as it'd cause the component to become highly inconsistent and keep trying to render itself).
Your ParentComponent consists of Input, Input & ChildComponent.
As you type in textbox, ParentComponent: inputs state is modified.
This state change causes ChildComponent to re-render, hence renderPartOfComponent is called (as isLoading remains false from previous render).
After re-render, useEffect will be invoked (Parent's state propagates to Child).
Since isLoading state is modified from the effects, another rendering happens.
I found the solution by creating and maintaining state within the ChildComponent
So, the order of processes was this:
props modified -> render takes place -> useEffect block is executed.
I found the workaround by simply instantiating a state within the childComponent and making sure that the props state is the same as the one in the child component before rendering, else it would just show loading... This works perfectly.
Nowadays you can use useLayoutEffect which is a version of useEffect that fires before the browser repaints the screen.
Docs: https://beta.reactjs.org/reference/react/useLayoutEffect
React memo isn't capturing the props neither the prevProps nor the nextProps and the component render well. The react docs say
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost.
my problem is to stop twice rendering using react memo, but memo seems to be not working and the component renders twice with the same props.
The component renders when the Create New Event is clicked on /events
here is the live sandbox.
Child Component located at /components/Event/CreateEvent/CreateEvent.js
the parent component is located at /Pages/Event/Event.js line number 999' from where the child component is being triggered
Here is the Code:
import React from "react";
import AuthContext from "../../context/global-context";
import CreateEvent from "../../components/Event/CreateEvent/CreateEvent";
function Events({ location }) {
// Sate Managing
const [allEvents, setAllEvents] = React.useState([]);
const [creating, setCreating] = React.useState(false);
// Context As State
const { token, email } = React.useContext(AuthContext);
// Creating Event Showing
const modelBoxHandler = () => {
// works on when the ViewEvent is open
if (eventSelected) {
setEventSelected(null);
return;
}
setCreating(!creating);
};
return (
<div className="events">
{/* New Event Creating */}
{creating && (
<CreateEvent onHidder={modelBoxHandler} allEvents={allEvents} />
)}
{console.log("Event Rendered.js =>")}
</div>
);
}
export default React.memo(Events, () => true);
Child Component where the Rect memo doesn't have props:
import React from "react";
import AuthContext from "../../../context/global-context";
function CreateEvent({ onHidder, allEvents }) {
// Context
const { token } = React.useContext(AuthContext);
console.log("CreatedEvent.js REnder");
return (
... Some code here
);
}
export default React.memo(CreateEvent, (prevProps, nextProps) => {
console.log("Hello", prevProps, nextProps);
});
Thanks in advance for your valuable answer and times!
The problem is that on basis of creating variable you are actually remounting and not rendering the CreateEvent component. What it means is that if creating variable changes, the component is unmounted and re-mounted when creating is true, so its not a re-render
Also you must note that modelBoxHandler function reference also changes on each re-render so even if your CreateEvent component is in rendered state and the parent re-rendered due to some reason , the CreateEvent component too will re-render
There are 2 changes that you need to make to make it work better
Define modelBoxHandler with a useCallback hook
perform conditional rendering in createEvent based on creating prop
// Creating Event Showing
const modelBoxHandler = useCallback(() => {
// works on when the ViewEvent is open
if (eventSelected) {
setEventSelected(null);
return;
}
setCreating(prevCreating => !prevCreating);
}, [eventSelected]);
...
return (
<div className="events">
{/* New Event Creating */}
<CreateEvent creating={creating} onHidder={modelBoxHandler} allEvents={allEvents} />
{console.log("Event Rendered.js =>")}
</div>
);
and in createEvent
function CreateEvent({ onHidder, allEvents, creating }) {
// Context
const { token } = React.useContext(AuthContext);
console.log("CreatedEvent.js REnder");
if(!creating) {
return null;
}
return (
... Some code here
);
}
export default React.memo(CreateEvent);
In your example, you don't have an additional render for React.memo to work.
According to your render logic, there aren't any nextProps, you unmount the component with conditional rendering (creating).
// You toggle with `creating` value, there is only single render each time
creating && <CreateEvent onHidder={modelBoxHandler} allEvents={allEvents}/>
// Works, because there will be multiple renders (nextProps)
true && <CreateEvent onHidder={modelBoxHandler} allEvents={allEvents} />
In this case, you might not need React.memo.