Parent component
const myParentComponent = ({ sourceValue, classes, doStuff }) => {
return (
<div>
<MyComponent className={classes.iconWithText} value={sourceValue} onDoStuff={doStuff} />
</div>
);
}
const mapStateToProps = () => {
...
}
function mapDispatchToProps(dispatch) {
return {
doStuff: () => dispatch(doStuffAction())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(myParentComponent));
uses
Child component
export default class MyComponent extends Component {
handleClick = () => {
console.info(this.props.value);
this.props.onDoStuff(this.props.value);
}
render() {
return (
<SomeIcon className={this.props.className} onClick={this.handleClick} />
);
}
}
Ultimately, the action being called itself does nothing more than log:
export function doStuff(value) {
console.info('At doStuff action', value);
return { type: DO_STUFF, value };
}
Technically, everything seems to work, there's no errors. But, that last console.info in the action logs value as undefined. I'm pretty sure it's because I map the onClick action like this:
function mapDispatchToProps(dispatch) {
return {
doStuff: () => dispatch(doStuffAction())
};
}
If I provide a paramater like so:
function mapDispatchToProps(dispatch) {
return {
doStuff: () => dispatch(doStuffAction('123456'))
};
}
Then the doStuff action logs that 123456 value.
It does seem that the sourceValue being passed to onDoStuff from the main component does not have any effect.
What do I need to do to make sure the original sourceValue is passed?
All you need to do is accept a param in the arrow function and pass it on to the action like
function mapDispatchToProps(dispatch) {
return {
doStuff: (value) => dispatch(doStuffAction(value))
};
}
In case you have to pass multiple params, you can write it like
function mapDispatchToProps(dispatch) {
return {
doStuff: (value, key) => dispatch(doStuffAction(value, key))
};
}
If you have multiple parameters that varies upon usage, say from one component you want to pass value to doStuffAction and in some component you want to pass value and key then its preferred to make the value as an object like
function mapDispatchToProps(dispatch) {
return {
doStuff: (obj) => dispatch(doStuffAction(obj))
};
}
and action:
export function doStuff({value, key}) {
console.info('At doStuff action', value, key);
return { type: DO_STUFF, value };
}
Related
I wonder how this code written in JavaScript
const stuff = useCallback(() => {
function first() {
return "firstStaff";
}
function major() {
return "majorStaff";
}
major.first = first;
return major;
})();
Can be written with correct types in TypeScript, so it has the right hints for stuff() and stuff.first()
We can define a type by combining multiple types like this to achieve required
type Stuff = { first: () => string } & (() => string);
const stuff: Stuff = useCallback(() => {
...
})();
stuff.first();
stuff();
If you are interested in function static property typing you can use this example:
import { useCallback } from 'react'
interface Major {
(): string
first: () => string
}
const stuff = useCallback((): Major => {
function first() {
return "firstStaff";
}
function major() {
return "majorStaff";
}
major.first = first;
return major;
}, [])();
Playground
Please see this question/answer if you want to know more about typing static function properties
useCallback need an array of dependency as the 2nd argument then it can be self infer
import { useCallback } from "react";
export default function App() {
const stuff = useCallback(() => {
function first() {
return "firstStaff";
}
function major() {
return "majorStaff";
}
major.first = first;
return major;
}, []);
return (
...
);
}
Working Example:
I have a component which is the "base" for another component. I want to add some more functionality to the newly created component
<SomeComponent
onSelect = { this.props.handleSelect }
onDeselect = { this.props.handleDeselect }
selectionList = { valuesList }
value = { values }
origin = "XYZ" />
onSelect triggeres the action this.props.handleSelect
export function handleSelect(value) {
return dispatch => {
dispatch(actionCreator(HANDLE_SELECT, value));
}
}
That actions goes into the reducer
case HANDLE_SELECT: {
const newValues = value_select(state, action);
return {
...state,
find: {
...state.a,
values: newValues
}
}
}
Finally, value_select is called to do all the magic
export const value_select = function(state, action) {
...
const newData = {
XYZ: action.payload
}
return newData
}
How would I mage the "a" from the props from my component acessible in value_select(). I need it where the XYZ is...
Please note that I cannot write anything into the onSelect, hence the onClick event. I am using a predesigned component which I dont want to alter. Only the components that are based on the original one.
You need to add another handler inside SomeComponent and add new argument with prop you want to pass to original handleSelect. If SomeComponent is from vendor and you can't change it's code, than you will have to wrap it
class BetterComponent extends React.Component{
handleSelect = (value) {
this.props.handleSelect(value, this.props.origin)
}
render() {
return (
<SomeComponent
...this.props
onSelect = { this.handleSelect }
/>
)
}
Add new param to your handle select
export function handleSelect(value, origin) {
return dispatch => {
dispatch(actionCreator(HANDLE_SELECT, {
value: value,
origin: origin
}));
}
}
Then origin will be accessible by action.payload.origin inside value_select
Of course now you have to call BetterComponent instead of SomeComponent
<BetterComponent
onSelect = { this.props.handleSelect }
onDeselect = { this.props.handleDeselect }
selectionList = { valuesList }
value = { values }
origin = "XYZ" />
I have a Connector, which has mapDispatchToProps and mapStateToProps functions, and I need to dispatch a action from my main component.
I'm getting an error saying dispatch is not defined when I'm trying to dispatch fetchPlaces(this.props.user.id)
this.props.user.id has value 1.
I need to get the user id and pass it to fetchPlaces, which intern gets me the places of the user. I'm not sure how to do it.
Connector
const mapStateToProps = function (store) {
return {
elements: store.elements.elements,
places: store.places.places,
geocode : store.geocode.geocode,
user : store.user.user
};
};
const mapDispatchToProps = function (dispatch) {
return {
userAction : dispatch(fetchUser()),
elementsAction : dispatch(fetchCategories()),
placesAction: (id) => { dispatch(fetchPlaces(id)) }
}
}
class BasicinfoConnector extends React.Component{
render() {
console.log(this.props.user);
if(typeof this.props.user.id != "undefined"){
return (
<Basicinfo elements={this.props.elements} places={this.props.places} geocode={this.props.geocode} user={this.props.user}/>
);
}
else{
return null;
}
}
}
export default Redux.connect(mapStateToProps, mapDispatchToProps)(BasicinfoConnector);
Component :
componentWillMount() {
console.log(this.props);
console.log(this.props.user.id);
dispatch(fetchPlaces(this.props.user.id))
}
Is placesAction: (id) => { dispatch(fetchPlaces(id)) } the right syntax of doing it?
UPDATE
I changed componentWillMount :
componentWillMount() {
console.log(this.props);
console.log(this.props.user.id);
dispatch(this.props.placesAction(this.props.user.id))
}
and mapDispatchToProps :
const mapDispatchToProps = function (dispatch) {
return {
userAction: bindActionCreators(fetchUser, dispatch),
elementsAction: bindActionCreators(fetchUser, dispatch),
placesAction: (id) => { dispatch(fetchPlaces(id)) }
}
}
Still have the same error.
You need to pass the property down to the next level, either by sharing all your props like this:
<Basicinfo {...this.props} />
or only the particular ones that you want
<Basicinfo placesAction={(id) => this.props.placesAction(id)} />
I have a ReactJs app which uses Redux to manage its store. My state is a complex json whose fileds can change. When i initiate the reducers, i have to specify the initial state else when accessing it before fetching the content from server, i get 'undefined' error. Following is the example,
//Test.jsx
class Test extends React.Component{
componentWillMount(){
fetchContent({
type: SET_CONTENT
});
}
render(){
return(
<div> {this.props.header} </div>
)
}
mapStateToProps(state){
return{
header: state.reducer1.a.b.c
}
}
}
export default (mapStateToProps)(Test);
//reducer1.js
export default function reducer1(state = {}, action = {}) {
switch (action.type) {
case SET_CONTENT:
return Object.assign({}, action.data);
default:
return state;
}
}
//reducer2.js
export default function reducer2(state = {}, action = {}) {
switch (action.type) {
case SET_SOMETHING_ELSE:
return Object.assign({}, action.data);
default:
return state;
}
}
//CombinedReducer.js
export default combinedReducers(Object.assign({}, {reducer1}, {reducer2}))
Now when the component initialises for the first time, state.reducer1.a.b.c throws undefined because the fetchContent() doesnt seems to be called at this point.
So my question is how do i solve this issue? Is specifying the initial state in reducer the only option? like the following,
//reducer1.js
export default function reducer1(state = { a: { b: { c: "somedata"}}}, action = {}) {
switch (action.type) {
case SET_CONTENT:
return Object.assign({}, action.data);
default:
return state;
}
}
You can do that, but you can also just update your mapState function to check for the existence of the first key.
function mapStateToProps(state){
return{
header: state.reducer1.a ? state.reducer1.a.b.c : <div>Loading..</div>
}
}
Also I assume you mean to have mapStateToProps outside of the class definition, as it's a function you pass to connect and not specific to the component:
class C {
...
}
function mapStateToProps (state) {}
connect(mapStateToProps)(C)
Lastly, are you missing dispatch in your fetchContent call? Is this using redux-thunk?
In most cases you shouldn't store all response data in reducers. Your state hierarchy shouldn't repeat hierarchy of JSON response.
It would me more simpler to store only c variable in reducer:
//reducer1.js
export default function reducer1(state = { c: undefined }, action) {
switch (action.type) {
case SET_CONTENT:
return Object.assign({}, state, { c: action.data.a.b.c });
default:
return state;
}
}
Also, you have some mistakes in your code:
mapStateToProps function should be defined outside of class
Instead of directly calling fetchContent function, you should pass it result to dispatch.
// Test.jsx
class Test extends React.Component{
componentWillMount(){
dispatch(fetchContent({
type: SET_CONTENT
}));
}
render() {
const { header } = this.props;
return header ? (
<div>{header}</div>
) : (
<div>Loading...</div>
);
}
}
function mapStateToProps(state) {
return {
header: state.reducer1.c
}
}
export default (mapStateToProps)(Test);
What is the best way to wire up a view to a library which continually dispatches events? I saw in the redux real-world example that it's best to use mapDispatchToProps and inject your loading action. In our case we need to listen for events to dispatch over time. I thought we could use middleware to do this but I'm not 100% sure. There other alternative would be to do the listening inside the action creator itself.
Any ideas?
// app.jsx
<div>
<MyCollection name="ONE" />
<MyCollection name="TWO" />
</div>
// my-collection.jsx
import {load} from './actions';
class MyCollection extends React.Component {
componentDidMount() {
this.props.load(this.props.name);
}
componentDidUpdate(prevProps) {
if(prevProps.name !== this.props.name) {
this.props.load(this.props.name);
}
}
render() {
return (
<div>{this.props.name}: {this.props.value}</div>
)
}
}
function mapStateToProps(state) {
return {
value: state.value
}
}
const mapDispatchToProps = {
loadAndWatch
}
connect(mapStateToProps, mapDispatchToProps)(MyCollection);
// actions.js
import {MyCollection} from '#corp/library';
export function load(name) {
return (dispatch, getState) => {
MyCollection.getValue(name, value => {
dispatch(setValue(name));
})
}
}
export function setValue(name) {
return {
type: 'SET_VALUE',
name
}
}
// reducer.js
function reducer(state, action) {
switch(action.type) {
case 'SET_WATCHING':
return Object.assign({}, state, {isWatching: true});
case 'SET_VALUE':
return Object.assign({}, state, {value: action.value});
default:
return state;
}
}
// middleware.js
import {setValue} from './actions';
function middleware({dispatch, getState}) {
return next => action => {
switch (action.type) {
case 'SET_VALUE':
next(action);
if (getState().isWatching) {
return;
}
MyCollection.addChangeListener(name, value => {
dispatch(setValue(name))
});
dispatch({type: 'SET_WATCHING'});
}
}
}
I believe redux-observable is what you are looking for, in case you don't want to overcomplicate your app with home-made middlewares.