React Children component lost status redering inner another component - javascript

The child component passed to another component is losing value in its status
[environment]:
I have 5 components:
Default,
Wrapper,
ChildrenComponent,
Div1,
Div2
In Default I'm rendering the Wrapper and inside the Wrapper I'm rendering ChildrenComponent, all of this in Default, I'm passing to Wrapper a "props" called "isDiv1", which is also my status in Default, I'm changing its value through a button in Default.
In Wrapper I am performing a logic analyzing if the value in "props.isDiv1" is true, if it is the Div1 component if not, I keep the Div2 Component, and I use the result of this logic to involve the "props.children" in Div1 or Div2 .
In ChildrenComponent I have a status that stores an initial text, and an input where every time it is changed, it changes this initial text to what has been changed.
And in Div1 and Div2 I have a simple div with a different "className" attribute.
import React from "react";
const Div1 = props => <div className="iam-div-1">{props.children}</div>;
const Div2 = props => <div className="iam-div-2">{props.children}</div>;
const Wrapper = props => {
const Div = props.isDiv1 ? Div1 : Div2;
return <Div>{props.children}</Div>;
};
const ChildrenComponent = props => {
const [text, setText] = React.useState("initial");
return <input value={text} onChange={e => setText(e.target.value)} />;
};
export default props => {
const [isDiv1, setIsDiv1] = React.useState(false);
return (
<>
<Wrapper isDiv1={isDiv1}>
<ChildrenComponent />
</Wrapper>
<button onClick={() => setIsDiv1(!isDiv1)}>change div</button>
</>
);
};
[problem]:
The problem is that every time I change the status of ChildrenComponent and click the button in Default, the value in the status of ChildrenComponent is reset.
Below is the link for you to test yourselves:
https://codesandbox.io/s/headless-monad-ymc2i?file=/src/App.js

The issue is that your Wrapper component is mounting a new Div component every time the value of isDiv1 changes. By mounting a new Div component, you are in turn mounting a new ChildComponent, which will always have the initial value.
A component will not stay mounted if it's parent becomes unmounted, nor will that component's props (i.e. React.Children) be preserved. If you think about the component tree like a real tree; a twig will not stay hanging in mid-air when the branch connecting it to the trunk is cut off.
If you want to preserve state when a component is unmounted, then you need to move that state into a higher level of the component tree. In your case it might look something like this:
const ChildrenComponent = props => {
return <input value={props.text} onChange={e => props.setText(e.target.value)} />;
};
export default props => {
const [isDiv1, setIsDiv1] = React.useState(false);
const [text, setText] = React.useState("initial");
return (
<>
<Wrapper isDiv1={isDiv1}>
<ChildrenComponent text={text} setText={setText}/>
</Wrapper>
<button onClick={() => setIsDiv1(!isDiv1)}>change div</button>
</>
);
};

Related

MUI Popper Component with Props keeps closing component on re-render

