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

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.

Related

How can I access my context API state in react app.js file

Hi I am managing my react app state with Context API, and I am able to destructure of the state and functions of my Context in all files except my app.js file.
Here I created a reusable file called 'createDataContext' to prevent writing duplicate code when creating a Context and Provider for each Context.
import React, { createContext, useReducer } from 'react';
export default (reducer, actions, initialState) => {
const Context = createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for(let key in actions){
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{ children }
</Context.Provider>
)
}
return { Context, Provider }
}
Here is my AuthContext which uses createDataContext file
import createDataContext from "./createDataContext";
import api from '../api/api'
const authReducer = (state, action) => {
switch(action.type){
case 'signin':
return { errorMessage: '', token: action.payload };
case 'add_error':
return { ...state, errorMessage: action.payload };
default:
return state;
}
}
const login = dispatch => {
return async (email, password) => {
try {
const res = await api.post('/users/login', { email, password });
localStorage.setItem('token', res.data.token);
dispatch({ type: 'signin', payload: res.data.token });
}catch(err){
dispatch({ type: 'add_error', payload: 'signin failed' })
}
}
}
export const { Context, Provider } = createDataContext(authReducer, { login }, {
token: null,
errorMessage: ''
});
I am able to use the context and destructor off the state and function like login in my pages and components but unable to do so in my app.js file.
import React, { useContext } from 'react';
import Home from './pages/home/Home';
import Login from './pages/login/Login';
import Profile from './pages/profile/Profile';
import Register from './pages/register/Register';
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';
import Navbar from './components/navbar/Navbar';
import { Provider as AuthProvider } from './context/AuthContext';
import { Context } from './context/AuthContext';
function App() {
const { state, login } = useContext(Context);
return (
<AuthProvider>
<Router>
<Navbar />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register />
</Route>
<Route path="/profile/:id">
<Profile />
</Route>
</Switch>
</Router>
</AuthProvider>
);
}
export default App;
I am getting error TypeError: Cannot destructure property 'state' of 'Object(...)(...)' as it is undefined. Can someone please point out what I'm or where I'm going wrong. Thanks.
You cannot use the context in the same component where it is being provided. To get around this create another new component that is a wrapper for the rest of the children of the Provider
// AppWrapper.jsx
import React from 'react'
import App from './app'
import { Provider as AuthProvider } from './context/AuthContext'
const AppWrapper = () => {
return (
<AuthProvider>
<App />
</AuthProvider>
)
}
default export AppWrapper
Then remove the <AuthProvider> from your app.js component, and wherever you were calling <App /> in the tree, call the <AppWrapper /> instead. Then inside of the App you can use the Context like you are trying to do.

ReactJS - Issues about Dynamic Routes

