How to trigger a function from another component in react native? - reactjs

import { copilot, walkthroughable, CopilotStep } from 'react-native-copilot';
class DashboardContent extends Component {
state ={
secondStepActive: true
};
componentDidMount() {
this.props.start()
this.props.copilotEvents.on('stepChange', this.handleStepChange);
}
handleStepChange = step => {
console.log(`Current step is: ${step.name}`);
};
render() {
return (
<View> ...... <View/>
);
}
}
export default copilot({
animated: true,
overlay: 'svg',
})(DashboardContent);
I am using the react-native-copilot library for a walkthrough. I wish to trigger the this.props.start() function which starts the walkthrough using a button from my NavBar component - The _infoPage function in the code below should trigger the function basicaly.
The code for the Navbar is :
class NavBar extends Component {
state ={
isModalVisible: false,
email:'',
emailError: false,
emailErrorMessage: '',
};
_profileEdit() {
Actions.profileedit();
}
_notificationsPage(){
Actions.notifications();
}
_infoPage = () =>{
this.props.toggleTour();
}
toggleModal = () => {
this.setState({isModalVisible: !this.state.isModalVisible});
};
render() {
const {index, routes} = this.props.tabs;
console.log(index);
return (
<SafeAreaView>
<View style={styles.container}>
<StatusBar />
<TouchableOpacity onPress={this._infoPage}>
<MaterialCommunityIcons name="information-outline" size={24} color="#979797" style={{padding:10}}/>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state){
return {
tabs : state.tabs
}
}
function mapDispatchToProps(dispatch){
return {
changeCounter : (index) => dispatch({type:'PAGE_CHANGED',payload: index}),
toggleTour: () => dispatch({
type: 'TOUR_OPENED'
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NavBar);
I was thinking of putting this.props.start() inside a function and calling the function from another component.
How do I go about this?
Here is my App.js
export default class App extends Component {
render() {
return (
<Provider store = {store}>
<Routes />
</Provider>
);
}
}
The call for the NavBar is in Routes:
export default class Routes extends Component {
render() {
return (
<Router navBar={TopNavbar}>
<Scene key="root">
...
</Scene>
</Router>
);
}
}

You could pass the function as a prop in the other component, for example in your render function you could do the following:
render() {
return (
<View>
<YourComponent startTutorial={this.props.start}></YourComponent>
</View>
);
}
Then in YourComponent call this.props.startTutorial(); from a button or any event.
** Update
So in your case you can do the following:
export default class App extends Component {
render() {
return (
<Provider store = {store}>
<Routes /*here*/ startTutorial={this.props.start} />
</Provider>
);
}
}
export default class Routes extends Component {
/*here*/
CTopNavbar = ({ children }) => (
<TopNavbar startTutorial={this.props.startTutorial}>
{children}
</TopNavbar>
);
render() {
return (
<Router navBar={/*here*/CTopNavbar}>
<Scene key="root">
...
</Scene>
</Router>
);
}
}
Then in NavBar run this.props.startTutorial(); in your button.

Related

Transform a stateless Wrap function to a component with state in react

i would like to create a wrapper component in react, and i need to pass some states to the child components.
The error message i get is:
Failed to compile.
./src/app/layouts/MainLayout.jsx
Line 9: 'children' is not defined no-undef
Line 77: 'Children' is not defined no-undef
Thats the the Wrapper component: MainLayout.jsx
import React, { Component } from 'react'
import { Redirect } from 'react-router'
import { Row, Col, Alert } from 'reactstrap'
import PortfolioTitle from './components/sidemodules/PortfolioTitle';
import AppMenu from './components/menu/AppMenu';
const Api = require('./api/PortfolioApi')
class MainLayout extends Component ({ children }) {
constructor(props) {
super(props)
this.state = {
portfolio: {
id: this.getPortfolioId(props),
},
redirect: null,
errors: []
}
}
getPortfolioId(props) {
try {
return props.match.params.id
} catch (error) {
return null
}
}
componentDidMount() {
if (this.state.portfolio.id) {
Api.getPortfolio(this.state.portfolio.id)
.then(response => {
const [error, data] = response
if (error) {
this.setState({
errors: data
})
} else {
this.setState({
portfolio: data,
errors: []
})
}
})
}
}
render() {
const { redirect, portfolio, errors } = this.state
if (redirect) {
return (
<Redirect to={redirect} />
)
} else {
return (
<>
<Row>
{errors.length > 0 &&
<div>
{errors.map((error, index) =>
<Alert color="danger" key={index}>
{error}
</Alert>
)}
</div>
}
<Col xl={3}>
<PortfolioTitle portfolio={portfolio} />
</Col>
<Col xl={9}>
<AppMenu portfolio={portfolio} />
{/* Here goes the wrapped content */}
{Children}
</Col>
</Row>
</>
)
}
}
}
export default MainLayout
This is the content that will be wrapped.
Smartfolio.jsx
import React from 'react'
import MainLayout from './layouts/MainLayout';
import HistoricalRentability from './components/dashboard/HistoricalRentability'
import PortfolioCompostition from './components/dashboard/PortfolioComposition';
function Smartfolio(props) {
var id = props.portfolio.id;
return (
<>
<MainLayout>
<HistoricalRentability />
<PortfolioCompostition id={id} />
</MainLayout>
</>
)
}
export default Smartfolio
I would like some help to understand how can i get this component working, thanks in advance
Your component should be like this,
class MainLayout extends Component {
......
render() {
const { children } = this.props;
return (
......
{ children }
)
}
}
Refer: https://reactjs.org/docs/components-and-props.html

How to set zustand state in a class component

I am working on a site that has a piece a global state stored in a file using zustand. I need to be able to set that state in a class component. I am able to set the state in a functional component using hooks but I'm wondering if there is a way to use zustand with class components.
I've created a sandbox for this issue if that's helpful:
https://codesandbox.io/s/crazy-darkness-0ttzd
here I'm setting state in a functional component:
function MyFunction() {
const { setPink } = useStore();
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
my state is stored here:
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
how can I set state here in a class componet?:
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
{
/* setPink */
}
}
>
Set State Class
</button>
</div>
);
}
}
A class component's closest analog to a hook is the higher order component (HOC) pattern. Let's translate the hook useStore into the HOC withStore.
const withStore = BaseComponent => props => {
const store = useStore();
return <BaseComponent {...props} store={store} />;
};
We can access the store as a prop in any class component wrapped in withStore.
class BaseMyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { setPink } = this.props.store;
return (
<div>
<button onClick={setPink}>
Set State Class
</button>
</div>
);
}
}
const MyClass = withStore(BaseMyClass);
Seems that it uses hooks, so in class you can work with the instance:
import { useStore } from "./store";
class MyClass extends Component {
render() {
return (
<div>
<button
onClick={() => {
useStore.setState({ isPink: true });
}}
>
Set State Class
</button>
</div>
);
}
}
Create a React Context provider that both functional and class-based components can consume. Move the useStore hook/state to the context Provider.
store.js
import { createContext } from "react";
import create from "zustand";
export const ZustandContext = createContext({
isPink: false,
setPink: () => {}
});
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
export const ZustandProvider = ({ children }) => {
const { isPink, setPink } = useStore();
return (
<ZustandContext.Provider
value={{
isPink,
setPink
}}
>
{children}
</ZustandContext.Provider>
);
};
index.js
Wrap your application with the ZustandProvider component.
...
import { ZustandProvider } from "./store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<ZustandProvider>
<App />
</ZustandProvider>
</StrictMode>,
rootElement
);
Consume the ZustandContext context in both components
MyFunction.js
import React, { useContext } from "react";
import { ZustandContext } from './store';
function MyFunction() {
const { setPink } = useContext(ZustandContext);
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
MyClass.js
import React, { Component } from "react";
import { ZustandContext } from './store';
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={this.context.setPink}
>
Set State Class
</button>
</div>
);
}
}
MyClass.contextType = ZustandContext;
Swap in the new ZustandContext in App instead of using the useStore hook directly.
import { useContext} from 'react';
import "./styles.css";
import MyClass from "./MyClass";
import MyFunction from "./MyFunction";
import { ZustandContext } from './store';
export default function App() {
const { isPink } = useContext(ZustandContext);
return (
<div
className="App"
style={{
backgroundColor: isPink ? "pink" : "teal"
}}
>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MyClass />
<MyFunction />
</div>
);
}
If you aren't able to set any specific context on the MyClass component you can use the ZustandContext.Consumer to provide the setPink callback as a prop.
<ZustandContext.Consumer>
{({ setPink }) => <MyClass setPink={setPink} />}
</ZustandContext.Consumer>
MyClass
<button onClick={this.props.setPink}>Set State Class</button>
This worked out pretty well for me.
:
import React, { Component } from "react";
import { useStore } from "./store";
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
useStore.getState().setPink() // <-- Changed code
}
>
Set State Class
</button>
</div>
);
}
}
export default MyClass;
I like to create a high order component similar to redux connect:
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
eg:
import React, { Component } from 'react';
import create from 'zustand';
import shallow from 'zustand/shallow';
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink })),
}));
class MyClass extends Component {
render() {
const { setPink } = this.props;
return (
<div>
<button onClick={() => setPink()}>Set State Class</button>
</div>
);
}
}
const MyClassWithZustand = connectZustand(useStore, (state) => ({ setPink: state.setPink }))(MyClass);
export default function Test() {
const isPink = useStore((state) => state.isPink);
return (
<>
<MyClassWithZustand />
{isPink ? 'Is Pink' : 'Is Not Pink'}
</>
);
}