I am curious how to architect a component leveraging MUI's Popover component when there are dynamic props getting passed to a controlled Slider component inside of the Popover component — as well as the anchor element also getting dynamically updated as the value changes getting passed-down from a higher order component.
What is happening is that when the controlled child is updated by the user, it dispatches the change higher up the chain, driving new values down, which then re-renders the component, setting the anchorEl back to null. Here's a quick video in action:
I'm sure there is something straightforward I could do to avoid this. Any help is appreciated!
Here is abbreviated code:
function Component({ dynamicProps }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const { dispatch } = useContext();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleChange = (_, newValue) => {
dispatch({
body: newValue
});
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div>
<Button
onClick={handleClick}
label={dynamicProps.label}
></Button>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
>
<Box sx={{ minWidth: "200px", mx: 2 }}>
<Slider
value={dynamicProps.value}
onChange={handleChange}
/>
</Box>
</Popover>
</div>
);
}
I have tried separating the Slider into another component, to avoid the re-render, and using my context's state to grab the values that I need, hover that point seems moot, since I still need to reference the anchorEl in the child, and since the trigger also is leveraging dynamic props, it will re-render and keep null-ing the anchorEl.
Ok team. Figured this one all-by-myself 🤗
Here's what you don't want to do: If you're going to use context — use it both for dispatching and grabbing state. Don't drill-down state from a parent component that will trigger a re-render. For both the button label and the controlled Slider, as long as you use the state insider the Component function through your context hook, you won't trigger a re-render, making your popper disappear from the re-render.
Do this 👇
export default function Assumption({ notDynamicProps }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const { dispatch, state } = useRentalCalculator();
Not this 👇
export default function Assumption({ dynamicProps, notDynamicProps }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const { dispatch } = useRentalCalculator();

How do I rerender form value and state value when using hooks and state is changed in a different component in react

I have an app that updates state in a child component with the function setBrewLog that was set with useState
const [brewLog, setBrewLog] = useState([{ og: '' }]).
In the react-dev tools I can see that state is updated, and inside useEffect I can see that the component is rerendered, but the form field will not update. As well On a side note does Formik solve this issue?
I do get a error in the Chrome console.
A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
Here is a simplified version of my code.
export const Brewlog = () => {
const [brewLog, setBrewLog] = useState({ og: '' })
const onBrewLogChanged = e => {
const tempBrewLog = [...brewLog]
tempBrewLog[e.target.id[e.target.id.length - 1]][e.target.name] = e.target.value
setBrewLog(tempBrewLog)
}
// useEffect is triggered when the Child component updates state.
useEffect(() => {
console.log(brewLog)
})
return(
<>
<h3>Brewlog</h3>
<Child setBrewLog={setBrewLog} />
<label htmlFor='og' >Original gravity</label>
//Does not update on state change
<p> is:{brewLog.og}</p>
<input
type="text"
name='og'
id='og0'
//does not update on state change
placeholder={brewLog.og}
value={brewLog.og}
onChange={onBrewLogChanged}
/>
<button type="button" onClick={''} >
Save Recipe
</button>
</>
)
}
const Child = ( props ) => {
const [message, setMessage] = useState('')
const ChangeState = () => {
props.setBrewLog([{ og: '1' }])
setMessage('OG is 1')
},
return (
<div>
<button type='button' onClick={ChangeState}>Start</button>
<p>{message}</p>
</div>
)
}
export default Child
Thanks you for your insights
As Emilie Bergeron pointed out I was using two different structures for my state. Changing them both to an object solved the problem.

React Parent/Child State Change Design Question

I have a component with two children, one of them is a button that toggles a state (modalVisible) that decides whether the other child, a modal, is visible.
I'm having trouble sharing the on/off state across the parent and the modal child. I tried keeping the state in the parent and then passing it as a prop to the child, but it wasn't rerendering the child everytime the parent state changed.
<CommentsModal visible={modalVisible} />
Inside CommentsModal.js...
import Modal from 'react-native-modal';
...
const CommentsModal = ({visible}) => {
const [modalVisible, setModalVisible] = useState(visible);
...
return <Modal visible={modalVisible} />
}
I considered keeping the state entirely in the parent, without passing it into CommentsModal, like so:
function renderModal() {
if (modalVisible) {
return <CommentsModal visible={true} />
} else {
return <View />
}
}
But I realized that there has to be a state inside CommentsModal because I need an "X" button that toggles the modal off.
I'm not sure what the best way to do this is... I could do redux, but since there is a dynamic number of these modals; I don't want my store to be that complicated. The only way I can think of is to move all of the modal code into the parent component, then they can share states easily, but it seems dirty to me. Does anyone have a solution?
Your intuition to keep the state in the parent component is correct. To implement the x button all you need is to pass a onClose prop to the modal which would be a function that sets modalVisible to false. so you'll end up with something like this:
// parent component
const ParentComponent = () => {
const [modalVisible, setModalVisible] = useState(false);
const openModal = () => setModalVisible(true);
const closeModal = () => setModalVisible(false);
return (
<div>
<CommentsModal visible={modalVisible} onClose={closeModal} />
<button onClick={openModal}>open the modal</button>
<p>other children here...</p>
</div>
)
}
// CommentsModal
const CommentsModal = (props) => (
<Modal visible={props.visible}>
<button onClick={props.onClose}>X</button>
<p>more modal content here...</p>
</Modal>
)

React component state not updating with passed props

I have created an Accordion component which has data(object) and expanded(boolean) as props.
expanded props is used to set the expanded/collapsed state of this component passed as a prop.
const DeltaAccordion = ({ index, data, expanded = true }) => {
Accordion component also has an internal state
const [isExpanded, setIsExpanded] = useState(expanded);
which is used for expanding/collapsing the accordion.
Below is my complete component
Accordion.jsx
import React, { useState } from "react";
// styles
import styles from "./index.module.scss";
const Accordion = ({ index, data, expanded = true }) => {
// state
const [isExpanded, setIsExpanded] = useState(expanded);
console.log(data.name, `prop-val==${expanded}`, `inner-state==${isExpanded}`);
return (
<div
className={`${styles.container} caption ${isExpanded && styles.expanded}`}
>
<div className={styles.header} onClick={() => setIsExpanded(!isExpanded)}>
<div>{data.name}</div>
<div>Click</div>
</div>
<div className={styles.content}>
{data.newValue && (
<div className={styles.newValue}>
<span>{data.newValue}</span>
</div>
)}
{data.oldValue && (
<div className={styles.oldValue}>
<span>{data.oldValue}</span>
</div>
)}
</div>
</div>
);
};
export default Accordion;
The parent component has a list of data and it loops through the list to create an accordion for each data item.
App.js file
{dataList.map((data, index) => (
<Accordion data={data} expanded={!collpaseAll} index={1} />
))}
Here goes problem
There is also a button in my App.js file which is for either expanding/collapsing all the accordions together.
But when I pass its value as prop expanded to the accordion component then this value is not getting applied to the internal isExpanded state of the accordion component.
Here is live running code at codesandbox: https://codesandbox.io/s/goofy-nobel-qfxm1?file=/src/App.js:635-745
Inside the Accordion
const [isExpanded, setIsExpanded] = useState(expanded);
This line will take first time(on first render) value. To assign it next time(rerender) value you need to add a effect
useEffect(() => {
setIsExpanded(expanded);
}, [expanded]);
And in your case, you can use the props expanded directly inside Accordion, you dont need to take it in local state.

export Hooks in React for Nested Components?

I'm exporting hooks with nested components so that the parent can toggle state of a child. How can I make this toggle work with hooks instead of classic classes or old school functions?
Child Component
export let visible;
export let setVisible = () => {};
export const ToggleSwitch = () => {
const [visible, setVisibile] = useState(false);
return visible && (
<MyComponent />
)
}
Parent
import * as ToggleSwitch from "ToggleSwitch";
export const Parent: React.FC<props> = (props) => {
return (
<div>
<button onClick={() => ToggleSwitch.setVisible(true)} />
</div>
)
}
Error: Linter says [setVisible] is unused variable in the child... (but required in the parent)
You can move visible state to parent like this:
const Child = ({ visible }) => {
return visible && <h2>Child</h2>;
};
const Parent = () => {
const [visible, setVisible] = React.useState(false);
return (
<div>
<h1>Parent</h1>
<Child visible={visible} />
<button onClick={() => setVisible(visible => !visible)}>
Toggle
</button>
</div>
);
};
If you have many child-components you should make more complex logic in setVisible. Put object to useState where properties of that object will be all names(Ids) of child-components
as you know React is one-way data binding so if you wanna pass any props or state you have only one way to do that by passing it from parent to child component and if the logic becomes bigger you have to make it as a global state by using state management library or context API with react hooks use reducer and use effect.

Categories