onDismiss function in snackbar doesn't work in react-native-paper - react-native

I'm building mobile application with react-native and react-native-paper.
And I'm using SnackBar component in react-native-paper, and if I use SnackBar component directly, onDismiss function in SnackBar component works well. (It means the snackbar will disappear correctly)
But if I use my original component(like SnackBarComponent component) which uses SnackBar component provided react-native-paper, somehow, the snackbar will not disappear correctly.
This is my custom SnackBar Component and the code which calls my original SnackBar Component.
My original SnackBar Component
import React, { Component } from 'react';
import { Text } from 'react-native';
import { Provider, Snackbar } from 'react-native-paper';
export default class SnackBarComponent extends Component {
constructor(props) {
super(props);
this.state = {
snackbarVisible: false
}
}
render() {
return(
<Provider>
<Snackbar
visible={this.props.snackbarVisible}
onDismiss={() => this.setState({ snackbarVisible: false })}
>
<Text>{this.props.snackbarText}</Text>
</Snackbar>
</Provider>
)
}
}
The code which calls SnackBarComponent(This is not whole code)
import SnackBarComponent from './components/SnackBarComponent';
:
handleShowSnackbar() {
this.setState({
snackbarVisible: true,
snackbarText: 'show snackbar'
})
}
:
<SnackBarComponent snackbarVisible={this.state.snackbarVisible} snackbarText={this.state.snackbarText}/>
:

You have a state containing snackbarVisible which is local to SnackBarComponent and it is initially false.
Then you have snackbarVisible in the parent component state where it's local to the parent component. It is not the same as snackbarVisible in SnackBarComponent.
In case you did not specifically defined a state in parent component containing snackbarVisible, please note that when you run setState method it will create snackbarVisible in the state if not found one.
When you are updating snackbarVisible(dismiss in this case) you have to update the one you defined here visible={this.props.snackbarVisible} which is containing the snackbarVisible in the parent component through the props. Which means you have to update the parent component's snackbarVisible. For that you can pass a callback to the SnackBarComponent and update the right value in the parent component. Here's an example:
//parent component
import SnackBarComponent from './components/SnackBarComponent';
:
handleShowSnackbar() {
this.setState({
snackbarVisible: true,
snackbarText: 'show snackbar'
})
}
//add a function to update the parents state
handleDismissSnackbar = () => {
this.setState({
snackbarVisible: false,
})
}
:
<SnackBarComponent snackbarVisible={this.state.snackbarVisible}
snackbarText={this.state.snackbarText}
dismissSnack={this.handleDismissSnackbar}/> //add here
Then the children component SnackBarComponent in this case as follows:
import React, { Component } from 'react';
import { Text } from 'react-native';
import { Provider, Snackbar } from 'react-native-paper';
export default class SnackBarComponent extends Component {
//you dont need to maintain this local state anymore for this purpose
/*constructor(props) {
super(props);
this.state = {
snackbarVisible: false
}
}*/
render() {
return(
<Provider>
<Snackbar
visible={this.props.snackbarVisible}
onDismiss={() => this.props.dismissSnack()} //use that function here
>
<Text>{this.props.snackbarText}</Text>
</Snackbar>
</Provider>
)
}
}
Now when you press dismiss, it will call the handleDismissSnackbar in parent component by dismissSnack passed through the props.
this is controlling from parent. Example of controlled components. You can find about it more here: https://reactjs.org/docs/forms.html#controlled-components

Related

Is there a way to detemine if the re-render is because of props or state change?

I want to stop the re-render on certain props changes, but if any state is changed i want it to re-render, I know one way is to check for all the states and props, but there's quite some complex data in the states, and i don't want to go through the computation of comparing them, cause i want to re-render on any state change, and only compare and stop the render when props are changed
shouldComponentUpdate life cycle method do this what you want. Lets say this is our App component
import React from 'react';
import Counter from './Counter';
import './style.css';
class App extends React.Component {
constructor() {
super();
this.state = {
appValue: 0,
};
}
render() {
return (
<Counter
appValue={this.state.appValue}
updateHandler={() => {
this.setState({ appValue: this.state.appValue + 1 });
console.log('appValue update: ', this.state.appValue);
}}
/>
);
}
}
export default App;
We use Counter component where we pass appValue and updateHandler as props. And in the Counter component, we render two values - Counter's own state value and appValue which is received as prop. Here is the Counter component:
import React from 'react';
class Counter extends React.Component {
constructor() {
super();
this.state = {
counterValue: 0,
};
}
shouldComponentUpdate(prevProps, prevState) {
if (prevState.counterValue !== this.state.counterValue) {
return true;
} else {
return false;
}
}
render() {
console.log('Inside render of Counter Component');
return (
<>
<div>
<h1>Counter Value: {this.state.counterValue}</h1>
<button
onClick={() => {
this.setState({ counterValue: this.state.counterValue + 1 });
}}
>
Update Counter Value
</button>
</div>
<div>
<h1>App Value: {this.props.appValue}</h1>
<button onClick={this.props.updateHandler}>Update App Value</button>
</div>
</>
);
}
}
export default Counter;
As you notice, when we click the Update App Value button - appValue will increase but won't show the updated value, that means Counter component won't re-render because of prop changing.
And when we click the Update Counter Value button - counterValue will increase and Counter component will re-render as-usual. We will see the updated appValue also.
This will happen because of condition checking inside shouldComponentUpdate life cycle method. To understand more clearly, notice the console.
Here is a little bit about shouldComponentUpdate life cycle method:
The shouldComponentUpdate is a lifecycle method in React. This method makes the component to re-render only when there is a change in state or props of a component and that change will affect the output.
The shouldComponentUpdate() is invoked before rendering an already mounted component when new props or states are being received.
Syntax:
shouldComponentUpdate(nextProps, nextState)

