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
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'm implementing immutable on my react project, using it with redux, making state an immutable object using fromJS() function (from immutable library). In my reducer file, everything works, I receive an action, I can set the new value using setIn() function, and I can use getIn() function to access state.
But when I get state from connect() function, using mapStateToProps, even if console.log shows an apparently immutable object, I can't use immutable functions, like toJs() or getIn() here. I receive always this error: TypeError: state.getIn is not a function.
My index.js file
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import PropTypes from 'prop-types';
import Button from '#material-ui/core/Button';
import { template as templateAction } from './actions';
import withReducer from '../../../reducer/withReducer';
import templateReducer from './reducer';
export const Template = ({ name, template }) => (
<Button onClick={template}>
{name}
</Button>
);
Template.propTypes = {
name: PropTypes.string.isRequired,
template: PropTypes.func.isRequired,
};
export const mapStateToProps = (state) => {
console.log('state is equal to', state);
return (
{
name: state.getIn(['templateReducer', 'name']),
});
};
export const mapDispatchToProps = (dispatch) => ({
template: () => dispatch(templateAction()),
});
export default compose(
withReducer('templateReducer', templateReducer),
connect(mapStateToProps, mapDispatchToProps),
)(Template);
Result of console.log(state)
Result of console.log(state)
PS: When I don't use immutable state, everything works well.
Looks like state inside mapStateToProps function is an object with one property of 'templateReducer' that has a value of type Map.
I'm a bit rusty with my React knowledge, but maybe sharing the code for templateReducer would be helpful.
What does withReducer('templateReducer', templateReducer) do with the reducer function?
Seems like it is setting the state from the reducer to the key templateReducer before sending it to the mapStateToProps function. (maybe??)
Probably, changing this function to access the State before using immutable methods will remove the error.
export const mapStateToProps = (state) => {
console.log('state is equal to', state);
return (
{
name: state['templateReducer']?.getIn(['name']),
});
};
Use connect from griddle-react not react-redux.
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.
//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);
This is my Selector , I can able to get data with in the selector but don't know how to call this into view (Component) ,
import {todos} from '../reducers/todos';
import { createSelector } from 'reselect'
var visibilityFilter='SHOW_ALL';
var getVisibilityFilter = (state) => visibilityFilter;
var getTodos = (state) => todos;
export const getVisibleTodos = createSelector(
[ getVisibilityFilter, getTodos ],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
)
export default getVisibleTodos;
I have Tried in Component
<button onClick={()=>props.getVisibleTodos(props.SHOW_ALL , props.experimentData.lights)}> SHOW_COMPLETED</button>
Error
Uncaught Error: Actions must be plain objects. Use custom middleware
for async actions.
Blockquote
Help me Out ...
You should call the selector on the connect function like this:
import { connect } from 'react-redux';
import getVisibleTodos from 'your/selector/file';
function YourComponent({ visibleTodos }) {
// You can access visibleTodos inside your component
// because now it's on the props
return (
<div>
//...
</div>
);
}
const mapping = (state, props) => ({
visibleTodos: getVisibleTodos(state, props),
});
connect(mapping)(YourComponent);
Inside the mapping function, you have access to the state and props for the current component. Keep in mind that all selectors must receive the redux store in order to query the data.
Good luck!
I expect that your Redux store state looks something like this:
{
todos: [
{
id: 1,
text: 'Buy milk',
completed: false
},
...
],
visibilityFilter: 'SHOW_ALL'
}
If it is so, then you have to rewrite your getVisibilityFilter and getTodos selectors.
const getVisibilityFilter = (state) => state.visibilityFilter;
const getTodos = (state) => state.todos;
Previously you weren't accessing the values from your state, using this edited functions you are. See how I am using dot notation to access the keys of state, which is nothing more than a JavaScript Object.
Also, when u want to use a selector, you should use it in a container component, where u can access the store's state using mapStateToProps function.
The container could look something like this:
import React from 'react';
import { connect } from 'react-redux';
import { getVisibleTodos } from './selectors.js';
import TodosList from './TodosList.jsx';
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
const VisibleTodosList = connect(
mapStateToProps
)(TodosList);
export default VisibleTodosList;
Where the TodosList component is your own component that displays the todos. It will receive all visible todos using props (this.props.todos).
In my opinion, selectors aren't called from your view (presentational) components, they are meant to be used in containers, where you can access the application's data.
If you want to learn more about containers and presentational components, take a look at this article, it's worth reading.