React layout children dynamically without unmounting - reactjs

I have a React component that is used to layout its children according to their key. Here is what it looks like:
<Container focusedChildKey={this.state.focusedChildKey}>
<Child key="child1" />
<Child key="child2" />
<Child key="child3" />
</Container>
In the container, in the render function I do something simple:
let focused = null;
let normals = [];
React.Children.forEach(children, (child) => {
if (child.key === this.props.focusedChildKey) {
focused = child
} else {
normals.push(child);
}
});
return (
<div>
<div className="focusedChild", style={{ ... }}>
{focused}
</div>
{normals}
</div>
);
The problem I encounter is that whenever the focusedChildKey props changes on the Container, the focused child got unmounted and mounted again. Children in my case carry a lot of states that I lose every time one of the child get focused.
I read a lot but can't understand why it's re-mounting the whole component.

Related

Component Re-rendering issue

I am working on the project in React Typescript.
I have created hierarchy of components as per requirement.
In one scenario I have to pass data from child component to parent component and I am passing function as props and it works.
Issue :
When passing data to parent component child component gets re-render it looks like. Mean to say Dropdown selection is get reset and tree control expanded nodes get collapsed and set to the position as first time rendered.
I have used useState,useEffects hooks.
I have also tried React.memo as a part of my search on internet.
What I need :
I want to pass data to parent component from child without re-render the child component as there is no change in the props of child component.
Try this approach:
Add useCallback hook to memoize your function which lift data to <Parent />.
Then use React.memo for <Child /> to control prop changes and avoid unwanted re-renders.
I prepare an example for you here.
UPD. I have uploaded an example, you can copy it and see how it works!
Here is Child component:
const Child = ({ onChange }) => {
console.log("Child re-render");
return (
<div className="App">
<h1>Child</h1>
<button onClick={() => onChange(Math.random())}>
Lift value to Parant
</button>
</div>
);
};
const areEqual = ({ onChange: prevOnChange }, { onChange }) => {
return prevOnChange === onChange; // if true => this will avoid render
}
export default React.memo(Child, areEqual);
And the Parent:
consn App = () => {
const [value, setValue] = useState("");
const onChange = useCallback((value) => setValue(String(value)), []);
console.log("Parant re-render");
return (
<div className="App">
<h1>Parent</h1>
<div>Value is: {value}</div>
<Child onChange={onChange} />
</div>
);
}
Best regards 🚀

How can I render one component conditionally twice and not lose internal states/unmounts?

I have one component which needs to be rendered conditionally. Renders the same component with different styles. So, I did like this
import ComponentToRender from '../../ComponentToRender'
const Main =()=> {
const [expand,setExpand] =useState(false)
return (
<div>
{!expand && <ComponentToRender {...someProps} />}
{expand && <div>
<ComponentToRender {...otherProps} />
</div>
}
<button onClick={()=>setExpand(pre => !pre)}>Expand</button>
</div>
)
}
For the above code, I get what I want in terms of UI. But, all the internal states are lost. I must render two components like that and keep the internal states. Is that possible to do that in React?
You can achieve this by keeping the component rendered unconditionally and hiding it with CSS.
You get to preserve Component‘s state for free along with the DOM state (scroll, focus, and input position). However, this solution has drawbacks, too:
You mount the component on startup, even if the user never accesses it.
You update the component even when it’s invisible.
import ComponentToRender from "../../ComponentToRender";
const Main = () => {
const [expand, setExpand] = useState(false);
return (
<div>
<div style={{ display: expand ? null : "none" }}>
<ComponentToRender {...someProps} />
</div>
<div style={{ display: !expand ? null : "none" }}>
<div>
<ComponentToRender {...otherProps} />
</div>
</div>{" "}
<button onClick={() => setExpand((pre) => !pre)}>Expand</button>
</div>
);
};
The reconciliation algorithm is such that when on next render you move from one component to component of different type (assuming they have same spot in component hierarchy), instance of old component is destroyed.
Since you have <ComponentToRender/> and another one is <div><ComponentToRender/></div>, they are different components (because one is inside a div).
Read about reconciliation.
What you can do is move the state of ComponentToRender to Main and pass it as props. Now even if the component unmounts the state will not be lost.

Pass state(index) to several children

