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);
Related
I'm attempting to create a React/Redux component that shows/hides an element when clicked.
I'm using this to trigger the function from another component:
import React from 'react'
//Some other code...
import { useSelector } from 'react-redux'
import onShowHelpClicked from '../help/AddHelpSelector'
<button onClick={onShowHelpClicked}>Help</button>
This this is AddHelpSelector:
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { helpVisible } from './HelpSlice'
export const AddHelp = () => {
const [isVisible, showHelp] = useState('')
const dispatch = useDispatch()
const onShowHelpClicked = () => {
dispatch(
helpVisible({
isVisible,
})
)
if (isVisible) {
showHelp(false)
} else {
showHelp(true)
}
}
return (
<section>
<h2 style={{ visibility: { isVisible } }}>Help section</h2>
</section>
)
}
export default AddHelp
Finally, this is HelpSlice
import { createSlice } from '#reduxjs/toolkit'
const initialState = [{ isVisible: false }]
const helpSlice = createSlice({
name: 'help',
initialState,
reducers: {
helpVisible(state, action) {
state.push(action.payload)
},
},
})
export const { helpVisible } = helpSlice.actions
export default helpSlice.reducer
I'm fairly certain I'm doing multiple things wrong, as this is my first attempt to do anything with Redux and I'm still struggling to wrap my mind around it after a week of learning.
But specifically, when clicking the help button I get this error.
"Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app"
The linked documentation provides a way to test a component to see if React is importing properly, and it's not. But I'm not really sure what I'm doing wrong.
I think it may be that I'm importing React multiple times, but if I don't then I can't use "useState."
What's the correct way to do this? I'm open to corrections on both my code as well as naming conventions. I'm using boilerplate code as a template as I try to understand this better after getting through the documentation as well as Mosh's 6 hour course which I just finished.
You're importing the < AddHelpSelector /> component here import onShowHelpClicked from '../help/AddHelpSelector', and then you try to use it as a callback handler for the button's onClick, which doesn't really make sense. I assume you actually wanted to only import the onShowHelpClicked function declared inside the < AddHelpSelector /> component (which is not really a valid way of doing it). Since you want to control the visibility using redux state, you could just grab the flag from the redux store inside the < AddHelpSelector /> component using useSelector hook. To set it, you're gonna do that in the component where your button is. For that, you just need to dispatch an action(like you already did), with the updated flag. No need for the local useState. Also, using the flag you could just conditionally render the element.
const App = () => {
const dispatch = useDispatch();
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
const handleClick = () => {
dispatch(
helpVisible({
!isVisible,
})
)
}
return (<button onClick={handleClick}>Help</button>);
}
export const AddHelp = () => {
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
return (
<section>
{isVisible && <h2>Help section</h2>}
</section>
)
}
export default AddHelp
I am encountering a function not found using useDispatch. Supposedly, I should be using react-redux, unfortunately, my version is 6.0.0 and what's needed is 7.0.0 and above. I'd like to explore useDispatch or related hook to dispatch an action function inside a function component. How do I do this given that I cannot upgrade my react-redux & still use function component?
My alternative is to use class component, but again, I'd like to see this work in function component. Let me know what other details needed.
Here's my code.
import React, { useDispatch } from 'react';
import {
Tooltip,
IconButton,
Icon,
} from '#material-ui/core';
import { logout } from './auth/store/actions';
const QuickLogout = () => {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(logout());
};
return (
<Tooltip title="Logout" placement="bottom">
<IconButton className="w-64 h-64" onClick={handleClick}>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
);
};
export default QuickLogout;
And here's the error when compiling.
TypeError: Object(...) is not a function
QuickLogout
webpack-internal:///.....onents/QuickLogout.js:18:75
Edit: I mentioned exploring useDispatch or related hooks given that I cannot upgrade. I felt that this is a gray statement as I really just wanted my code to work. That means, solution is not limited to useDispatch or other hooks. Hence, I chose the answer that did not use useDispatch or hooks because it was plain simple. Apologies for the vague statement. I'll take note on improving my writing skills.
React itself doesn't have useDispatch in it Hooks API, but it has useReducer to eliminate redux for a small project that redux is an overengineering process for them.
On the other hand, new versions of react-redux provide new handy hooks which you can make a similar version of those by yourself.
Here is a Tip
Hooks (React hooks) can only be used in functional component
HOC (Higher-order component) can be used anywhere
The connect function which react-redux provides is HOC so it can also be used with both class-base and functional components. It is also a good practice to separate your presentation UI and logic into representationals and containers, hence there will be no problem for your component to be functional and use all fancy things that could be used in traditional class-component and connect by react-redux.
you can also make your custom hook for handling such things in a re-usable manner with less code.
// setup.js
const store = createStore(/* put your reducer and enhancers here */)
export const { dispatch, getState } = store
// hooks.js
import { dispatch, getState } from '~setup.js'
export const useDispatch = () => dispatch
export const useSelector = selector => selector(getState())
// The above useSelector wont cause your component to re-render on data change
Then in your component
import { useDispatch } from '~hooks.js'
import { logout } from './auth/store/actions'
export default function QuickLogout() {
const dispatch = useDispatch()
const handleClick = () => {
dispatch(logout())
}
return (
<Tooltip>
<IconButton onClick={handleClick}>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
)
}
If you want to know more about custom hooks see making my own hook.
If you can't upgrade your react-redux version to one with the useDispatch and useSelector React hooks you can still use the connect Higher Order Component to inject dispatch, or to even more simply wrap your action creators with a call to dispatch.
import { connect } from 'react-redux';
const QuickLogout = ({ logout }) => { // <-- destructure logout prop
return (
<Tooltip title="Logout" placement="bottom">
<IconButton
className="w-64 h-64"
onClick={logout} // <-- assign to click handler
>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
);
};
const mapDispatchToProps = {
logout, // <-- inject logout action as prop already wrapped by dispatch!
};
const ConnectedQuickLogout = connect(
null, // <-- this is typically mapStateToProps, but we don't need it
mapDispatchToProps,
)(QuickLogout);
export default ConnectedQuickLogout;
If you prefer to keep the code closer to as-is then you can simply connect it to the redux store and a dispatch prop is still injected.
import { connect } from 'react-redux';
const QuickLogout = ({ dispatch }) => {
const handleClick = () => {
dispatch(logout());
};
return (
<Tooltip title="Logout" placement="bottom">
<IconButton className="w-64 h-64" onClick={handleClick}>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
);
};
export default connect()(QuickLogout);
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.
I am building an app using redux and react-native.
I am curious about a pattern which I use. I have encountered no downsides however I haven't seen it in any tutorials which makes me wonder why nobody does it.
Instead of passing action creators as props in the connect function like
connect(mapStateToProps,{ func1, func2 })(Component);
I imported the app store inside of the module where I declare the functions in the first place:
import { AppStore } from '../App';
const actionCreator = () => {
doSomethng();
appStore.dispatch({ type: 'Action' });
};
This to me makes it easier to do async actions because I need no middleware:
import { AppStore } from '../App';
const actionCreator = async () => {
await doSomethng();
appStore.dispatch({ type: 'Action' });
};
I did this because of the js-lint error 'no-shadow'. It made me realise that in order to use it I had to import the action creators in the component file, and then pass it as a prop to the connect function in order for the action creator to have access to dispatch.
import { actionCreator1, actionCreator2 } from './actionCreators';
const myComponent = (props) => {
const { actionCreator1, actionCreator2 } = props; //shadowed names
return (
<Button onPress={actionCreator1} />
);
};
export default connect({}, { actionCreator1, actionCreator2 })(myComponent)
In my version I just import it once but do not pass it to connect. This eliminates the need to shadow names.
import { actionCreator1, actionCreator2 } from './actionCreators';
const myComponent = (props) => {
return (
<Button onPress={actionCreator1} />
);
};
export default connect({})(myComponent)
I like that you try to find your own solutions to your specific problems. It's the sign of an engineer, just in this case this isn't the solution.
I think the idea of how Redux teaches you to do things is not intended to be canonized. You have the ability to put a dispatcher on your props because it allows things to be transparent, meaning that things are bound outside of your class and injected in. You have hidden your store dependency by directly referencing it in some other files. It's no longer as obvious how your application works with regards to the workflow. Another react developer would be confused, I suppose that's the major downside.
If you're ok with those aspects what you're doing is fine. Fine as in, it gets the job done, but not "fine" in that it embraces concepts like Single Responsibility Principle
//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);