React - Accessing data from child component without rendering child component - javascript

I am trying to access data within a deeply nested child component by passing a callback function from the parent component through various child components.
For example, say that this is the parent component that I am rendering:
function ParentComponent = () => {
const [data, setData] = useState();
const callback = (obj) => {
setData(obj);
}
return <p>{data}</p>
}
And I am trying to retrieve data from the nested component in a different file:
// I need to call this component somehow from the parent component
function InitialChildComponent = ({cb}) => {
return <NestedChildComponent cb={cb}/>
}
// this component cannot directly be called from ParentComponent
function NestedChildComponent = ({cb}) => {
let data = 'abc';
cb(data);
return <p> don't want this displayed </p>
}
Is there any way to run the callback function from within the nested child component without displaying the JSX in that same nested component? Or is there another better method of accessing this data from the nested child component?

Related

Is there any way I can update a mounted React node's props in my current setup?

So I have a mounted React application that is rendered like this (it's via another framework on top):
const componentRender = (entryComponent, initialProps, targetNode) => {
root.render(wrapper);
}
On top level, I call a 3rd party library and wrap my React application inside of it.
What's the best way to integrate React inside of a 3rd party library that its supposed to render into?
The best practice is to update props within a component, not outside of the render function. Otherwise, as you've said, it will cause a full remount.
So the solution would be to call your Ext.extend function from within the App component.
function App() {
const [ready, setReady] = useState(false);
const [props, setProps] = useState({});
// Calls once on first mount.
useEffect(() => {
Ext.extend(Ext.Container, {
listeners: {
afterrender: function () {
setProps(this.myProps);
setReady(true);
},
},
constructor: function (...args) {
Object.assign(this, args);
},
onServerSuccess: function(data) {
// here is where I receive new data
setProps(data);
}
});
}, [])
return (
ready ? <Component {...props} /> : null
);
}
ReactDOM.render(App, rootNode);
If there is more interaction between Ext and the react component, for example you need to pass this.root into a function, you can still do that with the normal DOM APIs like document.getElementById. Or you can add it like this:
// use this reference to do stuff with the react object
this.root = <Component {...props} />
return this.root;
but changing the react component object directly would be an anti-pattern. All state/props updates should happen using APIs provided by the react library.
Also if for some reason you need to render the react component directly inside the Ext.Container node, then you should use ReactDOM.createPortal instead of render.
Here's how that would look.
import { createPortal } from 'react-dom';
const Portal = memo(({ children, domNode }) => createPortal(
children,
domNode,
));
function App() {
const [ready, setReady] = useState(false);
const [props, setProps] = useState({});
// Calls once on first mount.
useEffect(() => {
Ext.extend(Ext.Container, {
listeners: {
afterrender: function () {
setProps(this.myProps);
setReady(true);
},
},
constructor: function (...args) {
Object.assign(this, args);
},
onServerSuccess: function(data) {
// here is where I receive new data
setProps(data);
}
});
}, [])
return (
ready ?
<Portal domNode={extComponentNode}>
<Component {...props} />
</Portal> : null
);
}
Where extComponentNode is the node you want it rendered in. createPortal allows the react component to share state with your react App even though it's rendered in a different place, without the issue of calling render again.
I suppose you want to re-render the component with updated states without unmounting-mouting it whenever the prop(s) passed to the component changes.
Create a useEffect inside this component to re-render the component whenever the required prop(s) changes. To do this, add the prop(s) for which you want to re-render the component in the dependency array of useEffect and pass a function that will update the states of the component.
useEffect(
<function which will update the states, causing re-rendering of the component>,
[ <all props for which you want to run the function separated by a comma> ]
)

How to access a function in a child class component from a stateless parent?

I have a redux-store with objects of initial values. And this store will get updated at a few places within the child component.
I created a stateless functional component as parent
const Parent = () => {
const store = useSelector(state => state);
const getInitState = () => {
depends on store, it will return an object as initial state for child component
}
let initState = getInitState(); //it has to be let instead of const, it could be changed during useEffect
useEffect(() => {
some initialization on mount
}, [])
return ( // return is simplified here
<Child initState={iniState} />
)
}
export default Parent;
I have a class child component something like below
class Child extends Component {
state = {
componentState: this.props.initState
}
....
}
export default Child;
I can't modify the child component It's a very complex component with many sub components which I dont handle.
Now I need to access setState function of child component from parent. Or I need to change the state of child from parent, is there a way to do that?
Yes, I understand a new design should be consider since it's anti-pattern, but I am just wondering if I can do it under current setting.
Thank you all in advance.
==============================================================
Edit: For whoever runs into the same problem, functional component does not support constructor. So I have included a breif correction to the answer.
Define parent as below
import React, { useRef } from "react";
const Parent = () => {
const childRef = useRef(null);
return (
<Child ref={childRef} />
)
}
export default Parent;
Then you are able to use childRef.current to access all function from child component.
The best way is using a react Context , and set state in parent then the child consume state of parent (using react hooks would be so easy than class component)
but in your case as you mentiened (I wonder I can do it under current setting)
you can use react refs :
first put ref prop in your rendered component tag then use it in parent to execute function that's declared inside child
as below :
inside parent component :
const Parent = () => {
.
.
.
constructor() {
//create react ref for our component
this.childComponent = React.createRef();
}
callChildFunction() {
// here using refs you can access function in you child refenrenced component
this.childComponent.cuurent.doSomeUpdateStateStuff(newState);
}
return ( // return is simplified here
<Child ref={this.childComponen} initState={iniState} />
)
...
}
and your child :
class Child extends Component {
state = {
componentState: this.props.initState
}
doSomeUpdateStateStuff(state) {
// stuff updating state callled from parent
}
....
}

cant pass react state to a child after setState()?

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

Updating state from another component in Reactjs

I want to update the state of a main component from the "A" component then push it to "B" component and then use it render to dynamically populate boxes.
Main Component :
constructor() {
super();
this.state = {
events:[],
alerts:[],
};
}
addEvent = newEvent => this.setState(state => {
const {events} = this.state
return [...events, newEvent]
});
addAlert = newAlert => this.setState(state =>{
const {alerts} = this.state
return [...alerts, newAlert]
});
render(){
const {events} = this.state
const {alerts} = this.state
console.log(events) // events are empty even after I pass and store it
// in SearchFlight Component
return(
<div >
<SearchFlight events={events} alerts={alerts} addAlert={this.addAlert} addEvent={this.addEvent} />
<Events events={events}/>
<Alerts />
</div>
);
}
SearchFlight Component(A component) :
handleSubmit= event =>{
const {addEvent} = this.props
const {addAlert} = this.props
event.preventDefault();
var newEvents=[];
var newAlerts=[];
var singleEvent={
event_name:'home',
date_time:'12-08-18 12:45 AM',
};
newEvents.push(singleEvent);
newAlerts.push("Remove the luggage tag");
addAlert(newAlerts);
addEvent(newEvents);
}
Then I have Event Component(B Component) which right now just have render method. I want to get the updated events here.
Problem : Getting empty events when I did console.log(events) in render method of Main Component
You aren't using the setState correctly in addEvents and addAlerts method. the callback pattern of setState needs to return an object
addEvent = newEvent => this.setState(state => {
const {events} = state
return { events: [...events, ...newEvent]}
});
addAlert = newAlert => this.setState(state =>{
const {alerts} = state
return {alerts: [...alerts, ...newAlert]
});
Also since events is an array of objects your need to iterate on them to render. Refer How to render an array of objects in React? answer for more details on how to do that
The State is private to the component.
If the state is passed to the child component using props, again props should not be mutated as per the reactjs guidelines.
You can only read the value in Component A from the props and modify that value to pass it further to the nested components. But can't modify the state of the Main Component from A.
Check this out: https://reactjs.org/docs/state-and-lifecycle.html
As pointed out by OneJeet React is all about top-down approach and the child component should not be aware if a parent component is stateless or stateful.
Passing the setState as a function and allowing the child component to call it, is bad practice. One way is to use redux and eliminate the need to update parent state from child component or re-structure the whole thing in a different way.

How to dynamically create components with Tags?

I want to dynamically create JSX tags for imported components. So my idea is having something like this:
import DemoComponent from './DemoComponent';
class DynamicRendering extends Component {
assembleResult() {
const {
democomponent
} = this.props;
const result = [];
if (democomponent) {
const Tag = `DemoComponent`;
result.push(<Tag />);
}
return result;
}
render() {
const result = this.assembleResult();
return result;
}
}
The idea is that I can pass a couple of different props to the component and then the component dynamically crates JSX tags and assembles them together. The reason I want this because I have about 15 components I want to render dynamically. Instead of implicitly writing them all I would prefer to make a loop over them and create them dynamically if needed. That way I can keep this component DRY.
The problem with the code above is that if you create a Tag like this, it will take it as a HTML element. This causes an error because there are no such HTML elements like 'DemoComponent'. I managed to solve this problem by creating a mapping of the name of the props to the component which should get loaded. See example below:
import DemoComponent from './DemoComponent';
const PROP_MODULE_MAP = new Map([
['democomponent', DemoComponent]
]);
class DynamicRendering extends Component {
assembleResult() {
const {
democomponent
} = this.props;
const result = [];
if (democomponent) {
const Tag = PROP_MODULE_MAP.get('democomponent');
result.push(<Tag />);
}
return result;
}
render() {
const result = this.assembleResult();
return result;
}
}
But I was wondering if there was a simpler way then creating this Map. Is there another way how you can dynamically create JSX tags which represent a imported component?
You can just let the parent pass the desired component type:
Parent.js:
import SomeComponent from './someComponent';
import Child from './child';
// the parent renders Child and passes the type SomeComponent as a prop
const Parent = () => <Child Elem={SomeComponent} />
Child.js:
// the Child renders the component type passed
// note that the prop "Elem" is capitalized so that it will not be treated as a html node
const Child = ({Elem}) => <Elem />;
export default Child;
This way the Child component is capable of rendering any component type that it gets passed. This is much more flexible and does not require the Child to know all the components it should render at compile time.
Note that when rendering the passed component type in the child the variable to render has to be capitalized or it will be treated as a usual html node. See User-Defined Components Must Be Capitalized for details.
If you do not want the prop name to be capitalized you can reassign the value to a capitalized name in the child before rendering it:
const Child = ({elem: Elem}) => <Elem />;
I don't know what is your project layout, but I suggest you could do something like this:
create a file for dynamic component import in your components folder:
import Comp1 from './Comp1'
import Comp2 from './Comp2'
export default { Comp1, Comp2 }
create a helper for function for dynamic rendering:
import components from './components/dynamic'
const renderComponents = compNames => {
// take compNames as a list
const compsToRender = compNames.map(name => components[name])
// get all components by compNames provided and return them in array
return compsToRender.map(Component => <Component />)
}
Then call it where you want .
<App>
{renderComponents(['Comp1', 'Comp2'])}
</App>
If you want to pass props with component names, you could pass objects instead of strings and pass those in components inside the function, but I don't see why it will be better then just use plain components with props

Categories