mapStateToProps & mapActionsToProps not firing in react component - javascript

I have a component (SearchFilter.js) and am using connect to trigger mapStateToProps and mapActionsToProps on export.
Trouble is, mapStateToProps isn't firing -- no props (neither state nor actions) show up in React DevTools and I can't even console log from inside mapStateToProps.
I've tried looking at various Stack Overflow threads but they mostly seem to be typos, or the actions themselves not working.
What's more, I've got an almost identical redux setup for another component (Counter.js) that woks perfectly.
I think it could have something to do with how I provide the store/route to components (see App.js below) as React.Provider shows up in React DevTools for the Counter but not SearchFilter.
Here's the SearchFilter component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { addSearchTerm } from "../redux/actions/searchActions";
import "../styles/SearchFilter.css";
export class SearchFilter extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: "",
showFilters: false,
};
}
//various content...
}
const mapStateToProps = (state) => {
console.log(state);
return {
search: state.search,
};
};
const mapActionsToProps = {
addSearchTerm,
};
export default connect(mapStateToProps, mapActionsToProps)(SearchFilter);
App.js
function App() {
return (
<Router>
<Provider store={store}>
<div className="App">
<NavBar />
<Counter />
<Switch>
<Route exact path="/" component={Home} /> // SearchFilter rendered in Home page
<Route path="/account" component={Account} />
</Switch>
</div>
</Provider>
</Router>
);
}
EDIT: Where I've implemented this component in the Home.js view:
export default function Home() {
return (
<div>
<h4>This is the Home page</h4>
<SearchFilter />
<ProfilesList />
</div>
);
}

Try to remove the "export" when you declare the class component, maybe that helps.
change
export class SearchFilter extends Component
to
class SearchFilter extends Component

Try using bindActionCreators from redux library - https://redux.js.org/api/bindactioncreators
To dispatch any action from your component, update mapActionsToProps const in your SearchFilter component.
const mapActionsToProps = (dispatch) => bindActionCreators({
addSearchTerm},dispatch);
};

Related

Redirecting to another page(route) from a React class component

