Is it possible to display props on a parent component? - reactjs

Is there a way to DISPLAY props on parent component in React?
Is this similar to passing props from child component to parent component?

React and many other modern frameworks (like Vue) have what they call a top-down data flow:
This is commonly called a “top-down” or “unidirectional” data flow.
Any state is always owned by some specific component, and any data or
UI derived from that state can only affect components “below” them in
the tree.
If you imagine a component tree as a waterfall of props, each
component’s state is like an additional water source that joins it at
an arbitrary point but also flows down.
What you can do instead is supply the child component with an event listener and update the parent component's state accordingly.
const ParentComponent = () => {
const [parentState, setParentState] = useState(null)
return (
<ChildComponent
onTextChange={(newState) => setParentState(newState)}
value={parentState}
/>
)
}
const ChildComponent = ({ onTextChange, value }) => {
return <input type="text" value={value} onChange={onTextChange} />
}

Related

How to pass data from child component to parent component without the child component rerendering in React?

My child component is an accordion with controls in it. The problem is, everytime you input data on the controls and it triggers an onChange event which passes its data to its parent component, the accordion closes/collapses and I don't need that behavior.
The accordion is just imported from a library and I can't change its behavior.
Is there anyway to prevent this or pass props/data from child component to parent component without rerendering/restarting DOM of child component?
Parent component:
const ParentComponent = () => {
const handleChangeChildAccordion = (e) => {
console.log(e);
}
return (
<ChildComponentAccordion currentData={currentData} onChangeTPAccordion={handleChangeChildAccordion}/> }
);
}
Child Component:
const ChildComponent = (props) => {
const onDataChangeHandler = (e) => {
props.onChangeTPAccordion(e);
};
return (
<Accordion onChange={onDataChangeHandler}/>
);
}
The child component doesn't remount, it rerenders. Normally, a component rerenders any time any its parent rerenders. To avoid that you:
Avoid changing the child component's props, and
Memoize the child component so it doesn't rerender when its props don't change in a way it cares about.
You do the first by ensuring that, for instance, callbacks you pass the component are stable (they don't change on every render). In a function component, you do it by using useMemo, useCallback, and/or useRef. In a class component, you typically do that by making the callbacks properties on the component instance that aren't recreated.
You do the second by using React.memo (if it's a function component) or for a class component by extending PureComponent (for simple memoization) or implementing shouldComponentUpdate.
You haven't given us much code to work with, so here's a simple example:
const { useState, useCallback, useRef } = React;
const log = (...msgs) => console.log(...msgs);
const Child1 = React.memo(({value, doUpdate}) => {
log(`Child1 rendering`);
return <div>
Child1 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Child2 = React.memo(({value, doUpdate}) => {
log(`Child2 rendering`);
return <div>
Child2 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Parent = () => {
log(`Parent rendering`);
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
// `useCallback` remembers the first function you pass it and returns
// it to you, only returning the new one you pass it if the
// dependencies array changes. This is one way to create a stable
// callback.
const update1 = useCallback(() => setCounter1(c => c + 1), []);
// `useRef` gives you an object you can put arbitrary properties on,
// which is another way to have a stable callback.
const update2Ref = useRef(null);
if (!update2Ref.current) {
update2Ref.current = () => setCounter2(c => c + 1);
}
const update2 = update2Ref.current;
return <div>
<Child1 value={counter1} doUpdate={update1} />
<Child2 value={counter2} doUpdate={update2} />
</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
TypeScript playground with types added
That shows two different ways of making acallback stable (useCallback and useRef). (useMemo is what useCallback uses under the hood, so I didn't do an example of it.)
In that example, notice how the child telling the parent to update its counter doesn't cause the other child to re-render. That's because:
We don't change the other child's props (because our callbacks are stable and the other child's counter didn't change), and
We've memoized the child components
You can customize whether the component re-renders by passing a second argument to React.memo which is a function that says whether the new props coming in are "the same" (for rendering purposes) as the previous ones. By default, React.memo does a simple === comparison on each prop. And again, for class components, PureComponent does the simple === comparison, or you can implement shouldComponentUpdate yourself for more finely-tuned checks.

How do I pass things between components on the same level?

I've got a React app of the form:
<App>
<ParentComponent>
<FormComponent>
<AnotherComponent>
</ParentComponent>
</App>
I want to be able to update some state values of <FormComponent> by clicking on elements in <AnotherComponent>.
I don't want to place them inside each other, but keep them side-by-side. I don't want to lift up <FormComponent> state as it's nicely encapsulated.
What's the best way to achieve this? Can I do it with just react or do I need RxJS/something else?
The data in React Flows Down.
If you don't want to lift the state up, what's left is Context API or any state management library like Redux and MobX (both implement Context API with different strategy).
But still, the state is "above" FormComponent (you still lifting state up).
const Context = React.createContext();
const ParentComponent = () => {
const contextState = useState(DEFAULT_STATE);
return (
<Context.Provider value={contextState}>
<FormComponent />
<AnotherComponent />
</Context.Provider>
);
};
const FormComponent = () => {
const [, setState] = useContext(Context);
// use setState
};
const AnotherComponent = () => {
const [state] = useContext(Context);
// valid state updated from FormComponent
};
As far as I can tell the "right thing" to do in these instances is move the state up one level, into your Parent component.
If you have a look at the Intro to React:
To collect data from multiple children, or to have two child components communicate with each other, you need to declare the shared state in their parent component instead.
"Lifting state up" is a common thing in React applications and doesn't require introducing a state management solution like Redux or RxJS.
Apart from the ways mentioned above you can pass down a function as prop from the Parent component to AnotherComponent. And when clicking any element in Another component, pass the intended value in that function, which will in turn be transferred to ParentComponent. And you can then pass the value as props to the FormComponent from the ParentComponent.
You can check this example for better understanding https://react-m9skpu.stackblitz.io

Adding a component to the render tree via an event handler, the component doesn't seem to receive new props. Why is this?

I have a context provider that I use to store a list of components. These components are rendered to a portal (they render absolutely positioned elements).
const A = ({children}) => {
// [{id: 1, component: () => <div>hi</>}, {}, etc ]
const [items, addItem] = useState([])
return (
<.Provider value={{items, addItem}}>
{children}
{items.map(item => createPortal(<Item />, topLevelDomNode))}
</.Provider>
)
}
Then, when I consume the context provider, I have a button that allows me to add components to the context provider state, which then renders those to the portal. This looks something like this:
const B = () => {
const {data, loading, error} = useMyRequestHook(...)
console.log('data is definitely updating!!', data) // i.e. props is definitely updating!
return (
<.Consumer>
{({addItem}) => (
<Button onClick={() => {
addItem({
id: 9,
// This component renders correctly, but DOESN'T update when data is updated
component: () => (
<SomeComponent
data={data}
/>
)
})
}}>
click to add component
</Button>
)}
</.Consumer>
)
}
Component B logs that the data is updating quite regularly. And when I click the button to add the component to the items list stored as state in the provider, it then renders as it should.
But the components in the items list don't re-render when the data property changes, even though these components receive the data property as props. I have tried using the class constructor with shouldComponentUpdate and the the component is clearly not receiving new props.
Why is this? Am I completely abusing react?
I think the reason is this.
Passing a component is not the same as rendering a component. By passing a component to a parent element, which then renders it, that component is used to render a child of the parent element and NOT the element where the component was defined.
Therefore it will never receive prop updates from where I expected it - where the component was defined. It will instead receive prop updates from where it is rendered (although the data variable is actually not coming from props in this case, which is another problem).
However, because of where it is defined. it IS forming a closure over the the props of where it is defined. That closure results in access to the data property.

How to set a child component without rendering it in the parent component?

My parent component retrieves a list and inflates it as a table.
I want the row click cb (onRowClick) to populate another component with some data from the parent component props/state.
I know this can be done using redux. Is there another way to simply set a child component without rendering it and then set its props on the click cb?
The click cb is currently looking like this:
onRowClick={(event) => {
return <ChildComponent data={props.data.data[event.index]} />
}}
This doesn't call the ChildComponent render function since the components aren't connected.
If you dont want to use redux, you can use simple props to pass a function that sets state, and pass the state data to the other component.
const App = () => {
const [apiData, setApiData] = useState([]);
useEffect(() => {
// fetch api here
setApiData();
})
const [data, setData] = useState();
return (
<>
<Component1 tableData={apiData} onRowClick={setdata} />
<ChildComponent data={data}/>
</>
);
}
If the two components are far apart in the tree, then you can use react contexts to pass data between them.

How to get state data from child to parent component?

I have two components, TabTble and MultiSelect.
I'm rendering MultiSelect component in TabTble, so parent component is TabTble and child component is MultiSelect.
I have some state in my child (MultiSelect) component, how to get it in TabTble component.
const selectedData = this.state.multi; this is state data
const {selectedData } = this.props in this way i passed it as props
<MultiSelect selectedData ={ selectedData } /> And render it in child component like this.
The correct way of sharing state variables between components is through redux store. The major advantage is if you have more than just 2 components, then u can use store state in all of them and modify it by dispatching actions which again will make the modified state available to all the components.
you can do like this suppose you have these two components :
class TabTble extends Component {
handleChildData = (fromMultiSelectData) => {
//Do whatever you want to do from this child data
}
return (
<MultiSelect selectedData ={ selectedData } handleChildData = {this.handleChildData}/>
);
}
wherease in MultiSelect component:
class MultiSelect extends Components {
state = {
localData:[] //local state data which we want to send to parent components
}
handleClick = () => {
let {localData} = this.state;
this.props.handleChildData(localStateData)
}
render(){
let {selectedData} = this.props;
return(
somechild
//Note you need some event handler like onChange,onClick to pass this data up
<div onClick={this.handleClick}>Send Data to parent</div> //you can pass any other data from here
)
}
}
// Note this design pattern is not good while designing react code. Its always recommended when you have such prop drill down or use good state management either context api or redux.
I think you should change a bit your code. Put the states who goes to change in Parent and pass it as a props to child, in child you can change the state.
If u are not using Redux I think it is the right way.
See that example:

Resources