When I'm pressing button '<' or '>' (increase or decrease difficulty level) I get error "Uncaught TypeError: Cannot set property 'props' of undefined" at PureComponent (react.development.js:444)
How can I fix it?
The code above is much less then actual code, however even in this size it not works well.
Even 'blah-blah' do not appears in console.
StartMenu.js
import React from 'react';
import { connect } from 'react-redux';
import increaseDifficultyLevelfunction from './increaseDifficulyLevel';
import decreaseDifficultyLevelfunction from './decreaseDifficulyLevel';
function StartMenu(props) {
return (
<div className="start-menu-container">
<button
type="button"
id="leveldown"
onClick={decreaseDifficultyLevelfunction}
>
<
</button>
<div id="level">{props.difficultyLevel}</div>
<button
type="button"
id="levelup"
onClick={increaseDifficultyLevelfunction}
>
>
</button>
<button
type="button"
id="startButton"
onClick={props.restartGame}
>
start the game
</button>
</div>
);
}
const mapStateToProps = state => ({
difficultyLevel: state.difficultyLevel,
});
const mapDispatchToProps = dispatch => ({
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(StartMenu);
decreaseDifficultyLevel.js
import { connect } from 'react-redux';
import { decreaseDifficultyLevel } from '../../actions/actionCeator';
function decreaseDifficultyLevelfunction(props) {
console.log('blah-blah');
props.decreaseDifficultyLevel();
}
const mapStateToProps = state => ({
difficultyLevel: state.difficultyLevel,
});
const mapDispatchToProps = dispatch => ({
decreaseDifficultyLevel: () => { dispatch(decreaseDifficultyLevel()); },
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(decreaseDifficultyLevelfunction);
I was digging into the details here and decided to try it in a codesandbox.
The common React component (till hooks) cases, is made of a class component. In these cases, functions/actions are bind at the top level and retain their context in sub components (so we can use them). In function components, things are bit different. In order to get the actions to work you need to do the following:
link your actions to redux, using mapDispatchToProps as usual.
Import * as actions from ’./actions’;
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators(levelActions, dispatch)
};
};
bind your actions so they can retain their context.
In order to make step 2, you need to declare the functions within the functional component (usually known as class variables) and then use them in the buttons onClick attribute. Like so:
const {
increaseDifficultyLevelFunction,
decreaseDifficultyLevelFunction
} = props.actions;
const increaseLevel = () => increaseDifficultyLevelFunction();
const decreaseLevel = () => decreaseDifficultyLevelFunction();
Check this reference, it covered your case with examples.
Related
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getTodoList } from "./redux/action/todoList";
Using functional components I have written want the same how we can write in class components
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTodoList());
}, []);
const todoList = useSelector((state) => state.todoList);
console.log(todoList);
return (
<div className="App">
{todoList.data.length > 0
? todoList.data.map((obj) => {
return (
<>
<div className="card">
<div className="content">
<div className="header">{obj.title}</div>
</div>
</div>
</>
);
})
: ""}
</div>
);
}
export default App;
To connect the redux store to your component, you can use connect API. As per the documentation
The connect function takes two arguments, both optional:
mapStateToProps: called every time the store state changes. It receives the entire store state and should return an object of data this component needs.
mapDispatchToProps: this parameter can either be a function or an object.
If it’s a function, it will be called once on component creation. It will receive dispatch as an argument and should return an object full of tasks that use dispatch to dispatch actions.
If it’s an object full of action creators, each action creator will be turned into a prop function that automatically dispatches its action when called. Note: We recommend using this “object shorthand” form.
Your code will be changed like this.
const mapStateToProps = (state, ownProps) => ({
todoList: state.todoList
});
const mapDispatchToProps = (dispatch) => ({
todoList: dispatch(getTodoList())
});
// `connect` returns a new function that accepts the component to wrap:
const connectToStore = connect(mapStateToProps, mapDispatchToProps);
// and that function returns the connected, wrapper component:
const ConnectedApp = connectToStore(App);
// We normally do both in one step, like this:
connect(mapStateToProps, mapDispatchToProps)(App);
I've started using the redux-toolkit slicers in functional components, (example from react-redux example)
slicer:
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
use in component:
const count = useSelector(selectCount);
const dispatch = useDispatch();
return (
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
)
my question is how can I use this slicer in the class component since I cant use hooks inside them.
I've tried using connect (from redux) but I can't find a way to "stitch" the actions and selectors from the slicer to my component.
I couldn't find any documentation on this as well.
Class vs. function components and redux-toolkit vs "vanilla" redux are two independent decisions that don't have any impact on each other. (Though you should be aware that function components and hooks are recommended over class components for everything React).
I've tried using connect (from redux) but I can't find a way to "stitch" the actions and selectors from the slicer to my component.
How do the docs "stitch" the actions and selectors when using useDispatch and useSelector? Do that, but with the connect higher-order component instead.
The increment() function in the docs example that you posted doesn't just magically exist, it needs to be imported from the slice. You can export the entire actions object and use actions.increment but you usually see the actions exported as individual variables.
From the docs:
Most of the time, you'll probably want to use ES6 destructuring syntax to pull out the action creator functions as variables, and possibly the reducer as well:
Your slice file might look like this:
const counterSlice = createSlice( /* same as before */ );
// destructure actions and reducer from the slice (or you can access as counterSlice.actions)
const { actions, reducer } = counterSlice;
// export individual action creator functions
export const { increment, decrement, incrementByAmount } = actions;
// often the reducer is a default export, but that doesn't matter
export default reducer;
The first argument of connect is mapStateToProps, where you use selectors (either inline arrow functions state => state.something or selector functions that you import) to create an object of props from the state. That might look like:
const mapStateToProps = (state) => ({
count: state.counter.value
});
The second argument mapDispatchToProps is optional. If you pass an object with your action creators, your component will receive versions of those action creators that are already bound to dispatch. You would be able to call this.props.increment() directly rather than this.props.dispatch(increment()). You will see this syntax commonly used in tutorials with connect.
import React from "react";
import { connect } from "react-redux";
import { increment, decrement } from "./counterSlice";
class MyComponent extends React.Component {
render() {
return (
<div>
<h1>Count is {this.props.count}</h1>
<button onClick={() => this.props.increment()}>
Increment
</button>
<button onClick={() => this.props.decrement()}>
Decrement
</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
count: state.counter.value
});
const mapDispatchToProps = { increment, decrement };
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
If you leave off the mapDispatchToProps argument entirely, your component receives the raw dispatch function. You would call the dispatch on you imported action creators like this.props.dispatch(increment()). This syntax is more similar to how useDispatch is used. Both connect and useDispatch give you access to the dispatch function and you can call that function with an action that you create from an action creator function like increment() or decrement().
import React from "react";
import { connect } from "react-redux";
import { increment, decrement } from "./counterSlice";
class MyComponent extends React.Component {
render() {
return (
<div>
<h1>Count is {this.props.count}</h1>
<button onClick={() => this.props.dispatch(increment())}>
Increment
</button>
<button onClick={() => this.props.dispatch(decrement())}>
Decrement
</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
count: state.counter.value
});
export default connect(mapStateToProps)(MyComponent);
Complete CodeSandbox
I want to connect / bind actions in the same file in which I am defining my component, and ideally would like this component to be functional. The problem is doing this means I must alias my action so as to avoid the eslint rule no-shadow. You can see this in the code block below. However, I also use an IDE and aliasing these actions renders them invisible to my IDE when trying to find all usages of said actions.
Is there a way I can connect these dispatched actions to my functional component while making said actions visible to my IDE for debugging?
import React from 'react';
import {connect} from 'react-redux';
import {actionOne, actionTwo} from '../../../../actions';
const ComponentOne = ({actionOneDispatch, actionTwoDispatch}) => {
const handleClick = () => {
actionOneDispatch();
actionTwoDispatch()
};
return (
<button onClick={handleClick}>Click Me</button>
);
};
const mapDispatchToProps = (dispatch) => ({
actionOneDispatch: () => {
dispatch(actionOne());
},
actionTwoDispatch: () => {
dispatch(actionTwo());
},
});
export default connect(null, mapDispatchToProps)(ComponentOne);
I have a problem with dispatching a action from componentDidMount...
error is : TypeError: this.props.postDetails is not a function
Action.js
export const postDetails = data => ({
type: "POST_DETAILS",
post: data
})
Container/GetDetails.js
import Details from '../components/Details'
import { postDetails } from '../actions'
const mapStateToProps = state => ({ post: state.post });
const mapDispatchToProps = dispatch => bindActionCreators({postDetails}, dispatch);
const GetDetails = connect(
mapStateToProps,
mapDispatchToProps
)(Details)
export default GetDetails
Component/Details.js
import React from 'react'
import { postDetails } from '../actions'
class Details extends React.Component {
constructor(props){
super(props);
}
componentDidMount() {
console.log("did mount details");
this.props.postDetails();
}
render() {
return (
<div>
Details page
</div>
)
}
}
export default Details;
Can someone help me? Why i have this error?
In App.js (or wherever you are importing the Details component), are you using the path to your GetDetails container (not component)?
I moved state from a component to a container and forgot to update the import path which gave me this same error. Updating the import path to the container took care of it.
Edit:
For example, I have an apiLanding folder that has apiLanding.js (the component) and apiLanding-container.js (the container).
In my app.js, I needed to change
import apiLanding from './components/apiLanding/apiLanding';
to
import apiLanding from './components/apiLanding/apiLanding-container';
That way, the app now has access to the redux state and actions. This was a silly mistake and may not be your issue, but wanted to share just in case the import path was overlooked.
You have to return an object, where the keys are your props. See docs.
const mapDispatchToProps = dispatch => ({ postDetails: bindActionCreators({postDetails}, dispatch) })
Or, you can use the shorthand notation:
const GetDetails = connect(
mapStateToProps,
{ postDetails }
)(Details)
I don't see bindActionCreator imported. Use eslint to get rid of these errors
There are two things which don't really seem right to me. Personally I never used bindActionCreators. I would just create my mapDispatchToProps as following:
const mapDispatchToProps = dispatch => {
return {
postDetails: () => dispatch(actions.postDetails)
};
};
But also your postDetails call expects an argument, which you should add in your function call. So your mapDispatchToProps would look like this:
const mapDispatchToProps = dispatch => {
return {
postDetails: (data) => dispatch(actions.postDetails(data))
};
};
Also you're importing your action as postDetails. Are you sure that this is just one action? And not a combination of all actions in your file? Note how I added your function as actions.postDetails instead of just postDetails.
//action code
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
export const clearCompleted = () => {
return{
type: CLEAR_COMPLETED
}
}
//reducer code
case CLEAR_COMPLETED:
return state.map(todo => {if (todo.completed)
{return {...todo, show:false}}
else {return todo}})
Problem dispatching action on Todo application in react-redux.
import React from 'react'
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
const ClearButton = ({dispatch}) => {
return(
<button fluid onClick={e => {dispatch(clearCompleted())}}>
Clear Completed
</button>
)
}
export default ClearButton
Trying to change the store by clicking on Clear Completed Button. Clear Completed Button should remove the completed todos from the store and todo list should be updated. I am trying to call 'clearCompleted' action with Clear Completed Button.
The difficulty you're having here is that your component doesn't know anything about the Redux store, and the dispatch function will not be in its props. The most basic way you can make dispatch available would be this:
export default connect()(ClearButton)
This will allow you to use dispatch(clearCompleted()) without messing around further with mapDispatchToProps. You'd have to change its definition so it's not a stateless component though.
However, you should probably ask yourself whether a tiny button really needs connect at all? You could probably just pass the correct function down from the containing component:
// TodoList.js
class TodoList extends Component {
render () {
return (
...
<ClearButton clearCompleted={this.props.clearCompleted} />
)
}
}
const mapStateToProps = state => ({
// ...
})
const mapDispatchToProps = dispatch => ({
clearCompleted: () => dispatch(clearCompleted())
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
Then the function will be in ClearButton's props without it needing to be connected:
<button onClick={this.props.clearCompleted}>
You can do it by wrapping your component in connect.
connect accepts two arguments as first call, mapStateToProps for mapping your store properties into your component's props and mapDispatchToProps for mapping action creators into your component's props. It's also followed by another call to that function with the Component name of yours written in class syntax.
If you insist in using stateless components with connect, you can use compose utility from redux.
import React from 'react'
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
class ClearButton extends React.Component {
render() {
const {clearCompleted} = this.props;
return(
<button fluid onClick={clearCompleted}>
Clear Completed
</button>
)
}
}
const mapDispatchToProps = dispatch => bindActionCreators({ clearCompleted }, dispatch);
export default connect(null, mapDispatchToProps)(ClearButton);