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);
Related
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.
My question is a bit different from other ones on stack-overflow because I already am using Redux, and my problem is that the data is not loaded properly. Let me explain:
I have two components Bookings and Rooms, and it is present in my Routes:
class Routes extends React.Component {
render() {
return (
<Switch>
<Route path='/' exact component={Bookings} />
<Route path='/bookings' component={Bookings} />
<Route path='/rooms' component={Rooms} />
</Switch>
);
}
}
Simplified version of Bookings:
class Bookings extends React.Component {
componentDidMount(){
this.props.load(services['bookings']);
}
render() {
const bookingsList = this.props.bookings.map(booking => <p>Booking ID: {booking.id} Booking: {booking.name} Room ID: {booking.room_id}</p>)
return <>
<p>Bookings are</p>
{bookingsList}
</>
}
}
function mapStateToProps(storeState){
let bookings = storeState.bookingsState;
return { bookings : bookings };
}
function mapDispatchToProps(dispatch){
let actionDispatchers = bindActionCreators(actionCreators, dispatch);
return actionDispatchers;
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Bookings);
Rooms:
class Rooms extends React.Component {
componentDidMount(){
this.props.load(services['rooms']);
}
render() {
const roomsList = this.props.rooms.map(room => <p>Room ID: {room.id} Name: {room.name}</p>)
return <>
<p>Rooms are:</p>
{roomsList}
</>
}
}
function mapStateToProps(storeState){
let rooms = storeState.roomsState;
return { rooms : rooms };
}
function mapDispatchToProps(dispatch){
let actionDispatchers = bindActionCreators(actionCreators, dispatch);
return actionDispatchers;
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Rooms);
Each of the components were working very well independently. I wanted to use roomsState inside Bookings to resolve Room IDs to Room Names. So I added roomsState in mapStateToProps:
function mapStateToProps(storeState){
let bookings = storeState.bookingsState;
let rooms = storeState.roomsState;
return { bookings : bookings, rooms: rooms };
}
But the problem here, I am guessing is that data is not loaded into roomState till the user navigates to the Rooms component:
How should I solve it the proper react-redux way?
Call your load action in your Routes component and pass it down to your Bookings like this:
class Routes extends React.Component {
componentDidMount(){
this.props.load(services['rooms']);
}
render() {
return (
<Switch>
<Route
path='/'
render={(props) => <Bookings {...props} rooms={this.props.rooms} />}
/>
<Route
path='/bookings'
render={(props) => <Bookings {...props} rooms={this.props.rooms} />}
/>
<Route
path='/rooms'
render={(props) => <Rooms {...props} isAuthed={true} />}
/>
</Switch>
);
}
}
function mapStateToProps(storeState){
let rooms = storeState.roomsState;
let bookings = storeState.bookingsState;
return { rooms : rooms, bookings: bookings };
}
function mapDispatchToProps(dispatch){
let actionDispatchers = bindActionCreators(actionCreators, dispatch);
return actionDispatchers;
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Routes);
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.
We are starting a new project in React.
And we need to use:
React Context API
i18n (react.i18nex)
GraphQL (Apollo client)
Redux
CCS-in-JS (styled-components or aphodite)
The thing is that any of this implementation wraps a component and pass it some props or use functions as children.
And I want to keep my components as decoupled and clean as possible.
How can I structure the wrappers?
Example
Home
<Home {...props} />
Context:
<ThemeContext.Consumer>
{ theme=> <Home {...theme} programa={theme} /> }
</ThemeContext.Consumer>
i18n:
<I18n>
{t => <Home text={t("translated text")} /> }
</I18n>
GraphQL:
<Query query={GET_PROGRAMA}>
{({ data }) => <Home data={data} />}
</Query>
Redux:
const mapStateToProps = state => ({
user: "some user"
});
export default connect(
mapStateToProps,
)(Home);
As you can see, the Home component receives isolated props from many sources.
How can I manage ir and keep it decoupled? There is some kind of composer?
Thank you!
I think you can convert this into an HOC that will handle all of the component wrapping for you:
const withWrappers = WrappedComponent => {
return class extends React.Component {
render () {
return (
<ThemeContext.Consumer>
{ theme =>
<I18n>
{ t =>
<Query query={GET_PROGRAMA}>
{ ({ data }) =>
<WrappedComponent
{...this.props}
{...theme}
programa={theme}
data={data}
text={t("translated text")}
/>
}
</Query>
}
</I18n>
}
</ThemeContext.Consumer>
)
}
}
}
Usage:
class Home extends React.Component {
render () {
return (
<div>Home</div>
)
}
}
export default withWrappers(Home);
Thanks for your help! All of you.
I've implemented a HOC, the same as you porposed to me, and keep the GraphQL components outside:
import React from "react";
import PropTypes from "prop-types";
import { mergeStyles } from "js/utils";
import { I18n } from "react-i18next";
import { TemplateContext } from "js/template-context";
const Wrapper = ({ Component, getStylesFromTemplate }) => props => {
const { classes } = props;
return (
<TemplateContext.Consumer>
{programa => {
const { template } = programa;
const stylesFromTemplate = getStylesFromTemplate(template);
const styles = mergeStyles({ classes, stylesFromTemplate });
return (
<I18n>
{(t, { i18n }) => {
return (
<Component
t={t}
styles={styles}
programa={programa}
{...props}
/>
);
}}
</I18n>
);
}}
</TemplateContext.Consumer>
);
};
Wrapper.propTypes = {
classes: PropTypes.object.isRequired
};
export default Wrapper;
Again, thank you so much!
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>;
}
}