Pass parameter from parent to child through third-party component - javascript

Is it possible to pass a function defined in Component1(only inside this component, not in any other) to Component2 as a property?
If there hasn't been Route component I would use React.cloneElement method.
Route could be used only inside MainComponent definition.
class MainComponent extends React.Component {
render() {
return (
<Component1>
<Route
path={match.url + '/myUrl'}
render={() => (
<Component2 />
)}
/>
</Component1>
)
}
}
Any ideas?

You can do something like:
class MainComponent extends React.Component {
callComponent1func = (...params) => {
if(this.component1Ref && typeof this.component1Ref.component1func === "function"){
this.component1Ref.component1func(...params);
}
};
render() {
return (
<Component1 ref={(element) => this.component1Ref = element}>
<Route
path={match.url + '/myUrl'}
render={() => (
<Component2 component1func={this.callComponent1func}/>
)}
/>
</Component1>
)
}
}
Here function callComponent1func is passed as a prop to Component2 and when it's called it accesses Component1 via ref and calls it's function. Hence achieving your use-case.

Define a prop in Component2 with your function :)
class Component1 extends React.Component {
callback = () => doSomething()
render() {
return (
<Route
path={match.url + '/myUrl'}
render={() => <Component2 callback={this.callback} />}
/>
)
}
}
function Component2({ callback }) {
myFunc();
}

So in component 1:
import { Route, BrowserRouter} from 'react-router';
class Component1 extends React.Component {
myFunc(){
}
render() {
return (
<BrowserRouter>
<Route
path={match.url + '/myUrl'}
render={() => <Component2 callback={this.myFunc} />}
/>
</BrowserRouter>
)
}
}
And in component 2 you can call the function from props using props.muFunc

