React - passing props up components tree through functional components - javascript

I want to pass a simple string, number or boolean up more than one level in my component tree. From what I read I need to do this with callback functions but I can't seem to get the logic right.
Here is a sample of where I pass a prop down from Parent App to grandchild Breadcrumb. I would like this prop to actually come from the last child in the tree, the "ResultsPage" component.
I realise there are better ways of doing sth like this (redux, context, different structure, etc), the point here for me is learning and to understand how to use callback functions and how to pass a prop up several more than 1 level.
Newbie friendly please - thanks for any input :)
class App extends React.Component {
render() {
return (
<>
<h1>Top level app</h1>
{/* I import the header and pass down prop */}
<Header currentLocation="Results Page" />
{/* I import the main app content */}
<ResultsPage />
</>
);
}
}
function Header(props) {
return (
<>
<h2>
This is the header element. It will have some nav items and the
breadcrumb I import
</h2>
{/* I import the breadcrumb accept the props from parent and pass the props down to child */}
<Crumbs currentLocation={props.currentLocation} />
</>
);
}
function Crumbs(props) {
return (
<>
{/* I display the props I passed down through the tree */}
<h3>
<small>This is the breadcrumb, you are on</small>{" "}
{props.currentLocation}
</h3>
</>
);
}
function ResultsPage() {
return (
<>
<p>
This is the actual results content. I would like this component to tell
the header component that I have loaded so it can update the breadcrumb
to let it know which page is currently loaded in the app.
</p>
</>
);
}
export default App;
To complete this issue I lewave the following solutions:
Codesandbox: Solution to the initial question
Codesandbox: Additional solution for the same problem using only functional components
Hope it helps the next guy :)

Maintain a local state variable to store the location, and pass a callback function through the props to set it.
class App extends React.Component {
constructor(props){
super(props);
this.state = {
currentLocation : "InitialLocation"
}
}
changeCurrentLocation = (newLocation) => {
this.setState({currentLocation : newLocation})
}
render() {
...
<ResultsPage callback={this.changeCurrentLocation}/>
}
}
The changeCurrentLocation function takes the new location as argument and modifies the state. Everytime the state changes, the render function is called again. This would refresh the view with updated state information, in your case - currentLocation.
function ResultsPage({ callback }) {
useEffect(() => {
callback('My Results');
}, [callback])
return (
...
);
}

Best way is to do this as I think keep the state inside a redux store. You can create a listener using subscribe() method in the redux to listen any dispatches from the child components from the parent component.
Also there is some easy method, You can use localstorage. You can store value from the child component and listen it by the parent component using window.addEventListener('storage', function(e) { } callback method. I hope you can understand what I tried to say.

Related

Understanding HOCs with Redux

I have a users component that just displays a list of users. I have tried to wrap it in a HOC loading component so that it only displays once the users are loaded, otherwise shows a loading spinner (well just text for now)
this is my HOC:
const Loading = (propName) => (WrappedComponent) => {
return class extends React.Component{
render(){
return this.props[propName].length === 0 ? <div> Loading... </div> : <WrappedComponent {...this.props} />
}
}
}
export default Loading;
at the bottom of my users component I have this:
export default connect(
mapStateToProps,
mapDispatchToProps
)(Loading('users')(Users));
currently, the word Loading... is just staying on screen. and propName is coming through as undefined
I think for some reason the users component is never getting populated. what have i done wrong?
Update after comments
My answer below is a misleading one since I hadn't understood your intention properly at that time. Also, my explanation about not getting props is somehow wrong. It is true if we don't render the components but here you are doing it. So, the problem was not that.
The problem here is your Loading component isn't rendered again after fetching users. Actually, you never fetch the users :) Here are the steps of your app (probably).
You are exporting a HOC function, not the Wrapped one here. It comes from your Users file but it does not export the real Users component. This is important.
Your parent renders the first time and it renders the exported HOC component.
Your child component renders and fall into Loading one not the Users one.
In Loading your users prop is empty, so you see Loading....
Your Users component never renders again. So, fetching the users there don't update the state.
Your solution is extracting the fetch out of Users and feed this component. Probably in a parent one. So:
Parent fetches the users then renders itself and all its children.
Your Loading HOC component renders a second time.
I don't know how do you plan to use this HOC but if I understood right (since I'm not so experienced with HOC) in your case the problem is you are not passing any prop to the Loading function. This is because you are not using it as a regular component here. It is a function and propName here is just an argument.
When we render a stateless function like this:
<Loading propName="foo" />
then there will be a props argument for our function. If we don't render it like that there will be no props argument and no props.propName. If this is wrong please somebody fix this and explain the right logic. So, you want to do something like this probably:
class App extends React.Component {
render() {
return (
<div><FooWithLoading /></div>
);
}
}
const Loading = (users) => (WrappedComponent) => {
return class extends React.Component {
render() {
return (
users.length === 0 ? <div> Loading... </div> :
<WrappedComponent
users={users}
/>
);
}
}
};
const Foo = props => {
return (
<div>
Users: {props.users}
</div>
);
}
const FooWithLoading = Loading("foobar")(Foo);
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
So in your case:
export default connect(
mapStateToProps,
mapDispatchToProps
)(Loading('users')(Users));
should work?
Or you need to render your component properly in a suitable place of your app.