I have these components. I want to turn every into a dynamic url. For example, when accessing in the browser, http://localhost:3000/houses/1 I want to appear the House 1.
The other things in the application are working fine. I just want to solve this problem of implementing dynamic routes.
The data is fetched from a json file
db.json file
[
{
"houseId": 1,
"name": "House 1",
"photos": [
"house1_001.jpg",
"house1_002.jpg",
"house1_003.jpg",
"house1_004.jpg"
]
},
{
"houseId": 2,
"name": "House 2",
"photos": [
"house2_001.jpg",
"house2_002.jpg",
"house2_003.jpg",
"house2_004.jpg"
]
},
{
"houseId": 3,
"name": "House 3",
"photos": [
"house3_001.jpg",
"house3_002.jpg",
"house3_003.jpg",
"house3_004.jpg"
]
}
]
Router Component
import React from 'react';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom'
import App from './App'
import Intro from './Intro'
import Houses from './Houses'
import House from './House'
export default props => (
<Router>
<Route exact path='/' render={() => <App />} >
<Route exact path='/intro' render={() => <Intro />} />
<Route exact path='/houses' render={() => <Houses />} />
<Route exact path='/houses/:houseId' render={(props) => <House {...props} />} />
</Route>
</Router>
)
Houses Component
import React, { Component } from 'react'
import House from './House'
var data = require('./db.json');
class Houses extends Component {
constructor(props) {
super(props);
this.state = {
houses: []
};
}
componentDidMount() {
this.setState({
houses: data
})
}
render() {
const { houses } = this.state;
return (
<div className="content house">
{
houses.map((house, index) => {
return (
<div>
<House house={house} />
</div>
)
})
}
</div>
)
}
}
export default Houses
**House Component**
import React, { Component } from 'react';
class House extends Component {
constructor(props) {
super(props)
this.state = {
houseId: ""
}
}
componentDidMount() {
this.setState({
houseId: this.props.match.params.id
})
}
render() {
return (
<div>
<h3>{this.props.house.name}</h3>
<ul>
{this.props.house.photos.map((photo, index) => {
return (
<li><img src={`/images/${photo}`} /></li>
)
})
}
</ul>
</div>
)
}
}
export default House;
House component
import React, { Component } from 'react';
class House extends Component {
constructor(props) {
super(props)
this.state = {
houseId: ""
}
}
componentDidMount() {
this.setState({
houseId: this.props.match.params.id
})
}
render() {
return (
<div>
<h3>{this.props.house.name}</h3>
<ul>
{this.props.house.photos.map((photo, index) => {
return (
<li><img src={`/images/${photo}`} /></li>
)
})
}
</ul>
</div>
)
}
}
export default House;
Pass the json data to <House/> component and use the id to display the correct data.
import React, { Component } from 'react';
const data = require('./db.json');
class House extends Component {
constructor(props) {
super(props)
this.state = {
houses: data,
}
}
render() {
const houseId = this.props.match.params.houseId;
return (
<div>
<h3>{this.state.houses[houseId].name}</h3>
<ul>
{this.state.houses[houseId].photos.map((photo, index) => {
return (
<li><img src={`/images/${photo}`} /></li>
)
})
}
</ul>
</div>
)
}
}
export default House;
Create two components, one will be rendered in Houses and one will be render on house/1
// rendered inside Houses
class House extends Component {
render() {
return (
<div>
<h3>{this.props.house.name}</h3>
<ul>
{this.props.house.photos.map((photo, index) => {
return (
<li><img src={`/images/${photo}`} /></li>
)
})
}
</ul>
</div>
)
}
}
HouseInfo, which display data by query parameter
import React, { Component } from 'react';
const data = require('./db.json');
class HouseInfo extends Component {
constructor(props) {
super(props)
this.state = {
houses: data,
}
}
render() {
const id = this.props.match.params.houseId;
const houseId = id >= 1 ? id - 1 : 0;
return (
<div>
<h3>{this.state.houses[houseId].name}</h3>
<ul>
{this.state.houses[houseId].photos.map((photo, index) => {
return (
<li><img src={`/images/${photo}`} /></li>
)
})
}
</ul>
</div>
)
}
}
export default HouseInfo;
Router
import React from 'react';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom'
import App from './App'
import Intro from './Intro'
import Houses from './Houses'
import House from './House'
import HouseInfo from './HouseInfo'
export default props => (
<Router>
<Route exact path='/' render={() => <App />} >
<Route exact path='/intro' render={() => <Intro />} />
<Route exact path='/houses' render={() => <Houses />} />
<Route exact path='/houses/:houseId' render={(props) => <HouseInfo {...props} />} />
</Route>
</Router>
)
Entire snippet is right except the thing is that you have wrongly matched the params id,
change the following code in house component
this.setState({
houseId: this.props.match.params.houseId
})
you have to use the same param id ie.,houseId inside the component
using the houseId in the state ie.,(this.state.houseId) in House component, loop through the json data and find the houseId and display the corresponding data.
I don't see what props you are passing to the House component but my guess is not exactly intended ones. Try this:
import { withRouter } from 'react-router-dom';
...
export default withRouter(Houses);
or without withRouter:
<Route exact path='/houses/:houseId' render={House} />
and in your Route your param value is specified as houseId, as it should be in House component:
this.setState({
houseId: this.props.match.params.houseId
})

React refresh component on login