I need some help to solve the following issue with using React.
In some web app I have a landing page, where I want to redirect the user to the login page in case she or he is not logged in.
I want to use the following landing page (taken from some tutorial I found on the net) in order to use it as a model for mine.
The problem is that this is a function component while my landing page is a class component. According to what I understand I guess I need to consider the code inside useEffect and (somewhat) transfer it to componentDidMount() in my class component. But I don't know how to do that. history.replace will not work in a class component (no Hooks in Classes). Any advice from a more React experienced user will be very welcome.
import React, { useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useHistory } from "react-router";
import "./Dashboard.css";
import { auth, db, logout } from "./firebase";
....
function Dashboard() {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const history = useHistory();
....
useEffect(() => { // Important part for my question !
if (loading) return;
if (!user) return history.replace("/");
....
}, [user, loading]);
return (
<div>
{/*...*/}
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
);
}
export default Dashboard;
Here is what I tried on my Class Component:
class MyCompo extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(MyCompo)--");
const { history } = this.props
history.push("/login");
}
.....
}
But I get the following error:
TypeError: history is undefined
componentDidMount
=============== Added information ===============
Below is the relevant part of the code I have been working on:
This part is what works:
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
What I tried in the Links Component did not work.
The code:
....
ReactDOM.render(
<Router>
<Switch>
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
<Route exact path="/login" component={Login} />
<Route exact path="/section1" component={Section1Page}/>
<Route exact path="/section2" component={Section2Page}/>
<Route exact path="/section3" component={Section3Page}/>
</Switch>
</Router>,
document.getElementById('root')
);
....
const TopMenu = () => {
return (
<div className='page_container'>
<Title/>
<Links path='/'/>
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
)
};
class Links extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(Links)--");
// This is some code I tried with no success.
const { history } = this.props
//history.push("/login");
}
componentDidUpdate(prevProps, prevState) {
console.log("--componentDidUpdate(Links)--");
}
render() {
return (
<div className='links_container'>
{(this.props.path != '/mng') &&
<React.StrictMode>
<Link to='/mng'>{mnMgrStr()}</Link><br/>
</React.StrictMode>}
{(this.props.path != '/other') &&
<React.StrictMode>
<Link to='/other'>{otherInpStr()}</Link><br/>
</React.StrictMode>}
.......
</div>
)
}
}
Following the example on the React Router docs you can use withRouter if your component isn't already receiving the route props, otherwise you can access history from the props.
class MyComponent extends React.Component {
...
componentDidMount() {
const { history } = this.props
// do whatever with history here
}
...
}
In react-router-dom version 5 there are a couple ways a class component can access the history object.
Rendered directly by a Route component via the component, or render or children function props so route props (i.e. history, location, and match) are passed.
component: <Route path="....." component={MyCompo} />
render: <Route path="....." render={routeProps => <MyCompo {...routeProps} />} />
Access the history object from the passed route props:
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
Decorated by the withRouter Higher Order Component so the route props are injected.
import { withRouter } from 'react-router-dom';
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
export default withRouter(MyCompo);
Well I hope by answering this question I can save lot of time of others. Don't need to panic it's not a major issue. I will explain step by step reason and solution.
First of all why this happening is
In react-router-dom **V6 (version 6) latest ** there is no history export or redirect.
There is navigate construct.
So to achieve in functional component there is useNavigate() hook.
Now coming to answer...
To redirect in class component using react-router-dom V6 we have to use component.
So now one has to follow the following steps:
Import navigate
import { Navigate } from "react-router-dom";
Use Navigate to redirect
So above I discussed syntax to do so now coming to your exact problem
You have to redirect user to login if he is not logged in
You can follow these steps:
create state to store status of user like logged in or not (inside constructor of class)
this.state = {
userLogged: false,
};
in your render method you have to add some condition like if user is not logged in take user to login page. see below..
render() {
const { userLogged } = this.state;
if (goToPay) {
return (
<Navigate to="/cart" state={selectedTiffin} props={selectedTiffin} />
);
}
}
That's it.
It can be confusing so I am giving full example so you can save your lot of time..
import React from "react";
import { Navigate } from "react-router-dom";
class Solve extends React.Component {
constructor(props) {
super(props);
this.state = {
userLogged: false,
};
}
// here you can write code to set the status of user like logged in or not
render() {
const { userLogged } = this.state;
if (userLogged ) {
return (
<Navigate to="/cart" />
);
}
return (
<>
Here you can return your original component that should be render when user is log in
</>
);
}
}
I hope this will help and work. Thank You

How to rewrite async HOC component to the React Hooks?