How can I trigger actions between siblings in React Native?

export default class Parent extends Component {
render() {
return (
<View>
<Sibling1/>
<Sibling2/>
</View>
);
}
}
Let's say the user touches sibling1 and I want sibling2 to turn green as a result. This tutorial explains how I can pass information between siblings, but not how I can prompt the receiving component to realize that the change occurred:
Not surprisingly, to pass data between siblings, you have to use the parent as an intermediary. First pass the data from the child to the parent, as an argument into a callback from the parent. Set this incoming parameter as a state on the parent component, then pass it as a prop to the other child (see above example). The sibling can then use the data as a prop.
I'm deeply confused about why React Native wouldn't make such a thing intuitive and easy since it's an extremely common need in any application and completely trivial to do on the browser with a basic library like JQuery.
How can I trigger actions between siblings in React Native?
Use componentWillReceiveProps lifeCycle to get a notification of when the component receives an update on one or more of its props.
export default class Parent extends Component {
render() {
return (
<View>
<Sibling1 onClick={() => this.setState({ color: '#00ff00')}/>
<Sibling2 color={this.state.color} />
</View>
);
}
}
export default class Sibling2 extends Component {
componentWillReceiveProps(nextProps) {
if(nextProps.color != this.state.color) {
// here you know the changed ocurred
this.setState({ color: nextProps.color });
}
}
render() {
return (this.state.color);
}
}

update child component when parent component changes

I'm kind of new to React js. I have two questions. Here I go
Question 1
I wanted to update(re-render) the child component when I update(re-render) the parent component. After some googling, I found out that when the props are changed and we call the forceUpdate function only the parent component is updated not the child component. If I wanted to update the child component I needed to needed to use setState function and set something in the state to update the child. But the problem that I'm facing is that when I set state in the parent component the child component is not updated. The render method of the child is not called. Can somebody please tell me what is it that I'm doing wrong?
Parent.jsx
class Application extends React.Component {
isBindEvents=false;
componentWillMount(){
let {dispatch} = this.props;
dispatch(getCompanyInfo( "abcd.com", (res) => { // THIS IS A ACTION CALL
this.props.user = res.data.user;
this.setState({name:res.data.user.name})
this.forceUpdate()
}))
}
render(){
return ( <div className='react-wrapper'>
<ABCD {...this.props} /> // THIS IS THE CHILD COMPONENT WHICH I WANT TO RE-RENDER WHEN THE PARENT COMPONENT CHANGES
<div >
<div id="body" >
<div>
<div >
<div className="container-fluid">
{this.props.posts}
</div>
</div>
</div>
</div>
</div>
</div>)
}
}
export default connect(mapStateToProps)(Application)
CHILD.JSX
class ABCD extends React.Component {
render() {
let isShowUser = this.props.user
? true
: false;
return (
<div> Here is the user name {isShowUser? this.props.user.name: 'user not present'} </div>
);
}
}
export default connect(mapStateToProps)(ABCD);
Now what I'm doing is that when the application component is mounting I generate an ajax call to my backend server and when it return it updates the props and I set the state so that that child component is also rerendered but the child component is not re-rendering. Can somebody please tell me what's is going wrong.
Question 2
The next question is related to react router I'm using react router for the routeing.This is how I'm using router
module.exports = {
path: '/',
component: Application,
indexRoute: require('./abcd'),
childRoutes: [
require('./componentTwo'),
require('./componentOne'),
]
};
Now let's suppose I'm going to component Two route which will render component Two and I generate a ajax call in application component and on the basis of the data returned by the ajax call I set some props in the application component and I also want the component Two to re-render as soon some props are changed in application is there any way to do that
Thanks any help will be appreciated
this.props.user = res.data.user;
You can't assign to props. Props are passed from a parent. Set the user in the state and pass the state to your child component like so:
<ABCD {...this.props} user={this.state.user} />
In your child component you will now have access to this.props.user. Also the this.forceUpdate() will not be needed then.

Is this the correct way to set the state of parent component in react.js

I set all the context of my main component from my child component and it works fine, but I don't know if this is correct
This is my main component
import Child from "./apps/child";
export default class NewTest extends Component {
constructor(){
super();
this.state={
one:1,
}
}
render() {
return (
<View style={styles.container}>
<Text>{this.state.one}</Text>
<Child root={this}/> //<----- Here i set all the context of my main Component to the child component
<TouchableOpacity onPress={()=>console.log(this.state.one)}>
<Text>Touch</Text>
</TouchableOpacity>
</View>
);
}
}
and this is my child component
export default class Child extends Component{
constructor(props){
super(props);
this.parent=this.props.root;
}
render(){
return(
<View>
<TouchableOpacity onPress={()=>{
this.parent.setState({one:this.parent.state.one+1}) // <- if you see here i change the state of the parent, and it work fine
}}>
<Text>Sum to Parent</Text>
</TouchableOpacity>
</View>
)
}
}
All this works, but is this the correct way to do it?
No, it's not. If you want to change the state of the parent component you should send a callback function as a prop to the child and then invoke it. For example, you could have a function in your NewTest:
increaseOne(){
this.setState({one:this.state.one+1})
}
Then, send it to the child with a prop:
<Child increaseOne={this.increaseOne.bind(this)}/>
Finally invoke it in the child onPress:
<TouchableOpacity onPress={this.props.increaseOne}>
If the application gets too complex, you could use Redux.
It is not the best "React way" to do it. It would have been better to pass a function to the child component that should work as a callback (something like "sumToParent"); and the parent would react on it by making the sum.
Another option could be using react-router and react-redux to maintain the state.
What you are trying to do, is to get full control over over component. This is not the best way to solve the problem, and the main reasoning that it will sooner or later strike you back. The idea is to pass hanlders via props, so they will be just invoked, but without any knowledge how they work.
So, in the code (function has to be bound already):
<Child increase={this.increase} decrease={this.decrease} />
With this approach you'll get much more easier to maintain and refactor solution – for instance, form can invoke passed submit function, which in the beginning can just fake it through setTimeout, but later will be replaced with real call.
It also increases testability, looses coupling and leads to the better code in general.

How to pass optional elements to a component as a prop in reactjs

I am trying to figure out the proper "react" way to pass in an optional prop that is an Element to a container component, that is handled differently from the children of that component.
For a simple example, I have a Panel component, which renders its children, that also has an optional "title" prop (which is an element rather than a string, for the sake of the example) that gets specially rendered (put in a special spot, with special behaviors in while maintaining the abstraction.
One option is to have a component which is pulled out of the children and rendered specially:
<Panel>
<Title> some stuff</Title>
<div> some other stuff</div>
</Panel>
But it seems wierd to have the children pulled out and handled separately like that.
How is this normally handled in react, and am I even thinking about this the right way
You don't need to do anything special. Just pass the title component as a prop, and then use {this.props.title} wherever you want it to be rendered:
class Panel extends React.Component {
render() {
return <div>
{this.props.title}
<div>Some other stuff...</div>
</div>;
}
}
class App extends React.Component {
render() {
var title = <Title>My Title</Title>;
return <Panel title={title}/>;
}
}
If you don't pass any value for the title prop (or if the value is false, null, or undefined) then nothing will be rendered there.
This is a fairly common pattern in React.
you can do something like this
render(){
<div>
{this.props.title ? this.props.title : null}
{this.props.children}
</div>
}
basically if you pass a title element as a prop then create it as an element and render it. else just put in null...
to create it you would do something like this.
<Panel title={<Title>Something Here</Title>}
<div> something here</div>
</Panel>
This is generally how react should handle optional child components
When you need an attribute from your optional prop, you will have to check first if the prop was delivered. Otherwise, you will get a:
TypeError: Cannot read property 'yourPropProperty' of undefined
In conditional rendering context (depending on my optional this.props.ignore array), this won't work:
{!this.props.ignore.includes('div')) && (
<div>
Hey
</div>
)}
Instead, you should do:
{(!this.props.ignore || !this.props.ignore.includes('div'))) && (
<div>
Hey
</div>
)}
One thing you can do is have default props (usually initialised to a no-op) for your component.
For example, if you want to have an optional function prop:
class NewComponent extends React.Component {
...
componentDidMount() {
this.props.functionThatDoesSomething()
}
}
NewComponent.defaultProps = {
functionThatDoesSomething: () => {}
}
This way, parent components can choose to pass the function prop or not and your app won't crash due to the error
this.props.functionThatDoesSomething is not a function
.

Categories