Do you mean something like this?
import React from "react";
import ReactDOM from "react-dom";
const A = () => {
const woof = () => {
alert("Woof!");
};
return <B woof={woof} />;
};
const B = ({ woof }) => {
return (
<React.Fragment>
<h1>Woofatron</h1>
<button onClick={woof}>click me </button>
</React.Fragment>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<A />, rootElement);
Working example here.

Related

Why is my component unable to access data from my reducer?

I am writing a React app in which somebody can sign up as a business or user, and a user is able to search for a business by name. I do not understand why I am getting an error when trying to render my search component, saying "TypeError: Cannot read properties of undefined (reading 'toLowerCase')". I do not understand why I am getting this error because I believe I am passing in the appropriate data via my reducers and the Redux store. This is my search component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import BusinessCard from '../Business/BusinessCard'
import { Card } from 'semantic-ui-react';
class Businesses extends Component {
state = {newSearch: ""}
handleInputChange = e => {
this.setState({newSearch: e.target.value})
}
render() {
const { businessesReducer} = this.props;
let businessesMatch = businessesReducer.businesses.filter( (business ) => business.name.toLowerCase().includes(this.state.newSearch.toLowerCase()))
return (
<div>
<input placeholder="Search Events and Services Near You" value={this.state.newSearch} name="businessName" type="text" onChange={this.handleInputChange} />
<Card.Group itemsPerRow={3}>
{ businessesMatch.map((business, id) => <BusinessCard key={id} business={business} />)}
</Card.Group>
</div>
)
}
}
const mapStateToProps = (state) => {
return ({
businessesReducer: state.businessesReducer
})
}
export default connect(mapStateToProps)(Businesses);
My businesses reducer:
const initialState =
{
businesses:[],
isLoading: false
}
export default (state = initialState, action) => {
switch (action.type) {
case 'LOADING':
return {
...state,
isLoading: true
}
case "GET_ALL_BUSINESSES_SUCCESS":
return { ...state,
businesses: action.businesses,
isLoading: false
}
default:
return state
}
}
BusinessCard.js (which I am trying to render per the user's search)
import React, { Component } from 'react';
import { Card } from 'semantic-ui-react';
import { connect } from 'react-redux';
class BusinessCard extends Component {
constructor(props) {
super(props);
}
render(){
const { business, businessesReducer } = this.props;
return(
<Card>
<div key={business.id} >
<Card.Content>
<Card.Header><strong>{business.name}</strong></Card.Header>
</Card.Content>
</div>
</Card>
)
}
}
const mapStateToProps = state => {
return {
businesses: state.businesses,
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps)(BusinessCard);
And App.js
import { getAllBusinesses } from './actions/business/business';
import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';
import history from './history';
class App extends React.Component {
componentDidMount() {
this.props.getAllBusinesses();
}
render() {
return (
<Router history={history}>
<div className="App">
<NavBar />
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About} />
<Route path="/services" component={Services} />
<Route path="/shop" component={Shop}/>
<Route path="/login-signup" component={LoginContainer}/>
<Route path="/signup" component={Signup}/>
<Route path="/business-signup" component={BusinessSignup}/>
<Route path="/professional-signup" component={ProfessionalSignup}/>
<Route path="/search" component={Businesses}/>
</Switch>
</div>
</Router>
)
}
}
const mapStateToProps = (state) => {
return {
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps, {getAllBusinesses})(App);
Does anybody have any idea why my search component cannot access "business" and its properties? Everything looks correct to me.
1: It would be good if you could show getAllBusinesses.
2: Please make sure if data exists in your store, you can use redux-dev-tools for that.
3: The first time that your component renders there is no data in your store and it's just an empty array so please first check if name exists and has value then try to convert it to lower case.
It would be something like this:
let businessesMatch = businessesReducer.businesses.filter(
(business) =>
business.name &&
business.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
Or if with optional chaining:
let businessesMatch = businessesReducer.businesses.filter((business) =>
business?.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
If none of these help please provide more information like a code sandbox.

Failed to redirect in react

I have an app component which I want to make a redirection once it lands on it from there if the condition doesn't satisfy and render another component when it finish rendering but somehow when I go to "path/myURl" I don't see my <Home> component:
const App: FunctionComponent<{}> = () => {
useEffect(() => {
if (!localstorage.getItem("token")) {
window.location.replace("/myUrl");
}
}, []);
return (
<>
<Route
exact
path="/myUrl"
render={() => <Home />}
/>
</>
);
};
export default App;
Home component:
const Home: FunctionComponent<{}> = () => {
return <> Hello</>
}
index page:
class WebComponent extends HTMLElement {
render() {
ReactDOM.render(
<root.div style={{ all: "initial" }}>
<BrowserRouter>
<App />
</BrowserRouter>
</root.div>,
this
);
}
connectedCallback(): void {
this.render();
}
}
Usually there is a better way to redirect in React apps. I would use for this purpose useHistory hook because you have a function component.
Try as the following:
import { useHistory } from 'react-router-dom'
const App: FunctionComponent<{}> = () => {
const history = useHistory();
useEffect(() => {
if (!localstorage.getItem("token")) {
history.push('/myUrl');
}
}, []);
// return
}
Additionally you should wrap the component with <Router> and <Switch> components as well in order to make it work.
If you are interested in further options for representing different programmatically possible redirect options for React Apps then check out my GitHub repository:
https://github.com/norbitrial/react-router-programmatically-redirect-examples
I hope this helps!
Use react router:
class WebComponent extends HTMLElement {
render() {
ReactDOM.render(
<root.div style={{ all: "initial" }}>
<BrowserRouter>
<Switch>
<Route
exact
path="/myUrl"
render={() => <Home />}
/>
<Route
path="/-"
render={() => <App />}
/>
</Switch>
</BrowserRouter>
</root.div>,
this
);
}
connectedCallback(): void {
this.render();
}
}
// Your App component
import { useHistory } from 'react-router-dom'
const App: FunctionComponent<{}> = () => {
const history = useHistory();
useEffect(() => {
if (!localstorage.getItem("token")) {
history.push('/myUrl');
}
}, []);
// return
}

How to pass the state of the page to other React?

I want to know how I can pass a status from one page to another page for if used in the other way.
My first page Body.js (Which I handle the state):
import React from 'react';
import './Body.css';
import axios from 'axios';
import { Link } from "react-router-dom";
import User from './User';
class Body extends React.Component {
constructor (){
super();
this.state ={
employee:[],
employeeCurrent:[],
}
}
componentDidMount(){
axios.get('http://127.0.0.1:3004/employee').then(
response=>this.setState({employee: response.data})
)
}
getName = () => {
const {employee} = this.state;
return employee.map(name=> <Link className='link' to={`/user/${name.name}`}> <div onClick={()=>this.add(name)} key={name.id} className='item'> <img className='img' src={`https://picsum.photos/${name.name}`}></img> <h1 className='name'> {name.name} </h1></div> </Link>)
}
add = (name) => {
const nam = name;
this.state.employeeCurrent.push(nam)
console.log(this.state.employeeCurrent)
}
render(){
return(
<div className='body'>
{this.getName()}
</div>
)
}
}
export default Body;
My second page which I want to get the state called employeeCurrent:
import React from 'react';
import Header from './Header';
import Body from './Body';
class User extends React.Component {
constructor (props){
super(props);
this.props ={
employeeCurrent:[],
}
}
render(){
return(
<div >
{this.props.employeeCurrent}
</div>
)
}
}
export default User;
I'm using the React Router, it looks like this:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import './App.css';
import Home from './Home';
import User from './User';
const AppRouter = () => (
<Router>
<div className='router'>
<Route exact path="/" component={Home}/>
<Route path="/user/:id" component={User}/>
</div>
</Router>
);
export default AppRouter;
My project is:
Home page, where you have users, obtained from the API, all users have attributes (name, age, city and country). Saved in employeeCurrent variable:
What I want is: grab these attributes from the clicked user and play on the user page:
Someone would can help me PLEASE?????
Like I explained earlier, you need to lift the state up:
AppRouter (holds the state and passes it to children)
class AppRouter extends React.Component {
state = {
employeeCurrent: [],
employee: []
};
componentDidMount() {
axios
.get("http://127.0.0.1:3004/employee")
.then(response => this.setState({ employee: response.data }));
}
add = name => {
this.setState(prevState => {
const copy = prevState.employeeCurrent.slice();
copy.push(name);
return {
employeeCurrent: copy
};
});
};
render() {
return (
<Router>
<div className="router">
<Route
exact
path="/"
render={props => (
<Home
{...props}
add={this.add}
employee={this.state.employee}
currentEmployee={this.state.currentEmployee}
/>
)}
/>
<Route
path="/user/:id"
component={props => (
<User
{...props}
employee={this.state.employee}
currentEmployee={this.state.currentEmployee}
/>
)}
/>
</div>
</Router>
);
}
}
Body and User (receive parent state as props together with updater functions):
class Body extends React.Component {
getName = () => {
const { employee, add } = this.props;
return employee.map(name => (
<Link className="link" to={`/user/${name.name}`}>
{" "}
<div onClick={() => add(name)} key={name.id} className="item">
{" "}
<img
className="img"
src={`https://picsum.photos/${name.name}`}
/>{" "}
<h1 className="name"> {name.name} </h1>
</div>{" "}
</Link>
));
};
render() {
return <div className="body">{this.getName()}</div>;
}
}
class User extends React.Component {
render() {
// you will need to map employeeCurrent somehow
return <div>{this.props.employeeCurrent}</div>;
}
}

How to pass component reference to routes component in react

In react i want to pass a reference of component to routes component . But when i route to path such as '/' it re render the passed component so starts with 0 again.
Here is a Link of Working example
https://codesandbox.io/s/884yror0p2
Here is a code, whenever i route from Home-->About the about counter starts with 0.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Dummy extends React.Component {
constructor(props) {
super(props);
this.state = {
c: 0
};
setInterval(() => {
this.setState({ c: this.state.c + 1 });
}, 1000);
}
render() {
return <h1> Counter {this.state.c}</h1>;
}
}
class BasicExample extends React.Component {
constructor(props) {
super(props);
this.state = {
// com: <Dummy /> this technique also not work
};
}
render() {
let com = <Dummy />;
return (
<div>
<Router>
<div>
{com}
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<hr />
<Route
exact
path="/"
// render={() => <Home com={com} />} //this also not work
component={props => <Home {...props} com={com} />}
/>
<Route path="/about"
component={props => <About {...props} com={com} />}
/>
</div>
</Router>
</div>
);
}
}
class Home extends React.Component {
render() {
return (
<div>
{this.props.com}
<h2>Home </h2>
</div>
);
}
}
class About extends React.Component {
constructor(props) {
super(props);
console.log(this.props);
}
render() {
return (
<div>
{this.props.com}
<h2>About</h2>
</div>
);
}
}
ReactDOM.render(<BasicExample />, document.getElementById("root"));
You can’t pass a component by “reference”. However, if you would like to have the same data between all the components you could initialize your state in the parent component (BasicExample) and pass it down or use a state container like Redux.
You can the component down but the component will also go through a mounting process that will invalidate the state that you have stored in it.
If you need to hold the state, it should be stored in the parent component.
const Router = ReactRouterDOM.HashRouter;
const Route = ReactRouterDOM.Route;
const Link = ReactRouterDOM.Link;
class Dummy extends React.Component {
constructor(props) {
super(props);
this.state = {
c: props.c || 0
};
}
componentWillReceiveProps(nextProps) {
if (this.props.c !== nextProps.c) {
this.setState({
c: nextProps.c
});
}
}
render() {
return <h1> Counter {this.state.c}</h1>;
}
}
class BasicExample extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
setInterval(() => {
this.setState(prevState => {
return {
counter: prevState.counter + 1
};
});
}, 1000);
}
render() {
let Com = <Dummy c={this.state.counter} />;
return (
<div>
<h2>Main App</h2>
<Router>
<div>
{Com}
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<hr />
<Route
exact
path="/"
// render={() => <Home com={com} />} //this also not work
render={props => (
<Home {...props} c={this.state.counter} Com={Dummy} />
)}
/>
<Route
path="/about"
render={props => (
<About {...props} c={this.state.counter} Com={Dummy} />
)}
/>
</div>
</Router>
</div>
);
}
}
class Home extends React.Component {
render() {
const Com = this.props.Com;
return (
<div>
<h2>Home </h2>
<Com c={this.props.c} />
</div>
);
}
}
class About extends React.Component {
constructor(props) {
super(props);
}
render() {
const Com = this.props.Com;
return (
<div>
<h2>About</h2>
<Com c={this.props.c} />
</div>
);
}
}
ReactDOM.render(<BasicExample />, 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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router/4.2.0/react-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router-dom/4.2.2/react-router-dom.js"></script>
<div id="root"></div>
Few things to note here are:
Use render property of Route to pass in any props
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop
Use function version of setState to refer to prevState

How to pass component callback to children element in react-router?

I would like to wrap my current router by Layout with one MapBox and pass to children MapBox callback function:
<Route path='/posts' component={PostsList} />
<Route path="/posts/:id" component={Post} >
</Route>
Currently to handle callback I repeat MapBox on two components:
export default class Post extends React.Component {
mapMoved(map) {console.log('map moved', map)}
render() {
const {post, posts} = this.props;
return (
<div>
{post.title}
<MapBox mapMoved={::this.mapMoved} posts={posts} />
</div>
);
}
}
const mapStateToProps = (state) => ({
post: state.post,
posts: state.posts,
});
export default connect(mapStateToProps)(Post);
export default class PostsList extends React.Component {
renderList() {
const {posts} = this.props;
}
mapMoved(map) {console.log('map moved', map)}
render() {
return (
<div>
{::this.renderList()}
<MapBox mapMoved={::this.mapMoved} posts={this.props.posts} />
</div>
);
}
}
const mapStateToProps = (state) => ({
posts: state.posts,
});
export default connect(mapStateToProps)(PostsList);
Desired routes wrapped by Layout:
<Route component={Layout}>
<Route path='/posts' component={PostsList} />
<Route path="/posts/:id" component={Post} >
</Route>
</Route>
Desired Layout component:
export default class Layout extends React.Component {
mapMoved(map) {}
render() {
return (
<div>
{this.props.children} //I would like to pass mapMoved callback here
<MapBox mapMoved={::this.mapMoved} posts={this.props.posts} />
</div>
);
}
}
const mapStateToProps = (state) => ({
posts: state.posts,
});
export default connect(mapStateToProps)(Layout);
I'm using Redux, maybe should I pass somehow callback function trough it?
You can pass props to children like so
export default class Layout extends React.Component {
mapMoved(map) {}
render() {
return (
<div>
{React.cloneElement(this.props.children || <div />, {mapMoved: (map) => this.mapMoved(map)})} //I would like to pass mapMoved callback here
<MapBox mapMoved={::this.mapMoved} posts={this.props.posts} />
</div>
);
}
}
const mapStateToProps = (state) => ({
posts: state.posts,
});
export default connect(mapStateToProps)(Layout);

Categories

Resources