After learning how to pass a state to a child, I am now wondering how to do it between children.
Parent:
const PostTemplate = ({ data }) => {
const [isIndex, setIndex] = useState(0);
return (
<>
<Slider
setIndex={isIndex}
{...data}
/>
<Views
setIndex={setIndex}
{...data}
/>
</>
);
};
Child1 (Views):
const Views = (data) => {
return (
<div>
{data.views.edges.map(({ node: view }, index) => (
<div
onClick={() => {
data.setIndex(index);
}}
>
<p>Hello</p>
/>
</div>
))}
</div>
);
};
Child2 (Slider):
const Slider = (data) => {
return (
<Swiper initialSlide={data.isIndex}>
{data.views.edges.map(({ node: view }) => (
<SwiperSlide>Slide</SwiperSlide>
))}
</Swiper>
);
};
This returns a rather strange error: undefined is not an object (evaluating 'el.classList').
What I would like to do is pass the index of Views to Slider.
Thanks for all the help!
Props:
Props are data which is passed to the component when it is added to ReactDOM
Props are immutable- means component can never change it's own props.
Data Flow:
When two child component have to share/use same data, parent component will pass this data to child component. Data (as Props) flows from Up to Down
Now this data is owned by Parent component. So any change of this data have to handle by this parent component also. For example, if child component wants to change this Data, they have to call parent's component change handler. Change Event flow from child to parent.
In your example PostTemplate is parent and Views & Slider are child.
PostTemplate own and manage index data (state).
PostTemplate will send index data (state) to child components: Views & Slider
Now both components have index value in their Props.
Child component Views need to change the value of Index. So parent component also pass it's own index change handler to Views component
Views component calls the change handler it got from it's parent as props when it needs to change Index value.
Here is a working example from your code in question:
function Slider(props) {
return (
<fieldset>
<legend>Slider Component </legend>
<p>Got Index data as Props: {props.index}</p>
</fieldset>);
}
class PostTemplate extends React.Component {
constructor(props) {
super(props);
this.setIndex = this.setIndex.bind(this);
this.state = {index: 0};
}
setIndex(e) {
this.setState({index: e.target.value});
}
render() {
const index = this.state.index;
return (
<fieldset>
<legend>PostTemplate Component:</legend>
<ol>
<li key={1}> This is parent component which has two child componets: Slider, Views </li>
<li key={2}> As Index data is used by both of it's child components, Index data is initilized and managed by this component.</li>
<li key={3}> When a child component needs to use this data (state:index), Posttemplate (parent) will pass the value as props </li>
<li key={3}> When a child component needs to change this data (state:index), Posttemplate (parent) will pass the changeHandler as props </li>
</ol>
<Views
index={index}
setIndex={this.setIndex}/>
<Slider
index={index} />
</fieldset>
);
}
}
function Views(props) {
return (<fieldset>
<legend>Views Component </legend>
<p>Got Index data as Props: {props.index}</p>
<p>Got index change handler function from Props: {typeof props.setIndex }</p>
<input
value={props.index}
onChange={props.setIndex} />
</fieldset>);
}
ReactDOM.render(
<PostTemplate />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js">
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js">
<div id="root">
<!-- This div's content will be managed by React. -->
</div>
Try it on CodePen

React components when to write logics into component and when to write them in their parents?

Here is my component:
export default function Card({ title, myFunc }) {
return (
<div>
<h2>{title}</h2>
<button onClick={myFunc}>click to add</button>
</div>
);
}
and here is my app (its parent) where I used it:
function App() {
return (
<div className="App">
{data.map((item) => {
return (
<Card
key={item.id}
title={item.title}
myFunc={() => {
alert('hi');
}}
/>
);
})}
</div>
);
}
As it can be seen I implemented the logic,which shows 'hi' by clicking on the button, outside my component. This is while I could have implemented the above logic just inside my component to do the same thing without any change in my app code just like this:
export default function Card({ title }) {
return (
<div>
<h2>{title}</h2>
<button
onClick={() => {
alert('hi');
}}
>
click to add
</button>
</div>
);
}
What I'm trying to figure out is when should I implement my logic inside the component and when outside it? Is there any rules or something about it or it does not matter at all?
If Parent need information for working and Child update this information, you must place information and function in Parent and give a callback function to the Child.
If an other Child of Parent need the value update by an another Child, you must create value and function in Parent, and give value and callback to Child.
Else, if Parent don't care about the Child to do and the value is manipulate, Child be work alone.
In you're example, Child need title from Parent because Parent have the Array with title so clearly, it's normal usage. But the function alert('hi') can be directly on your child, because every Card do the same things and Parent don't care about what happen after alert('hi').

Calling this.refs.component.function

So i have a navigation component at a higher level like this:
<Navigation ref="nav">
<Children>
<Footer>
In one of my children components I want to call a method inside my Navigation component. How would I do so? I gave this a shot:
if Navigation has a method called
toggle() {
this.setState({open: true})
}
could I call it like this in the children component?
Children:
openSomething = () => {
console.log(this.refs);
this.refs.nav.toggle();
}
<Somebutton onClick={openSomething} name="OpenStuff" />
After i had done this, refs had come out undefined, so I wasn't sure how to go about this. The idea is that i have a child component that I want to open a modal on button click, but it is its child and I thought of doing it this way, however it didnt work.
You can't do this from Children because Navigation is its sibling. Only their parent can refer to Navigation via refs. You can trigger an event from Children which is handled by the parent:
<Somebutton onClick={this.props.onOpen} name="OpenStuff" />
And then handle it in the parent component:
render() {
return (
<div>
<Navigation ref="nav" />
<Children onOpen={() => this.handleOnOpen()} />
</div>
)
}
handleOnOpen() {
this.refs.nav.toggle()
}

Resources