How can I trigger actions between siblings in React Native? - javascript

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);
}
}

Related

React - passing props up components tree through functional components

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.

Updating Parent Component state from multiple child components' componentDidMount() synchronously

Ok so this question is a bit tricky. I have been thinking about whether this is even correct concept wise, considering React is supposed to be a one-way flow of data, from parent to children, and not viceversa. But I would like to post the question anyway so I get different opinions and even possibly a way to get this to work.
In my app, I have a pretty large component that accepts forms as its children, and does some nifty React magic to pass its methods to the children so when the children elements are changed, they trigger the parent components methods that store the data in state and handles the form submissions. It works very nicely, however it is not so good at catching "defaultValues".
In a nutshell, I'm trying to trigger my parent method on the chilren's componentidMount() method, and it works, however, if there's more than one child trying to do this, the method gets called twice but it only uses the second child's dataset.
I have created a simplified version of my issue in the following code:
import React from 'react'
export class Parent extends React.Component {
constructor(props){
super(props)
this.state = {
data : {name:'james'}
}
this.updateData = this.updateData.bind(this)
}
updateData(key,data){
console.log('updating data')
this.setState({
data : {...this.state.data,[key]:data}
})
}
render(){
console.log(this.state)
return (
<div>
<Child1 updateData={this.updateData}/>
<Child2 updateData={this.updateData}/>
</div>
)
}
}
class Child1 extends React.Component {
componentDidMount(){
this.props.updateData('child1','myData')
}
render(){
return (
<div>
I am Child 1
</div>
)
}
}
class Child2 extends React.Component {
componentDidMount(){
this.props.updateData('child2','myData2')
}
render(){
return (
<div>
I am Child 2
</div>
)
}
}
This code will render 'updating data' twice on the console, but it will only update the state with the data sent in child2. Again, I can see how this may not be the best approach considering that im setting the state of a parent from its children, but it would be a good solution for setting default values on a parent component that gets reused a lot with different children.
Im all ears stack overflow
I think the problem is that setState does both updates at the same time (batches them) meaning the same initial state is used when merging both partial states. You need to use updater function as shown by kind user:
this.setState((prevState) => ({ data: { ...prevState.data, [key]: data } }));

How do I reuse a React Component without having to change its inner props?

I don't know if that even exists. I am trying to reuse a component, however, the component I am trying to reuse receive props and already handles them inside of the component.
If I reuse this component in another place I am going to have to change all of the props received.
Is that a common thing when developing in React or I am doing something wrong?
I think you should divide the component with the props to two:
Component A. functionality (and all the props needed)
Component B. a type of container with no props just holding the component A
Hope this helps you!
Yes is common and is often resolved with HoC (High Order Components)
ref: https://reactjs.org/docs/higher-order-components.html
Example
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />;
}
}
}
const EnhancedComponent = logProps(WrappedComponent);

Call React Component Method From Parent

Im new to react and i was wondering how i can call a child components method from the parent?
for example
var ChildClass = class Child {
howDoICallThis () {
console.log('Called!')
}
render () {
return (<Text> Child Class </Text>)
}
}
var ParentClass = class Parent {
// how can i call child.howDoICallThis() ?
render () {
return (<ChildClass> </ChildClass>)
}
}
Can someone explain clearly how i can do this?
This isn't really how things should be done in React. One very common way of approaching these situations is to define methods within a container component that handles business logic, then pass those methods down to presentational components as props.
class Child extends Component {
render () {
return <Text onClick={this.props.handleClick}>Child Class</Text>
}
}
class Parent extends Component {
handleClick = () => {
console.log('Called!')
}
render () {
return <Child handleClick={this.handleClick} />
}
}
Basically, you're wanting to pass things up, where React is designed to pass things down. You can still pass things up using refs and 'lifting state up' (see https://facebook.github.io/react/docs/lifting-state-up.html), but as far as methods go, you should really only ever pass them down. If you structure your components correctly, you shouldn't ever have to call a child's method from the parent.
(btw... make sure you are extending Component. you missed this in you sample code)

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.

Categories