I'm using a template for my React app, and I came across one component that constantly gives me the following warning:
Warning: componentWillMount has been renamed, and is not recommended for use. See https:... for details.
Now, I'm trying to rewrite this component to React Hooks, to avoid multiple warnings in the console.
This is how the component looks like:
import React, { Component } from 'react';
import Nprogress from 'nprogress';
import ReactPlaceholder from 'react-placeholder';
import 'nprogress/nprogress.css';
import 'react-placeholder/lib/reactPlaceholder.css';
import CircularProgress from '../components/CircularProgress/index';
export default function asyncComponent(importComponent) {
class AsyncFunc extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
componentWillMount() {
Nprogress.start();
}
componentWillUnmount() {
this.mounted = false;
}
async componentDidMount() {
this.mounted = true;
const { default: Component } = await importComponent();
Nprogress.done();
if (this.mounted) {
this.setState({
component: <Component {...this.props} />,
});
}
}
render() {
const Component = this.state.component
|| (
<div
className="loader-view"
style={{ height: 'calc(100vh - 200px)' }}
>
<CircularProgress />
</div>
);
return (
<ReactPlaceholder type="text" rows={7} ready={Component !== null}>
{Component}
</ReactPlaceholder>
);
}
}
return AsyncFunc;
}
And here is the example of its usage:
import React from 'react';
import { Route, Switch, withRouter } from 'react-router-dom';
import asyncComponent from '../util/asyncComponent';
const Routes = ({ match }) => (
<Switch>
<Route
path={`${match.url}/main`}
component={asyncComponent(() => import('./routes/MainPage/index'))}
/>
{/* <Route component={asyncComponent(() => import("app/routes/extraPages/routes/404"))}/> */}
</Switch>
);
export default withRouter(Routes);
I know how to rewrite component lifecycle methods (componentDidMount, componentWillUnmount should be rewritten to useEffect), but I don't understand part with props - asyncComponent gets importComponent as a prop, but where are we getting props in AsyncFunc? And how this could be rewritten to the functional component?
As I understood, asyncComponent is a Higher Order Component that responds with an updated component.
Sorry for not providing a sandbox for this example.
I couldn't test this code but I think is a solution
import React, { useState, useEffect } from 'react';
import Nprogress from 'nprogress';
import ReactPlaceholder from 'react-placeholder';
import 'nprogress/nprogress.css';
import 'react-placeholder/lib/reactPlaceholder.css';
import CircularProgress from '../components/CircularProgress/index';
const asyncComponent = importComponent => {
const [component, setComponent] = useState(null);
Nprogress.start();
useEffect(async () => {
const { default: Component } = await importComponent();
Nprogress.done();
setComponent(<Component {...importComponent} />);
}, []);
return component ? (
<ReactPlaceholder type="text" rows={7} ready>
{component}
</ReactPlaceholder>
) : (
<div className="loader-view" style={{ height: 'calc(100vh - 200px)' }}>
<CircularProgress />
</div>
);
};
export default asyncComponent;
I don't see the need to use the state mounted because you only use it in the dismount to setState component, but if 2 lines before you set mounted as true, it is not necessary to generate a re-render, you can go and setState component directly.
I hope this helps you.
According to reactjs.org, componentWillMount will not be supported in the future. https://reactjs.org/docs/react-component.html#unsafe_componentwillmount
There is no need to use componentWillMount.
Before discussing your question but where are we getting props in AsyncFunc?
start with an example
const A = props => {
return <p>{JSON.stringify(props.location)}</p>;
};
function App() {
return (
<Switch>
<Route exact path="/" component={A} />
<Route exact path="/a" component={p => <A {...p} />} />
</Switch>
);
}
Route / and route /a both components are getting props but in different ways
on route / the props are automatically passed to the component
As you know HOC take a component and in response, it returns a component
so asyncComponent(() => import('./routes/MainPage/index') will return AsyncFunc
so we can simply say that
<Route
path={`${match.url}/main`}
component={AsyncFunc}
/>
and that's all
component={AsyncFunc} is equal to component={(p) => <AsyncFunc {...p} />}
and that how pros are passing

Using react api context alongside react router

I have a problem with passing context to route. I get an error when i click a link that goes to my component where context was passed from App component. Below is that component with App (only one import just to show where Context is coming from):
App.js
import { Context } from './Context';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
cryptolist: []
}
}
componentDidMount = () => {
fetch('https://api.coinmarketcap.com/v2/ticker/?structure=array')
.then(response => response.json())
.then(json => this.setState({
cryptolist: json.data
}))
}
render() {
return (
<div>
<Menu />
<Context.Provider value={this.state}>
<Userlist />
</Context.Provider>
</div>
)
}
}
export default App;
Userlist.js ( should be cryptolist or something )
import { Context } from '.././Context'
export default class Userlist extends Component {
render() {
return (
<main>
<Context.Consumer>
{(context) => context.cryptolist.map(el => {
return (
<div>
<h2>{el.name}</h2>
<h5>{el.symbol}</h5>
<h3>{el.quotes.USD.price}</h3>
</div>
)
})}
</Context.Consumer>
</main>
)
}
}
Context.js
import React from 'react';
export const Context = React.createContext();
Everything works just fine here untill i wanted to make a menu that links to this component.
import React from "react";
import { slide as Slider } from 'react-burger-menu';
import { BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";
import Main from './main';
import Userlist from './userlist';
export default class Menu extends React.Component {
render () {
return (
<Router>
<div className="bg-navy w-100 h-100">
<Slider width={ 180 } isOpen={ false }>
<Link className="menu-item" to="/main">Home</Link>
<Link className="menu-item" to="/crypto">About</Link>
</Slider>
<Switch>
<Route path="/main" component={Main} />
<Route path="/crypto" component={Userlist} />
</Switch>
</div>
</Router>
);
}
}
When i click a link to component Userlist i get an error thats cryptolist is not defined. I get it that Userlist can't see a context after clicking link to it. How to pass it correctly?
You are using the routes in the Menu component. Is this really you want? Though, I don't know how this slide thingy works. Maybe this is the way you want to go. I think your problem occurs because your Menu component is not wrapped by the provider. Try like this:
<Context.Provider value={this.state}>
<Menu />
<Userlist />
</Context.Provider
Your Menu component will call Userlist but as it is out the Provider the context doesn’t exist!
Replace Userlist in Context.Provider by Menu and all will be fine.

React - Get data before rendering pages

I have a situation where I need to fetch updated props within componentWillMount()
My Layout :
#connect((store) => {
//console.log(store);
return {
element: store.elements.elements,
connections: store.connections.connections,
attributes: store.attributes.attributes,
media: store.media.media,
places: store.places.places,
user: store.user.user
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
}
componentWillReceiveProps(nextProps) {
this.props.dispatch(updateStoreUser(nextProps.user))
}
shouldComponentUpdate(nextProps) {
console.log(nextProps);
return true;
}
render() {
const { location } = this.props;
return (
<div className="main-container">
<Header/>
<NavConnector/>
{this.props.children}
</div>
);
}
}
{this.props.children} will render pages depending on the route.
I have a BasicInfo Component :
componentWillMount() {
console.log(this.props);
this.props.dispatch(fetchPlaces(1))
}
Where I need to pass user id to fetchPlaces, something like this this.props.dispatch(fetchPlaces(this.props.user.id)
But this.props does not have user.id yet, in the componentWillReceiveProps of the layout I'm updating the store, but gets updated after componentWillMount() of BasicInfo component is called.
The console log :
UPDATE
I have a connector for BasicInfo, this.props.user inside the render method is always undefined. But the store has the user values by now.
Is there any way to pass data from Layout? The place where {this.props.children} is being called? Because that's where the BasicInfoConnector is being called.
import React from "react"
import * as Redux from 'react-redux';
import Basicinfo from "./Basicinfo"
const mapStateToProps = function (store) {
return {
elements: store.elements.elements,
places: store.places.places,
geocode : store.geocode.geocode,
user : store.user.user
};
};
class BasicinfoConnector extends React.Component{
render() {
console.log(this.props.user);
return (
<BasicInfoConnector elements={this.props.elements} places={this.props.places} geocode={this.props.geocode} user={this.props.user}/>
);
}
}
export default Redux.connect(mapStateToProps)(BasicinfoConnector);
Client JS
import React from "react"
import ReactDOM from "react-dom"
import { Router, Route, IndexRoute, hashHistory } from "react-router"
import { Provider } from "react-redux"
import { useScrollToTop } from "scroll-behavior"
import store from "./store"
import '../styles/sass/master/global.scss'
import Layout from "./components/Layout";
import Alerts from "./components/Dashboard/Alerts/Alerts"
import AttributesConnector from "./components/Dashboard/Attributes/AttributesConnector"
import BasicInfoConnector from "./components/Dashboard/Basicinfo/BasicinfoConnector"
import ConnectionsConnector from "./components/Dashboard/Connections/ConnectionsConnector"
import MediaConnector from "./components/Dashboard/Media/MediaConnector"
import Stats from "./components/Dashboard/Stats/Stats"
const app = document.getElementById('app')
ReactDOM.render(
<Provider store={store}>
<Router histroy={hashHistory} onUpdate={() => window.scrollTo(0, 0)}>
<Route path="/" component={Layout}>
<IndexRoute component={BasicInfoConnector}></IndexRoute>
<Route path="location" component={BasicInfoConnector}></Route>
<Route path="alerts" component={Alerts}></Route>
<Route path="attributes" component={AttributesConnector}></Route>
<Route path="connections" component={ConnectionsConnector}></Route>
<Route path="media" component={MediaConnector}></Route>
<Route path="stats" component={Stats}></Route>
</Route>
</Router>
</Provider>,
app);
Assuming that you want to fetch places in componentWillMount, the only solution is to not render the component at all using conditional rendering until the user id is available since componentWillMount gets called only once. Something like this:
{this.props.user?<BasicInfo />:null}
Update:
You need to export a component which is connected (subscribed) to redux store. You are exporting the component which is not connected yet. Just remove the export default before the component declaration
class BasicinfoConnector extends React.Component
and add an export default before the connect statement.
export default Redux.connect(mapStateToProps)(BasicinfoConnector);
This should fix your issue.

Access redux store from redux-simple-router children

I'm trying to figure out how to access the redux store from within route so I can dispatch actions from within the route.
Here's what my top level Component looks like:
class App extends Component {
render() {
return (
<div>
{ children }
</div>
);
}
}
My redux-simple-router code looks like:
render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={ Home } />
<Route path="/example" component={ ExampleRoute } />
</Route>
</Router>
</Provider>,
rootElement
)
If I dump props from within the ExampleRoute component, I don't have access to the store. Any help appreciated!
You should use connect from react-redux to get dispatch and current state from the store. It is outlined in the redux docs here: http://rackt.org/redux/docs/basics/UsageWithReact.html
Here is your Example component:
//...
import { connect } from 'react-redux'
//...
export class Example extends Component {
render () {
const { dispatch, thingName } = this.props
return (
<button onClick={ () => {
dispatch(myAwesomeActionCreator())
}}>{ thingName }</button>
);
}
}
export default connect(state => state)(Example)
Some good examples of how to use connect can be found in the react-redux docs: https://github.com/rackt/react-redux/blob/master/docs/api.md#examples
I was able to get this working with "Monkeypatch" middleware, but there's got to be a better way.
First I created a function to monkeypatch the children variable. This function takes the child, the dispatch and the store as arguments, and returns an updated children variable with keys for the store and dispatch:
function routeStoreMiddleware (children, dispatch, store) {
return {
...children,
props: {
...children.props,
dispatch: dispatch,
store: store
}
}
}
Then I simply updated the component that already has access to the dispatch and store to consume the middleware function:
class App extends Component {
render() {
return (
<div>
{ routeStoreMiddleware(children, dispatch, store) }
</div>
);
}
}
Since the poorly named routeStoreMiddleware function simply returns an updated children object, it still works.
Now I can dispatch events and display data from within the ExampleRoute component.
import React, { Component } from 'react';
import { myAwesomeActionCreator } from '../actions.js'
export class Example extends Component {
render () {
const { dispatch, store } = this.props
return (
<button onClick={ () => {
dispatch(myAwesomeActionCreator())
}}>{ store.thingName }</button>
);
}
}
Yay!
Please note:
I've been reading a lot here about how to make middleware properly in redux, but I haven't had time yet to understand it fully. There's a better way than I've done here.

Categories

Resources