mapStateToProps and mapDispatchToProps is not sending props to component - javascript

Please I am working on a redux project but the data that was returned to mapStateToProps was not send to the component. Console.log props are undefined
LiveEvent.protoTypes = {
event: PropTypes.object.isRequired,
error: PropTypes.string,
};
const mapDispatchToProps = (dispatch) => {
return {
getSingle: (values) => {
console.log(Creators, "creators")
dispatch(Creators.getOneEvent(values));
},
};
};
const mapStateToProps = (state) => (
console.log(state.event.event, "state from liveEvent"),
{
event: state.event.event,
error: state.event.error_message,
}
);
export default connect(mapStateToProps, mapDispatchToProps)(LiveEvent);
function LiveEvent({match}, props) {
console.log(props, "props")
Is there anything am not doing right

function LiveEvent({match}, props) {
The props object comes in the first argument to the function. Nothing is passed as the second argument. So you're destructuring the props to get match, and then creating a useless variable which is named props, but is unrelated to the actual props.
If you'd like to get the entire props object without destructuring, then change your code to:
function LiveEvent(props) {
// Do whatever with props, including things like
// const { match } = props
}
Alternatively, use destructuring to assign the props you care about to local variables:
function LiveEvent({ match, event, error, getSingle }) {
}

Ah, you have comma, I read it as semi-colon after console. It works fine.
There are miscellaneous bugs in your code.
Console doesn't work here:
Implicit return
const mapStateToProps = (state) => ( // <= returns implicitly
console.log(state.event.event, "state from liveEvent"),
{
event: state.event.event,
error: state.event.error_message,
}
);
To debug with the console, you have to use curly brace use return statement:
const mapStateToProps = (state) => {
console.log(state.event.event, "state from liveEvent"),
return {
event: state.event.event,
error: state.event.error_message,
}
};
2. Defining proptypes in your component indicates that the component should pass the props.
Component props
LiveEvent.protoTypes = {
event: PropTypes.object.isRequired,
error: PropTypes.string,
};
This code expects to have props in component itself:
<LiveEvent event={...} error={...} />
The connected component with redux won't get props from it as it's first priority is to check and get props from the component itself.
So, remove proptypes from your component so that the component can receive redux props.

Related

How to access redux-toolkit reducer/action from a class component using react-redux connect()?

I have a redux-toolkit store at store.js.
import { configureStore } from '#reduxjs/toolkit';
import productCurrency from './stateSlices/productCurrency';
const Store = configureStore({
reducer: {
productCurrency: productCurrency,
},
})
export default Store;
The createslice() function itself is in a different file and looks like this below.
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
value: '$',
}
export const productCurrency = createSlice({
name: 'productCurrency',
initialState,
reducers: {
setproductCurrency(state, newState) {
state.value = newState
},
},
})
export const { setproductCurrency } = productCurrency.actions;
export default productCurrency.reducer;
My issue is that I have a class component NavSection that needs to access the initial state and the reducer action setproductCurrency() to change the state. I am trying to use the react-redux connect() function to accomplish that.
const mapStateToProps = (state) => {
const { productCurrency } = state
return { productCurrency: productCurrency.value }
}
const mapDispatchToProps = (dispatch) => {
return {
setproductCurrency: () => dispatch(setproductCurrency()),
dispatch,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NavSection);
Now, I am able to access the state by ussing this.props.productCurrency. Yet, if I try to access the setproductCurrency() by ussing this.props.setproductCurrency()... Chrome console gives me an error that "this.props.setproductCurrency() is not a function".
Is there a way of fixing this, or am I trying to do something impossible?
UPDATE #1
I think I just moved the ball in the right direction. I changed the onClick function to be an arrow function as shown below.
onClick={() => this.props.setproductCurrency('A$')}
Now, setproductCurrency() is considered a function, but it returns a different error when I click the button...
Objects are not valid as a React child (found: object with keys {type, payload}). If you meant to render a collection of children, use an array instead.
Why would this function now return an object? It is supposed to change the state and trigger a re-render of the page so that the class component can access the newly changed state.
To be clear, RTK has nothing to do with React-Redux, connect, or mapDispatch :)
The current error of "Objects are not valid as a React child" is because your reducer is wrong. A reducer's signature is not (state, newState). It's (state, action). So, your line state.value = newStateis reallystate.value = action`, and that's assigning the entire Redux action object as a value into the state. That's definitely not correct conceptually.
Instead, you need state.value = action.payload.

Redux + Typescript + funtional component: imported component requires props that come from Redux

I have a component that looks something like this:
it has an interface with "alerts" property
it is connected to Redux and gets "alerts" from props.
interface IAlert {
alerts: { id: string; type: string; msg: string }[];
}
const Alert: FC<IAlert> = ({ alerts }) => {
return (
//does something with alerts
);
};
Alert.propTypes = {
alerts: PropTypes.array.isRequired
};
const mapStateToProps = (state: any): object => ({
alerts: state.alerts
});
export default connect(mapStateToProps, {})(Alert);
The Problem is:
When I import this component (that creates the Alerts) into another components I get this:
Property 'alerts' is missing in type '{}' but required in type 'Pick<IAlert, "alerts">'.ts(2741)
I don't want to pass "alerts" into the imported elemnt but just get it from Redux.
Thanks for the help!
It's a lot easier to get typescript support by using the useSelector hook like in #Jannis's answer.
You can get proper typing with connect. The reason that you aren't getting it here is because the types of your mapStateToProps function are incorrect.
const mapStateToProps = (state: any): object => ({
alerts: state.alerts
});
connect makes it so that the connected version of the component no longer requires the props which are added by mapStateToProps or mapDispatchToProps. But what props are those?. The type definition for your mapStateToProps doesn't say that it returns a prop alerts. It just returns object.
Returning IAlert makes your error go away because now connect knows that the alerts prop has already been provided.
const mapStateToProps = (state: any): IAlert => ({
alerts: state.alerts
});
If you have a proper type for your state instead of any then you shouldn't need any return type at all. For this particular component, your IAlert props type actually describes both the required state and the return.
const mapStateToProps = (state: IAlert) => ({
alerts: state.alerts
});
But generally you want to get the state type from your store or reducer as described here.
export type RootState = ReturnType<typeof rootReducer>
export type RootState = ReturnType<typeof store.getState>
You'll need to use that RootState in useSelector, but there's a helpful shortcut so that don't need to assign the type every time that you use it. You can create your own typed version of the useSelector hook as explained here.
import { TypedUseSelectorHook, useSelector } from 'react-redux'
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
So now your component doesn't need any props, and the type of the alerts variable is known based on the type of the state
export default () => {
const alerts = useAppSelector(state => state.alerts);
return (
//does something with alerts
);
};

Chain connect/mapStateToProps/mapDispatchToProps functions for code reuse in react-redux

Say I have two redux connected components. The first is a simple todo loading/display container, with the following functions passed to connect(); mapStateToProps reads the todos from the redux state, and mapDispatchToProps is used to request the state to be provided the latest list of todos from the server:
TodoWidgetContainer.js
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
The second redux component is intended to be applied to any component on a page so that component can indicate whether a global "loading" icon is displayed. Since this can be used anywhere, I created a helper function that wraps MapDispatchToProps in a closure and generates an ID for each component, which is used to make sure all components that requested the loader indicate that they don't need it anymore, and the global loader can be hidden.
The functions are basically as follows, with mapStateToProps exposing the loader visibility to the components, and mapDispatchToProps allowing them to request the loader to show or hide.
Loadify.js
function mapStateToProps(state) {
return {
openLoader: loaderSelectors.getLoaderState(state)
};
}
function mapDispatchToProps() {
const uniqId = v4();
return function(dispatch) {
return {
showLoader: () => {
dispatch(loaderActions.showLoader(uniqId));
},
hideLoader: () => {
dispatch(loaderActions.hideLoader(uniqId));
}
};
};
}
export default function Loadify(component) {
return connect(mapStateToProps, mapDispatchToProps())(component);
}
So now, if I have a component that I want to give access to the loader, I can just do something like this:
import Loadify from '...'
class DisplayComponent = new React.Component { ... }
export default Loadify(DisplayComponent);
And it should give it a unique ID, allow it to request the loader to show/hide, and as long as there is one component that is requesting it to show, the loader icon will show. So far, this all appears to be working fine.
My question is, if I would like to apply this to the todos component, so that that component can request/receive its todos while also being allowed to request the loader to show while it is processing, could I just do something like:
TodoWidgetContainer.js
import Loadify from '...'
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
const TodoContainer = connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
export default Loadify(TodoContainer);
And will redux automatically merge the objects together to make them compatible, assuming there are no duplicate keys? Or will it take only the most recent set of mapStateToProps/mapDispatchTo unless I do some sort of manual merging? Or is there a better way to get this kind of re-usability that I'm not seeing? I'd really rather avoid having to create a custom set of containers for every component we need.
connect will automatically merge together the combination of "props passed to the wrapper component", "props from this component's mapState", and "props from this component's mapDispatch". The default implementation of that logic is simply:
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
So, if you stack multiple levels of connect around each other , the wrapped component will receive all of those props as long as they don't have the same name. If any of those props do have the same name, then only one of them would show up, based on this logic.
Alright, here is what I would do. Create a higher order component (HOC) that adds a new spinner reference to your reducer. The HOC will initialize and destroy references to the spinner in redux by tying into the life cycle methods. The HOC will provide two properties to the base component. The first is isLoading which is a function that takes a boolean parameter; true is on, false is off. The second property is spinnerState that is a readonly boolean of the current state of the spinner.
I created this example without the action creators or reducers, let me know if you need an example of them.
loadify.jsx
/*---------- Vendor Imports ----------*/
import React from 'react';
import { connect } from 'react-redux';
import v4 from 'uuid/v4';
/*---------- Action Creators ----------*/
import {
initNewSpinner,
unloadSpinner,
toggleSpinnerState,
} from '#/wherever/your/actions/are'
const loadify = (Component) => {
class Loadify extends React.Component {
constructor(props) {
super(props);
this.uniqueId = v4();
props.initNewSpinner(this.uniqueId);;
this.isLoading = this.isLoading.bind(this);
}
componentWillMount() {
this.props.unloadSpinner(this.uniqueId);
}
// true is loading, false is not loading
isLoading(isOnBoolean) {
this.props.toggleSpinner(this.uniqueId, isOnBoolean);
}
render() {
// spinners is an object with the uuid as it's key
// the value to the key is weather or not the spinner is on.
const { spinners } = this.props;
const spinnerState = spinners[this.uniqueId];
return (
<Component isLoading={this.isLoading} spinnerState={spinnerState} />
);
}
}
const mapStateTopProps = state => ({
spinners: state.ui.spinners,
});
const mapDispatchToProps = dispatch => ({
initNewSpinner: uuid => dispatch(initNewSpinner(uuid)),
unloadSpinner: uuid => dispatch(unloadSpinner(uuid)),
toggleSpinner: (uuid, isOn) => dispatch(toggleSpinnerState(uuid, isOn))
})
return connect(mapStateTopProps, mapDispatchToProps)(Loadify);
};
export default loadify;
Use Case Example
import loadify from '#/location/loadify';
import Spinner from '#/location/SpinnerComponent';
class Todo extends Component {
componentWillMount() {
this.props.isLoading(true);
asyncCall.then(response => {
// process response
this.props.isLoading(false);
})
}
render() {
const { spinnerState } = this.props;
return (
<div>
<h1>Spinner Testing Component</h1>
{ spinnerState && <Spinner /> }
</div>
);
}
}
// Use whatever state you need
const mapStateToProps = state => ({
whatever: state.whatever.youneed,
});
// use whatever dispatch you need
const mapDispatchToProps = dispatch => ({
doAthing: () => dispatch(doAthing()),
});
// Export enhanced Todo Component
export default loadify(connect(mapStateToProps, mapDispatchToProps)(Todo));

How to update state using Redux?

I am using this starter kit https://github.com/davezuko/react-redux-starter-kit and am following some tutorials at the same time, but the style of this codebase is slightly more advanced/different than the tutorials I am watching. I am just a little lost with one thing.
HomeView.js - This is just a view that is used in the router, there are higher level components like Root elsewhere I don't think I need to share that, if I do let me know, but it's all in the github link provided above.
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { searchListing } from '../../redux/modules/search'
export class HomeView extends React.Component {
componentDidMount () {
console.log(this.props)
}
render () {
return (
<main onClick={this.props.searchListing}>
<NavBar search={this.props.search} />
<Hero/>
<FilterBar/>
<Listings/>
<Footer/>
</main>
)
}
}
I am using connect() and passing in mapStateToProps to tell the HomeView component about the state. I am also telling it about my searchListing function that is an action which returns a type and payload.
export const searchListing = (value) => {
console.log(value)
return {
type: SEARCH_LISTINGS,
payload: value
}
}
Obviously when I call the method inside the connect() I am passing in an empty object searchListing: () => searchListing({})
const mapStateToProps = (state) => {
return {
search: { city: state.search }
}
}
export default connect((mapStateToProps), { searchListing: () => searchListing({}) })(HomeView)
This is where I am stuck, I am trying to take the pattern from the repo, which they just pass 1, I think anytime that action is created the logic is just add 1 there is no new information passed from the component.
What I am trying to accomplish is input search into a form and from the component pass the users query into the action payload, then the reducer, then update the new state with the query. I hope that is the right idea.
So if in the example the value of 1 is hardcoded and passed into the connect() method, how can I make it so that I am updating value from the component dynamically? Is this even the right thinking?
You almost got it right. Just modify the connect function to pass the action you want to call directly:
const mapStateToProps = (state) => ({
search: { city: state.search }
});
export default connect((mapStateToProps), {
searchListing
})(HomeView);
Then you may use this action with this.props.searchListing(stringToSearch) where stringToSearch is a variable containing the input value.
Notice : You don't seem to currently retrieve the user query. You may need to retrieve it first and then pass it to the searchListing action.
If you need to call a function method, use dispatch.
import { searchListing } from '../../redux/modules/search';
const mapDispatchToProps = (dispatch) => ({
searchListing: () => {
dispatch(searchListing());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeView);
Then, you have made the function a prop, use it with searchListing.

Error in redux when executing an action: Uncaught type error: cannot read property 'type' of undefined

Just new to React. I guess It's a basic question but I can't get why the reducer doesn't get fired or update the state:
My HomeView.js:
....
const { localeChange, counter, locale } = this.props
return (
<div>
<button onClick={() => increment(7)}>Increment</button>
<input type='text' value = {counter}/>
.....
</div>
)
const mapStateToProps = (state) => ({
locale: state.locale,
counter: state.counter
})
export default connect(mapStateToProps, {localeChange, increment})(HomeView)
My reducer (constant, action and reducer in the same file):
export const COUNTER_INCREMENT = 'COUNTER_INCREMENT'
export function increment (text) {
return { type: COUNTER_INCREMENT, text }
}
export default function counterIncrement (state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + action.text
default:
return state
}
}
And finally my "parent" reducer:
import { combineReducers } from 'redux'
import { routeReducer as router } from 'react-router-redux'
import locale from './modules/locale'
import counter from './modules/counter'
export default combineReducers({
locale,
router,
counter
})
What I understand:
In my state I have a field named counter (it's there using redux dev tools).
When I click the button I dispatch increment action, so I really should dispatch an action like this:
{type: COUNTER_INCREMENT, text: 7}
However counterIncrement function (reducer) gets action as undefined: Uncaught type error: cannot read property 'type' of undefined.
Any way I could debug this properly? I put a breakpoint in the reducer counterIncrement but doesn't get fired.
Redux validate that every action you trigger actually contains a type property.
My guess is that you're calling dispatch() with undefined. Your example code doesn't contains any dispatch calls, so I won't be able to help further - but my guess is that it'll be trivial to figure out. Either you call dispatch() with the wrong arguments, or your action creator doesn't return an object with a type property.
Side note (unrelated to your question), but your action creator type label doesn't match the one inside your reducer.

Categories

Resources