React Re-Render Component on props Change

I have a Tabbar in my Tabbar Component, Which I Change the index props in it :
class Tabbar extends Component {
state = {
index: this.props.index,
name: this.props.name,
image: this.props.image
};
changeTabs = () => {
this.setState({index: this.props.index});
}
render() {
return (
<React.Fragment>
<div id={this.state.index} className="col">
<button onClick={this.changeTabs}></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And Then In my Other Component, I Wanna Re-Render a fragment after props change. Here's my Code :
import Tabbar from './Tabbar';
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
{index: 0, name: "tab0", image:require('../Assets/profile.svg'),childView: {ProfilePage} },
{index: 1, name: "tab1", image:require('../Assets/home.svg'),childView: {HomePage}},
{index: 2, name: "tab2", image:require('../Assets/blog.svg'),childView: {BlogPage}},
],
}
}
handleRender = () => {
this.state.tabs.map(item => {
if (item.index === this.props.index) {
return <item.childView/>;
}
})
return <BlogPage/>;
}
render() {
return (
<div>
<Header/>
{this.handleRender()}
{this.state.tabs.map(item =>
<Tabbar key={item.index} index={item.index} name={item.name} image={item.image}/>
)}
</div>
);
}
}
export default Tabview;
The Method "handleRender" should handle the rendering.
I tried to use "componentDidMount" or "componentDidUpdate", But I didn't work.
How Can I Make it Work?
Thank you in advance!
You dont need to have a state in the child component for this reason
You can simply have a callback in parent and call it in child component like below.
import React, { Component } from "react";
class Tabbar extends Component {
render() {
return (
<React.Fragment>
<div id={this.props.index} className="col">
<button
onClick={() => this.props.changeTabs(this.props.index)}
></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And in parent you maintain the active index state
import Tabbar from "./Tabbar";
import React, { Component } from "react";
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
//your tabs
],
activeIndex: 0
};
}
handleRender = () => {
this.state.tabs.map((item) => {
if (item.index === this.state.activeIndex) {
return <item.childView />;
}
});
return <div />;
};
render() {
return (
<div>
{this.handleRender()}
{this.state.tabs.map((item) => (
<Tabbar
key={item.index}
index={item.index}
name={item.name}
image={item.image}
changeTabs={(index) => this.setState({ activeIndex: index })}
/>
))}
</div>
);
}
}
export default Tabview;