useContext does not forward dispatch function from useReducer

I am trying not to use Redux. So I am stuck with useContext in combination with useReducer for globaal state management. My problem: I can not update the state from a child component using dispatch.
Let me explain in more detail. My context file is pretty straight forward:
import React, { createContext } from "react";
const ActivateContext = createContext();
export default ActivateContext;
I import it in App.js and wrap it around the root component within my navigation:
import React, { useState, useReducer } from "react";
import Navigation from "./Navigation";
import ActivateContext from "./store/activate-context";
const Reducer = (state, action) => {
if (action.type === "ACTIVATE_IT") return true;
};
export default function App() {
let initialState = false;
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Provider store={store}>
<ActivateContext.Provider value={{activeStatus: state, activeDispatch: dispatch}}>
<Navigation />
</ActivateContext.Provider>
</Provider>
);
I then import "ActivateContext" in my child component called "Child". I save everything in the constant "activated". I then use "activated" in the prop called "access":
import React, {useContext} from "react";
import ActivateContext from "../../../store/activate-context";
function Child (props) {
const activated = useContext(ActivateContext);
<MightComponent title="So Amazing" access={activated} />
I tried to add a button to the component "Child" to change the state in App.js but nothing happens:
<TouchableOpacity
onClick={() => ActivateContext.activeDispatch("ACTIVATE_IT")}
>
<Text>Testit</Text>
</TouchableOpacity>
I know useContext works. If I i set "intitialState" to true in App.js and give it as a value to my provider, the "access" prop in the Child component receives "true", which makes the component change its style:
<ActivateContext.Provider value={initialState}>
<Navigation />
</ActivateContext.Provider>
However I do not manage to use useContext to also pass down the dispatch function down the component tree...
Any help is much appreciated.
Thanks!
I think you're trying to access your context values incorrectly in your onClick function, here:
onClick={() => ActivateContext.activeDispatch("ACTIVATE_IT")}
You're passing an object with two fields to your value prop:
<ActivateContext.Provider value={{activeStatus: state, activeDispatch: dispatch}}>
<Navigation />
</ActivateContext.Provider>
So you should be able to access both of these values, in your pages, doing something like:
const {activeStatus, activeDispatch} = useContext(ActivateContext);
And, since your dispatch expects an object with a type field, your onClick function would be something like:
onClick={() => activeDispatch({type: "ACTIVATE_IT"})}

React children's when using HOC to wrap parent

I am using React 16.8.6 and I have the following structure:
page.js
<ParentComponent id="testData">
<ChildComponent value={data => data.text} />
</ParentComponent>
parentComponent.tsx
export default class ParentComponent extends React.PureComponent<IParentProps> {
...
render() {
const items = this.props.children;
<MiddleComponent items={items} />
}
}
ParentContainer.ts
import { withTranslation } from 'react-i18next';
import ParentComponent from './ParentComponent';
export default withTranslation()(ParentComponent);
I need to know inside of MiddleComponent the element type (not as a String but as a React element since I am going to create a new Element based on it) of each child (so, in this case I should have ChildComponent), but when I inspect with chrome, all my children have a I18nextWithTranslation type...
Any idea how to fix this? Or if this is maybe a known bug?
If I don't use any hoc at all, when I write child.type it returns me ChildComponent(props). But this is not true to when I am using hocs to wrap the parent...
The issue was very stupid...
I was importing the <ChildComponent> as a default import even though the child was not exported as default.
Basically
import ChildComponent from '' instead of import { ChildComponent } from ''
In the example below, we're setting Component.displayName on our components so we can access that property in parents. This is a super trivial example that could be expanded to work with an array of children if needed.
const ChildComponent = () => {
return <div>child render</div>
}
ChildComponent.displayName = "MyComponentName"
const ParentComponent = ({ children }) => {
// This is the type of component.. should output "MyComponentName"
const childType = children.type.displayName
return (
<div>
<h1>Render Children</h1>
{children}
</div>
)
}
function App() {
return (
<ParentComponent>
<ChildComponent />
</ParentComponent>
)
}

Getting undefined is not an object (evaluating 'props.navigation.addListener')

I am trying to implement HOC for Backhandler. I have 3 component all are wrapped in createBottomTabNavigator, home is one of them. but before implementing backhandling ,HOC showing this error.
Component home-
import React, { Component } from 'react';
import {Text,View} from 'react-native';
import updateComponent from './HOC/updateComponent';
class home extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View><Text> HOC</Text></View>
);
}
}
export default updateComponent(home);
HOC updateComponent
import React, { Component } from 'react';
const updateComponent = WrappedComponent => {
class NewComponent extends Component {
constructor(props) {
super(props);
}
render() {
return <WrappedComponent />;
}
}
return NewComponent;
};
export default updateComponent;
Your home component should be capitalized, from React docs:
When an element type starts with a lowercase letter, it refers to a
built-in component like or and results in a string 'div'
or 'span' passed to React.createElement. Types that start with a
capital letter like compile to React.createElement(Foo) and
correspond to a component defined or imported in your JavaScript file.
We recommend naming components with a capital letter. If you do have a
component that starts with a lowercase letter, assign it to a
capitalized variable before using it in JSX.
Another thing (not sure causing the error but you'll have bugs later on), is that you don't pass the props to the NewComponent, which means every time you will wrap a component with updateComponent you'll lose all the props.
Solution:
home -> Home.
return <WrappedComponent /> --> return <WrappedComponent {...this.props} />.

React Router 4 can not load new content on same component with <Link>

I can't seem to trigger any other react component life cycle method other than render() when I click on a link that leads to a page that loads exactly the same component, even though the url is different. So here's my code
//index.js - the entry point
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import Config from './Settings/Config';
import App from './Components/App';
const c = new Config();
render(
<BrowserRouter basename={c.routerBaseName}>
<App />
</BrowserRouter>
, document.getElementById('root'));
Here's my App JS
// Components/App.js
import React, {Component} from 'react';
import {Route} from 'react-router-dom';
import BlogEntry from './BlogEntry';
export default class App extends Component {
render() {
console.log('app');
return (
<div>
<Route exact path="/blog/:name" component={BlogEntry} />
</div>
)
}
}
And here is my BlogEntry.js
// Components/BlogEntry.js
import React from 'react';
import { Link } from 'react-router-dom';
export default class BlogEntry extends React.Component {
async componentDidMount() {
const [r1] = await Promise.all([
fetch(`http://api.myservice.com/${this.props.match.params.name}`)
]);
this.setState({content:await r1.json()});
console.log('fetch');
}
render() {
console.log('render');
if(!this.state) return <div></div>;
if(!this.state.content) return <div></div>;
const content = this.state.content;
return (
<div id="blog-entry" className="container">
<h1>{content.title}</h1>
<div dangerouslySetInnerHTML={{__html:content.content}}></div>
<div className="related-other">
<h2>Related Content</h2>
<ul>
<li><Link to="/blog/new-york-wins-the-contest">New York Wins the Contest!</Link></li>
<li><Link to="/blog/toronto-with-some-tasty-burgers">Toronto with Some Tasty Burgers</Link></li>
</ul>
</div>
</div>
);
}
}
So what happens is that when I click on the link for Toronto with Some Tasty Burgers or New York Wins the Contest! I see the url in my web browser address bar update accordingly. But my componentDidMount does not fire. And hence no new content is fetched or loaded.
React also won't let me put an onPress event handler to the <Link> object. And even if I did, managing the history state when browser clicks back button would be a nightmare if I were to create my own onpress event handler to load pages.
So my question is, how do I make it so that clicking on one of the links actually causes the component to fetch new data and redraw and also be part of the browser back button history?
I added this to my BlogEntry.js and everything works now:
componentWillReceiveProps(nextProps) {
this.props = nextProps;
}
I don't think your proposed solution, via componentWillReceiveProps (deprecated) is good enough. It's a hack.
Why don't you keep the route id in the state (as in /blog/:id).
Then something like this:
componentDidUpdate() {
const { match: { params: { id: postId } = {} } } = this.props;
if(this.state.postId !== postId) {
// fetch content
}
}