I have 2 components, NavBar which contains a login modal and the 'body' of page.
I want to detect when a user logs in and re-render the page based on that. How do I update the login prop in the second component when I log in using the modal of the first one?
A simplified version of the code to keep it short:
// NavBar.js
export default class NavBar extends Component {
constructor(props) {
super(props)
this.initialState = {
username: "",
password: "",
loginModal: false
}
this.handleLogin = this.handleLogin.bind(this)
}
handleLogin(e) {
e.preventDefault()
loginAPI.then(result)
}
render() {
return( <nav> nav bar with links and login button </nav>)
}
// Some random page
export default class Checkout extends Component {
constructor(props) {
super(props);
this.state = {
order_type: 'none',
loggedIn: false
}
this.Auth = new AuthService()
}
componentDidMount() {
if (this.Auth.loggedIn()) {
const { username, email } = this.Auth.getProfile()
this.setState({ loggedIn: true, email: email })
}
try {
const { order_type } = this.props.location.state[0]
if (order_type) {
this.setState({ order_type: order_type })
}
} catch (error) {
console.log('No package selected')
}
}
componentDidUpdate(prevProps, prevState) {
console.log("this.props, prevState)
if (this.props.loggedIn !== prevProps.loggedIn) {
console.log('foo bar')
}
}
render() {
return (
<section id='checkout'>
User is {this.state.loggedIn ? 'Looged in' : 'logged out'}
</section>
)
}
}
// App.js
function App() {
return (
<div>
<NavBar />
<Routes /> // This contains routes.js
<Footer />
</div>
);
}
// routes.js
const Routes = () => (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/register" component={Register} />
<Route exact path="/registersuccess" component={RegisterSuccess} />
<Route exact path="/faq" component={FAQ} />
<Route exact path="/checkout" component={Checkout} />
<Route exact path="/contact" component={Contact} />
{/* <PrivateRoute exact path="/dashboard" component={Dashboard} /> */}
<Route path="/(notfound|[\s\S]*)/" component={NotFound} />
</Switch>
)
I would recommend using the react context API to store information about the logged in user.
See: https://reactjs.org/docs/context.html
Example
auth-context.js
import React from 'react'
const AuthContext = React.createContext(null);
export default AuthContext
index.js
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import AuthContext from './auth-context.js'
const AppWrapper = () => {
const [loggedIn, setLoggedIn] = useState(false)
return (
<AuthContext.Provider value={{ loggedIn, setLoggedIn }}>
<App />
</AuthContext.Provider>
)
}
ReactDOM.render(
<AppWrapper/>,
document.querySelector('#app')
)
Then inside any component you can import the AuthContext and use the Consumer component to check if the user is logged in order set the logged in state.
NavBar.js
import React from 'react'
import AuthContext from './auth-context.js'
const NavBar = () => (
<AuthContext.Consumer>
{({ loggedIn, setLoggedIn }) => (
<>
<h1>{loggedIn ? 'Welcome' : 'Log in'}</h1>
{!loggedIn && (
<button onClick={() => setLoggedIn(true)}>Login</button>
)}
</>
)}
</AuthContext.Consumer>
)
export default NavBar
HOC version
with-auth-props.js
import React from 'react'
import AuthContext from './auth-context'
const withAuthProps = (Component) => {
return (props) => (
<AuthContext.Consumer>
{({ loggedIn, setLoggedIn }) => (
<Component
loggedIn={loggedIn}
setLoggedIn={setLoggedIn}
{...props}
/>
)}
</AuthContext.Consumer>
)
}
export default withAuthProps
TestComponent.js
import React from 'react'
import withAuthProps from './with-auth-props'
const TestComponent = ({ loggedIn, setLoggedIn }) => (
<div>
<h1>{loggedIn ? 'Welcome' : 'Log in'}</h1>
{!loggedIn && (
<button onClick={() => setLoggedIn(true)}>Login</button>
)}
</div>
)
export default withAuthProps(TestComponent)
Alternatively if you have redux setup with react-redux then it will use the context API behind the scenes. So you can use the connect HOC to wrap map the logged in state to any component props.

react router v4 input component losing focus while typing

I'm using react router v4 and using the render prop to load up a settings component that has a dynamic input (a value prop based on the state with an onChange handler). When I load the component without using react router, typing into the input field is dynamic, and changes state as you type. But when I use react router, each character press re-renders the entire settings component, causing the input field to lose focus. Not sure why this is happening, since I'm using the render prop instead of the component prop on the <Route /> component. Any help would be appreciated!
My App Component:
import React, { Component, Fragment } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "../Home/Home";
import Header from "../Header/Header";
import AppSettings from "../AppSettings/AppSettings";
import NotFound from "../NotFound/NotFound";
import { secsToMs, minsToMs, msToTime } from "../../helpers";
import "./App.css";
class App extends Component {
state = {
settings: {
time: {
break: minsToMs(5),
relax: minsToMs(15),
work: minsToMs(25)
},
trackLength: 2,
autoplay: true
},
defaultSettings: {
time: {
break: minsToMs(5),
relax: minsToMs(15),
work: minsToMs(25)
},
trackLength: 4,
autoplay: false
},
time: minsToMs(25),
totalTime: minsToMs(25),
timerPlaying: false,
track: {
tasksCompleted: 0,
breaksCompleted: 0,
timerName: "work"
}
};
updateSettings = (key, updatedSetting) => {
let settings = { ...this.state.settings };
settings.time[key] = updatedSetting;
this.setState({ settings });
};
//...other App methods
render() {
const MainAppContent = ({ location }) => (
<Fragment>
<Header track={this.state.track} location={location} />
<Home
timerPlaying={this.state.timerPlaying}
totalTime={this.state.totalTime}
time={this.state.time}
track={this.state.track}
trackLength={this.state.settings.trackLength}
startTimer={this.startTimer}
pauseTimer={this.pauseTimer}
resetTimer={this.resetTimer}
skipTimer={this.skipTimer}
/>
<AppSettings
settings={this.state.settings}
updateSettings={this.updateSettings}
restoreDefaultSettings={this.restoreDefaultSettings}
/>
</Fragment>
);
const SettingsAppContent = ({ location }) => (
<Fragment>
<Header track={this.state.track} location={location} />
<AppSettings
settings={this.state.settings}
updateSettings={this.updateSettings}
restoreDefaultSettings={this.restoreDefaultSettings}
/>
</Fragment>
);
return (
<main className="App">
<BrowserRouter>
<Switch>
<Route exact path="/" component={MainAppContent} />
<Route
path="/settings"
render={props => <SettingsAppContent {...props} />}
/>
<Route component={NotFound} />
</Switch>
</BrowserRouter>
</main>
);
}
}
export default App;
My AppSettings Component:
import React, { Component, Fragment } from "react";
import RangeSlider from "../RangeSlider/RangeSlider";
import { minsToMs } from "../../helpers";
import "./AppSettings.css";
class Settings extends Component {
render() {
return (
<Fragment>
<h1>Settings</h1>
{Object.keys(this.props.settings.time).map(key => (
<RangeSlider
name={key}
key={key}
time={this.props.settings.time[key]}
updateSettings={this.props.updateSettings}
/>
))}
<button onClick={this.props.restoreDefaultSettings}>
Revert to Default
</button>
</Fragment>
);
}
}
export default Settings;
My Input Component:
import React, { Component } from "react";
import { msToTime, minsToMs } from "../../helpers";
import "./RangeSlider.css";
class RangeSlider extends Component {
onSettingsChange = e => {
let rangeValue = parseInt(e.currentTarget.value);
if (rangeValue > 60) {
rangeValue = 60;
} else if (rangeValue < 1 || rangeValue === NaN) {
rangeValue = 1;
}
let rangeValueMs = minsToMs(rangeValue);
let key = e.currentTarget.name;
let updatedSetting = rangeValueMs;
const updatedSettings = {
...this.props.settings,
[key]: rangeValueMs
};
console.log("updatedSettings", updatedSettings);
this.props.updateSettings(key, updatedSetting);
};
render() {
const { name, time } = this.props;
return (
<div>
<input
type="number"
min="1"
max="60"
value={msToTime(time).m}
className="text-box"
name={name}
onChange={this.onSettingsChange}
/>
</div>
);
}
}
export default RangeSlider;

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

Categories

Resources