How do I pass props from top navigation to screen within a nested navigation in react navigation 5

I have a case where I have a nested navigator setting.
The app.js holds the loggedIn state and the navigators start as The startnav gets called in app.js passing loggedIn state:
class StartupNav extends Component {
constructor(props) {
super(props);
console.log(props.loggedIn);
}
render() {
return (
<NavigationContainer independent={true}>
<Stack.Navigator>
{this.props.loggedIn ? (
<>
<Stack.Screen name="MainContainer" component={MainContainer} />
</>
) : (
<Stack.Screen
name="AuthStack"
component={AuthStack}
params="that" //props pass attempt which isnt successful
/>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
}
export default StartupNav;
The authStack holds the login part as :
class AuthStack extends Component {
constructor(props) {
super(props);
console.log('props from authstack', props);
}
render() {
return (
<NavigationContainer independent={true}>
<Stack.Navigator>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="ForgotPassword" component={ForgotPassword} />
</Stack.Navigator>
</NavigationContainer>
);
}
}
Then theres the login screen where I do the login logic and make an attempt to set the state in app.js loggedIn=true. After login is Successful I need to open the MainContainer which contains the loggedin app screens.
I am unable to pass props from the upper level navScreen to the lowers so that I could call prop function when login is successful. I tried as commented in the code like we normally do but this wasnt even valid.
Can anybody shed a light on this, or point me to the right direction.
I'm using react navigation 5.
I think you could do that using context (https://reactjs.org/docs/context.html)
I created an example snack here https://snack.expo.io/wzqRLEbi4
const LoginContext = React.createContext({
loggedIn: false,
setLogin: () => {},
setLogout: () => {}
});
export default class App extends Component {
constructor(props) {
super(props);
this.setLogin = () => {
this.setState({
loggedIn: true
})
};
this.setLogout = () => {
this.setState({
loggedIn: false
})
};
// State also contains the updater function so it will
// be passed down into the context provider
this.state = {
loggedIn: false,
setLogin: this.setLogin,
setLogout: this.setLogout
};
}
render() {
return (
<LoginContext.Provider value={this.state}>
<NavigationContainer independent={true}>
<Stack.Navigator>
{this.state.loggedIn ? (
<>
<Stack.Screen name="MainContainer" component={MainContainer} />
</>
) : (
<Stack.Screen
name="AuthStack"
component={AuthStack}
params="that" //props pass attempt which isnt successful
/>
)}
</Stack.Navigator>
</NavigationContainer>
</LoginContext.Provider>
);
}
}
class Login extends Component {
tryLogin = (setLogin) => {
// your login logic
let isLoginSuccess = true;
// if success
setLogin();
}
render() {
return (
<LoginContext.Consumer>
{({setLogin}) => (
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<TouchableOpacity onPress={() => this.tryLogin(setLogin)}><Text>Login</Text></TouchableOpacity>
</View>
)}
</LoginContext.Consumer>
)
}
}

Fetching api and sending the state using Context api

This is my App.js below and on fetching the data and setState I am
unable to display the date in child, however my data is fetched in
console log. Even i have MyContext in separate file
//App.js
class MyProvider extends Component {
state = { userData: '' }
componentWillMount() {
fetch('https://randomuser.me/api/')
.then(res => res.json())
.then(userData => {
this.setState({ userData: userData })
console.log('here is list =')
console.log(userData);
});
}
render() {
return (
<MyContext.Provider value={{ state: this.state }}>
{this.props.children}
</MyContext.Provider>
);
}
}
class App extends Component {
render() {
return (
<div>
<MyProvider value={{ state : this.state }}>
<Header />
<User />
</MyProvider>
</div>
);
}
}
export default App;
//User.js
//Cannot display gender , email in below code
class User extends Component {
render() {
return (
<div>
<MyContext.Consumer>
{
(context) => (
<React.Fragment>
<p>Gender : {context.state.gender}</p>
<p>Email : {context.state.email}</p>
</React.Fragment>
)
}
</MyContext.Consumer>
</div>
);
}
}
export default User;
In the Context Provider you are setting the data inside userData state and hence you need to access it in consumer like context.state.userData.gender. Also since userData is an object after the response is available, you should initialise it as an object in Provider constructor as well otherwise you would need to add a check in the User component because accessing email and gender from userData
class MyProvider extends Component {
state = { userData: {} }
componentWillMount() {
fetch('https://randomuser.me/api/')
.then(res => res.json())
.then(userData => {
this.setState({ userData: userData })
console.log('here is list =')
console.log(userData);
});
}
render() {
return (
<MyContext.Provider value={{ state: this.state }}>
{this.props.children}
</MyContext.Provider>
);
}
}
class App extends Component {
render() {
return (
<div>
<MyProvider value={{ state : this.state }}>
<Header />
<User />
</MyProvider>
</div>
);
}
}
export default App;
User.js
class User extends Component {
render() {
return (
<div>
<MyContext.Consumer>
{
(context) => (
<React.Fragment>
<p>Gender : {context.state.userData.gender}</p>
<p>Email : {context.state.userData.email}</p>
</React.Fragment>
)
}
</MyContext.Consumer>
</div>
);
}
}
export default User;

Resources