I am new to react and come from a background of functional component only.
In my react project,
When I conditionally rendering , ie from false to true, the data inside child component will be gone.
Then I wonder why is that.
Then I heard a concept called unmounting. It means, when my condition change from true to false, the component will get unmounting. And in unmounting, the state inside will gone.
But then, it doesn't add up.
Q: Whenever we re-render any other components, just like the normal situation, we will also unmount component in order to do re-rendering. And our state value would not be gone.
Why this problem was happened especially on having conditional statement in react?
Edit:
My emphsis is not on how to avoid state loss. My question is that why data will be gone in conditional rendering. And why unmounts will cause such problem, but re rendering would not cause such ( both also involves unmounting)
Here is my code
In parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 />
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
In child:
import React, { useEffect, useState } from "react";
function Child1() {
useEffect(() => {
console.log("useeffect in child");
console.log("newMessage: " + newMessage);
});
const [newMessage, setNewMessage] = useState("");
return (
<div>
<input onChange={(event) => setNewMessage(event.target.value)} />
</div>
);
}
export default Child1;
Add some picture to illurste what I mean by data lose in conidtional rendering
enter
https://i.stack.imgur.com/UrIhT.png
click to not show it
https://i.stack.imgur.com/0OC87.png
click to show again
https://i.stack.imgur.com/4zlWk.png
Try moving all state management to the parent component and leave the child component 'dumb'. You can pass the setMessage and any other state variables to the child as props.
Parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 setMessage={setMessage}/>
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Child:
import React from "react";
function Child1({setMessage}) {
return (
<div>
<input onChange={(event) => setMessage(event.target.value)} />
</div>
);
}
export default Child1;
The answer for your question is very simple, While unmounting you are removing the component itself from react-dom. The state, props and all the data's handled inside your component will be active only If the component is inside the react-dom. If the particular component is unmounted, all the states and props that was created and processed will also be removed from react-dom along with the component. And the fresh component, state and props will be mounted in the react-dom if you conditionally render the component again.
Related
in react I have 2 functional component, I want to call parent's function in child component on button click which update parent state. on this function invoke my child component is re-render.so I want Stop this re-rendering, i trying solve it as follow but not working
app.js
import React, { useState, useCallback } from "react";
import Child from "./child";
export default function App()
{ let [show, setShow] = useState(false);
console.log("App Re-render:", show);
const setMyState = useCallback(() => {
setShow(!show);
});
return (
<div className="App">
<h1>Parent Component</h1>
<h2>----------------------------------</h2>
<Child func={setMyState}></Child>
</div>
);
}
child.js
import React, { memo } from "react";
function Child({ func }) {
console.log("Child re-render");
return (
<div>
<p>Child Component</p>
<button onClick={func}>Call Parent Method </button>
</div>
);
}
export default memo(Child);
this "func" function should call multiple time.
please help me to solve this problem
The code you posted looks good. Do you have more code that isn't posted? Can you try to reproduce in an example?
You don't need the useCallback hook to do the functionality you are looking for. So skip that.
I have done the same thing here and it works:
https://codesandbox.io/s/gallant-hellman-60lw3d?file=/src/App.js
So I have a component that looks like this:
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
And then I have another component that looks like this:
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container ;
Now, the thing is the {children} of the 1st component is sometimes the 2nd component, and sometimes it's not. Therefore I can't just put the CSS and HTML from the 2ndcomponent into the 1st component - which in turn would fix my problem.
But as you might be able to see, there is an onClick event in the first component. I would like it so that when that is clicked, the state from the click is send to the 2nd component and toggles the className-toggle.
Can this be achieved by doing this, or do I have to set everything up differently ?
And yes, I am quite new to React, so please don't be harsh.
Css
I would look into better methods of applying styling with css. Not sure about your project scope/tools but typically all the css files are imported in the dom root and loaded in there. This avoids creating css files for every component.
Here's 9 ways of implementing css for react.
Passing HTML
In react if you want to render component in another component instead of passing it as a child you should import it as follows.
// replace container path with actual path of Container file
// ex './Container.js'
import Container from 'container_path.js';
Now Rendering the Component is as simple as including it in the html code.
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
<Container/>
</div>
</>
);
Here's a Stack Overflow post of users importing components using react + es6 + webpack. More information on importing components is available there.
State management
In react if you have a state that is being accessed by multiple components the standard is to keep the state in the parent component.
This way you can pass the state as a prop to any children components. You can also create a function which updates this state and pass that function as a prop to the children.
ex:
import React, { useState } from "react";
import Container from "./Container.js";
import Navigation from "./Navigation.js"
const Parent = props => {
const [toggle, toggleState] = useState(false);
return (
<div>
<Container toggleState={toggleState} toggle={toggle} />
<Navigation toggleState={toggleState} toggle={toggle} />
</div>
)
}
Before continuing working on your project I would recommend researching functional components vs class components. Here's a helpful article.
Try to wrap second component to function with state from first component as argument.
Wrapper for your second component and using for first component
const putInnerComponent = (stateFromOuterComponent) => <Container toggle={stateFromOuterComponent}/>;
<Navigation children={putInnerComponent}/>
Your first component
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children(toggle)}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
Your second component
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children, toggle }) => {
//const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container;
I am using react, redux with immutable js. I am facing a problem when I use useEffect or similar hooks with non-primitive data that effect is running even though it hasn't changed as I am using hoc recommended by redux docs.
Does anyone has a solution or best practice for using immutable js and redux with react hooks?
This is my parent component.
import { useSelector, useDispatch } from "react-redux";
import { setValue, getValue, getData } from "./store";
import "./styles.css";
import Child from "./Child";
export default function App() {
const data = useSelector(getData);
const value = useSelector(getValue);
const dispatch = useDispatch();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => dispatch(setValue(value + 1))}>
Change Value
</button>
<Child value={value} data={data} />
</div>
);
}
This is child component which is wrapped with toJS hoc
import { toJS } from "./hoc";
function Child(props) {
useEffect(() => {
// this runs even if only props.value has changed.
console.log(props.data, "changed");
}, [props.data]);
return <div>{props.value}</div>;
}
export default toJS(Child);
Reproducible example:
Demonstration:
https://codesandbox.io/s/2zkxyk31oy
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("render");
let [val, setVal] = useState(0);
return <button onClick={() => setVal(1)}>go</button>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
What I expect to see is two render calls: the initial one, and the one caused by the state change.
The third setState call should have no effect, since react bails out of rendering if the state hasn't changed. https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
What exactly is happening?
The doc link you provided states:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper”
It appears the behaviour is consistent with what the doc says. In order to test that, you can check if child components are being rendered. Something like this:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
console.log("render");
let [val, setVal] = useState(0);
return <button onClick={() => setVal(1)}><Child/></button>;
}
function Child() {
console.log('render the child')
return <span>go</span>
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The result in the console now is:
render
render the child
render
render the child
render
I'm using the React Context API with the main intent of avoiding prop drilling. Right now my Context includes a useState and various functions that update the state - these are put into a const object that is passed as the value prop of ActionsContext.Provider. This is an abstraction of my current component hierarchy:
Header
---NavPanel
ContentContainer
---Content (Context.Consumer being returned in this component)
where Header and ContentContainer are sibling elements and NavPanel and ContentContainer are their respective children.
I initially put the Context.Consumer in Content because the other elements did not need it. However I'm building a feature now where NavPanel needs to know about the state that's managed by the Context. So I put another Consumer in NavPanel, only to find that a separate Consumer means a separate instance of the state.
Is there any smart workaround that gives NavPanel and Content access to the same state, that doesn't involve putting the Consumer in the parent component of Header and Content? That would result in a lot of prop drilling with the way my app is currently structured.
Codesandbox example of multiple instances: https://codesandbox.io/s/context-multiple-consumers-v2wte
Several things:
You should have only one provider for every state you want to share.
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
It is better to split your context in several contexts so you pass values instead of objects. This way when you update your state React will detect it is different instead of comparing the same object.
Your input should be a controlled component https://reactjs.org/docs/forms.html
Consider using the useContext API for better ergonomics if you are using React 16.8 instead of ContextConsumer.
With these changes, your code would be:
MyContext.js
import React, { useState } from "react";
export const MyItemContext = React.createContext();
export const MySetItemContext = React.createContext();
export const MyHandleKeyContext = React.createContext();
const ContextProvider = props => {
const [itemBeingEdited, setItemBeingEdited] = useState("");
const handleKey = event => {
if (event.key === "Enter") {
setItemBeingEdited("skittles");
} else if (event.key === "K") {
setItemBeingEdited("kilimanjaro");
} else {
setItemBeingEdited("");
}
};
const editFunctions = {
itemBeingEdited,
setItemBeingEdited,
handleKey
};
return (
<MyItemContext.Provider value={itemBeingEdited}>
<MyHandleKeyContext.Provider value={handleKey}>
<MySetItemContext.Provider value={setItemBeingEdited}>
{props.children}
</MySetItemContext.Provider>
</MyHandleKeyContext.Provider>
</MyItemContext.Provider>
);
};
export default ContextProvider;
PartOne.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartOne = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
placeholder="Type in me"
onKeyDown={handleKey}
value={itemBeingEdited}
onChange={e => setItem(e.target.value)}
/>
</React.Fragment>
);
};
export default PartOne;
PartTwo.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartTwo = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
value={itemBeingEdited}
type="text"
placeholder="Type in me"
onChange={e => setItem(e.target.value)}
onKeyDown={handleKey}
/>
</React.Fragment>
);
};
export default PartTwo;
index.js
import React from "react";
import ReactDOM from "react-dom";
import PartOne from "./PartOne";
import PartTwo from "./PartTwo";
import ContextProvider from "./MyContext";
import "./styles.css";
function App() {
return (
<div className="App">
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox: https://codesandbox.io/s/context-multiple-consumers-vb9oj?fontsize=14