react redux set ownProps from the component receiving ownProps - react-native

I need to set the height of the ownProps from within the component that has the ownProps. It doesn't seem to update, it is always undefined.
Can it be done?
Here is my code, with the line of code to look at being commented with //////////////////
const mapStateToProps = (state: State, ownProps): Object => ({
searchText: state.product.search.query.locationAutocomplete.searchText,
place: state.product.search.query.locationAutocomplete.place,
searchResults: state.product.search.query.locationAutocomplete.searchResults,
shouldHideResults:
state.product.search.query.locationAutocomplete.shouldHideResults,
height: ownProps.height,
onContentSizeChange: ownProps.onContentSizeChange
})
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => ({
updateSearchQueryPageLocationAutocompleteSearchText: (searchText: string) => {
dispatch(
updateSearchQueryPageLocationAutocompleteSearchText({
searchText: searchText
})
)
},
updateSearchQueryPageLocationAutocompletePlace: (locationPlace: Place) => {
dispatch(
updateSearchQueryPageLocationAutocompletePlace({
locationPlace: locationPlace
})
)
},
getSearchQueryPageLocationAutocompletePlaceDetails: (placeId: number) => {
dispatch(
getSearchQueryPageLocationAutocompletePlaceDetails({ placeId: placeId })
)
},
getSearchQueryPageLocationAutocompleteResults: (text: string) => {
dispatch(getSearchQueryPageLocationAutocompleteResults({ text: text }))
},
updateProductSearchQueryPageIsLoctionListDisplayed: (displayed: boolean) => {
dispatch(updateProductSearchQueryPageIsLoctionListDisplayed(displayed))
},
updateLocationShouldHideResults: (shouldHideResults: boolean) => {
dispatch(updateSearchQueryPageShouldHideLocationResults(shouldHideResults))
}
})
let LocationView = (props: LocationViewProps): Object => {
return (
<Autocomplete
value={props.searchText}
customStyle={autocompleteStyle.customStyle(props.place)}
placeholder={'Location'}
updateValue={props.updateSearchQueryPageLocationAutocompleteSearchText}
updateDataBehindValue={
props.updateSearchQueryPageLocationAutocompletePlace
}
getDataBehindValueDetails={
props.getSearchQueryPageLocationAutocompletePlaceDetails
}
autocompleteResults={props.searchResults}
getSearchQueryPageLocationAutocompleteResults={
props.getSearchQueryPageLocationAutocompleteResults
}
shouldHideResults={props.shouldHideResults}
onListItemSelect={location => {
props.getSearchQueryPageLocationAutocompletePlaceDetails(
location.place_id
)
props.updateSearchQueryPageLocationAutocompleteSearchText(
location.description
)
props.updateLocationShouldHideResults(true)
props.updateProductSearchQueryPageIsLoctionListDisplayed(false)
}}
onContentSizeChange={height => {
console.log(height)
props.height = getHeight(height)}////////////////////////this line///////////////////////
}
onChangeText={text => onChangeText(props, text)}
/>
)
}
const getHeight = height => {
const h = Math.max(60, height)
return h
}
LocationView = connect(
mapStateToProps,
mapDispatchToProps
)(LocationView)

height: ownProps.height,
It's unseless, either you receive your props from the parent <MyComponent height={...} ..., either you pass the props from your store mapStateToProps = (state, ownProps) => ({ height: state..., //here ownProps contains to the props passed to MyComponent, so height={...} })
If you want to change the props passed from the parent, use a handle function like that
handleSetheight = height => this.setState({ height })
return (<Child height={this.state.height} setHeight={this.handleSetHeight}/>)

Related

react-native InputText onChange returns undefined or doesn't allow to change the value

I have the following code:
const { state, updateParentName, updateBabyName } = useContext(AuthContext);
const [isParentInputVisible, setParentInputVisible] = useState(false);
const toggleParentInput = () => {
setParentInputVisible(!isParentInputVisible);
};
<View style={styles.headerContainer}>
<Text style={styles.header}>Hello, </Text>
{isParentInputVisible
? (<TouchableOpacity onPress={toggleParentInput}>
<Text style={styles.headerLink}>{state.parentName}</Text>
</TouchableOpacity>)
: (<TextInput
value={state.parentName}
style={styles.parentInput}
onChangeText={(value) => {updateParentName(value);}}
onSubmitEditing={toggleParentInput}
/>)}
</View>;
And, as you can see, I use a Context to store my parentName value. I also set this var to parent as my default value.
Now, the problem that I am having and, that is driving me crazy, is I can't change the value in this Input field to anything. When I try to enter a new value there is returns undefined. When I remove onChangeText prop from that input for the sake of testing, it doesn't allow me to change the default value.
Can anyone point me to what should I do to fix it? I couldn't find anything useful that would help me.
UPDATE:
AuthContext file:
import createDataContext from './createDataContext';
const authReducer = (state, action) => {
switch (action.type) {
case 'update_parent_name':
return { ...state, parentName: action.payload };
default:
return state;
}
};
const updateParentName = (dispatch) => ({ parentName }) => {
dispatch({ type: 'update_parent_name', payload: parentName });
};
export const { Provider, Context } = createDataContext(
authReducer,
{
updateParentName,
},
{
parentName: 'parent',
}
);
and, just in case createDataContext code:
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext(defaultValue);
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
The error is you've taken object as an argument in this function updateParentName but while calling the function through prop onChangeText you've passed the value as is without enclosing it in an object!
const updateParentName = (dispatch) => ({ parentName }) => {
dispatch({ type: 'update_parent_name', payload: parentName });
};
onChangeText={(value) => {updateParentName(value);}} //passed only value not in an object!
Just change the onChangeText prop as such
onChangeText={(value) => {updateParentName({parentName:value});}}

react-native re-render negates filter

To learn myself react-native I am building an app that contains a FlatList filled with tickets with help of redux. When I try to filter through the tickets by typing in a number, the list gets filtered but only for 1 second. After that it gives a list of all tickets again. I have trouble finding the the logical error behind my beginner code. Any help would be appreciated.
I pasted the list below:
const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
useEffect(() => {
getTickets();
}, []);
const [enteredValue, setEnteredValue] = useState();
const [selectedNumber, setSelectedNumber] = useState(false);
const [displayedTickets, setDisplayedTickets] = useState();
const [confirmed, setConfirmed] = useState(false);
useEffect(() => {
setDisplayedTickets(allTickets);
});
const confirmInputHandler = () => {
const chosenNumber = parseInt(enteredValue);
if (isNaN(chosenNumber) || chosenNumber <= 0) {
Alert.alert(
'Invalid number',
'The number of upvotes has to be greater than 0.',
[{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
);
return;
}
setConfirmed(true);
setSelectedNumber(chosenNumber);
Keyboard.dismiss();
};
const resetInputHandler = () => {
setEnteredValue('');
setConfirmed(false);
};
const numberInputHandler = inputText => {
setEnteredValue(inputText.replace(/[^0-9]/g, ''));
};
if (confirmed) {
const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
setDisplayedTickets(foundTickets);
setConfirmed(false);
}
return (
<View>
<SearchBarUpvotes
numberInputHandler={numberInputHandler}
confirmInputHandler={confirmInputHandler}
enteredValue={enteredValue}
/>
<FlatList
removeClippedSubviews={false}
data={displayedTickets}
renderItem={({ item }) => (
<TicketItem ticket={item} navigation={navigation} />
)}
keyExtractor={item => item.id}
/>
</View>
);
};
const mapStateToProps = state => ({
ticket: state.ticket
});
export default connect(mapStateToProps, {
getTickets
})(AllTicketList);
The problem is in your second useEffect hook:
useEffect(() => {
setDisplayedTickets(allTickets);
});
This effect, will set the displayedTickets to allTickets on every re-render.
So here's what happens:
1. When you filter the tickets, you're changing the state, and you're setting the displatedTickets to be the filtered tickets: setDisplayedTickets(foundTickets);.
2. The displayedTickets is updated, the component is re-rendered, you see the new tickets for a second, and as soon as it is re-rendered, that effect is executing again and it sets the displayedTickets to allTickets again: setDisplayedTickets(allTickets);.
So here's my advice:
1. Remove the second useEffect - that will prevent the displayedTickets to be set again to allTickets on every re-render .
2. In your flatlist change the data to displayedTickets || allTickets. In this way, when the tickets will be unfiltered - the list will display the allTickets and as soon as you filter them, the list will display the displayedTickets.
So here's how your final code should look like:
const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
useEffect(() => {
getTickets();
}, []);
const [enteredValue, setEnteredValue] = useState();
const [selectedNumber, setSelectedNumber] = useState(false);
const [displayedTickets, setDisplayedTickets] = useState();
const [confirmed, setConfirmed] = useState(false);
// Remove this effect
//useEffect(() => {
// setDisplayedTickets(allTickets);
//});
const confirmInputHandler = () => {
const chosenNumber = parseInt(enteredValue);
if (isNaN(chosenNumber) || chosenNumber <= 0) {
Alert.alert(
'Invalid number',
'The number of upvotes has to be greater than 0.',
[{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
);
return;
}
setConfirmed(true);
setSelectedNumber(chosenNumber);
Keyboard.dismiss();
};
const resetInputHandler = () => {
setEnteredValue('');
setConfirmed(false);
};
const numberInputHandler = inputText => {
setEnteredValue(inputText.replace(/[^0-9]/g, ''));
};
if (confirmed) {
const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
setDisplayedTickets(foundTickets);
setConfirmed(false);
}
return (
<View>
<SearchBarUpvotes
numberInputHandler={numberInputHandler}
confirmInputHandler={confirmInputHandler}
enteredValue={enteredValue}
/>
<FlatList
removeClippedSubviews={false}
data={displayedTickets || allTickets} /* <-- displayedTickets || allTickets instead of displayedTickets */
renderItem={({ item }) => (
<TicketItem ticket={item} navigation={navigation} />
)}
keyExtractor={item => item.id}
/>
</View>
);
};
const mapStateToProps = state => ({
ticket: state.ticket
});
export default connect(mapStateToProps, {
getTickets
})(AllTicketList);

Enzyme + React Native + Jest: How to `console.log()` the content of a `<Text />`?

I'm testing the content of a <Text /> tag in React Native using Enzyme and Jest. My problem is the that the test is failing (even though everything empirically works and even though I feel like I wrote the test correctly). Here is the test:
describe("when less than minimum mandatory chosen", () => {
it("should render a label saying choose at least X items", () => {
console.log(wrapper);
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
).toBe(true);
});
});
I would like to check what the actual string within the <Text /> tag is. How could I achieve this?
As per request by Brian, here is the full test code:
import React from "react";
import { shallow } from "enzyme";
import { Text, View } from "react-native";
import {
SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import { AccordionList } from "./AccordionList";
import styles from "./styles";
const createTestProps = props => ({
header: "Kekse",
items: [
{
uuid: "1057e751-8ef1-4524-a743-1b4ba7b33d7b",
name: "Haferkeks",
price: "2.00",
priceCurrency: "EUR"
},
{
uuid: "f41f8e1a-b526-490e-ba4a-3d6acb3f3c16",
name: "Schokosojakeks",
price: "1.50",
priceCurrency: "EUR"
}
],
chosenItems: [],
onItemPressed: jest.fn(),
...props
});
describe("AccordionList", () => {
describe("rendering", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
});
it("should render container", () => {
expect(
wrapper
.find(View)
.at(0)
.prop("style")
).toContain(styles.container);
});
it("should render a <Collapsible />", () => {
expect(wrapper.find("Collapsible")).toHaveLength(1);
});
it("should give the header's <TouchableOpacity /> the headerbutton style", () => {
expect(
wrapper
.find("TouchableOpacity")
.at(0)
.prop("style")
).toEqual(styles.headerButton);
});
it("should render a header", () => {
expect(
wrapper
.find(Text)
.at(0)
.contains(props.header)
).toBe(true);
});
it("should give the header the header style", () => {
expect(
wrapper
.find(Text)
.at(0)
.prop("style")
).toEqual(styles.header);
});
it("should render a subheader", () => {
expect(
wrapper
.find(Text)
.at(1)
.prop("style")
).toContain(styles.subHeader);
});
it("should render a <TouchableOpacity /> for each of it's items", () => {
expect(wrapper.find("TouchableOpacity")).toHaveLength(props.items.length + 1);
});
describe("folded", () => {
it("should render an arrow pointing to the right", () => {
expect(wrapper.find("Image").prop("source")).toEqual(
require("../../assets/icons/rightArrow.png")
);
});
it("should render the folded arrow with the default style", () => {
expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
});
describe("mandatory", () => {
beforeEach(() => {
props = createTestProps({ minChoices: 1 });
wrapper = shallow(<AccordionList {...props} />);
});
it("should render a mandatory label with the minimum number of mandatory items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${props.minChoices})`)
).toBe(true);
});
});
describe("optional", () => {
it("should render an optional label", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`)
).toBe(true);
});
});
});
describe("expanded", () => {
beforeEach(() => {
wrapper.setState({ collapsed: false });
});
it("should render an arrow pointing down", () => {
expect(wrapper.find("Image").prop("source")).toEqual(
require("../../assets/icons/downArrow.png")
);
});
it("should render the expanded arrow with the default style", () => {
expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
});
// FIXME: These tests should also work but don't for some reason.
describe("mandatory", () => {
beforeEach(() => {
props = createTestProps({ minChoices: 1 });
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
describe("when less than minimum mandatory chosen", () => {
it("should render a label saying choose at least X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
).toBe(true);
});
});
describe("when more than minimum mandatory chosen", () => {
beforeEach(() => {
props = createTestProps({
minChoices: 1,
chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
});
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should render a label saying choose up to X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
).toBe(true);
});
});
});
describe("optional", () => {
it("should render a label saying choose up to X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
).toBe(true);
});
});
});
describe("item chosen", () => {
beforeEach(() => {
props = createTestProps({ chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"] });
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should render a checkmark for the item", () => {
expect(
wrapper
.find("Image")
.at(1)
.prop("source")
).toEqual(require("../../assets/icons/checkmark.png"));
});
it("should render the checkmark with the checkmarkIcon and active style", () => {
expect(
wrapper
.find("Image")
.at(1)
.prop("style")
).toEqual([styles.checkmarkIcon, styles.active]);
});
it("should render the folded arrow with the primary style", () => {
expect(
wrapper
.find("Image")
.at(0)
.prop("style")
).toContain(styles.active);
});
it("should render the expanded arrow with the primary style", () => {
wrapper.setState({ collapsed: false });
expect(
wrapper
.find("Image")
.at(0)
.prop("style")
).toContain(styles.active);
});
});
describe("max items chosen", () => {
beforeEach(() => {
props = createTestProps({
maxChoices: 1,
chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
});
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should disable all items but the chosen", () => {
expect(
wrapper
.find("TouchableOpacity")
.at(2)
.prop("disabled")
).toEqual(true);
});
});
});
describe("interaction", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
});
// FIXME: This test does not work for some reason...
// describe("pressing the header", () => {
// beforeEach(() => {
// wrapper.instance().toggleExpanded = jest.fn();
// wrapper
// .find("TouchableOpacity")
// .first()
// .prop("onPress")();
// });
//
// it("should call the toggleExpanded() instance function", () => {
// expect(wrapper.instance().toggleExpanded).toHaveBeenCalledTimes(1);
// });
// });
describe("pressing an item", () => {
beforeEach(() => {
wrapper
.find("TouchableOpacity")
.at(1)
.prop("onPress")();
});
it("should call the onItemPressed callback", () => {
expect(props.onItemPressed).toHaveBeenCalledTimes(1);
});
});
});
describe("component methods", () => {
describe("toggleExpanded", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
wrapper.instance().toggleExpanded();
});
it("should change the state of the component to collapsed=false", () => {
expect(wrapper.instance().state.collapsed).toBe(false);
});
});
});
});
And here is the full code of the component:
import React, { PureComponent } from "react";
import { Image, Text, TouchableOpacity, View } from "react-native";
import Collapsible from "react-native-collapsible";
import PropTypes from "prop-types";
import {
SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import styles from "./styles";
export class AccordionList extends PureComponent {
static propTypes = {
header: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
chosenItems: PropTypes.array.isRequired,
onItemPressed: PropTypes.func.isRequired,
minChoices: PropTypes.number,
maxChoices: PropTypes.number,
borderTop: PropTypes.bool,
borderBottom: PropTypes.bool
};
static defaultProps = {
minChoices: 0,
maxChoices: 1,
borderTop: false,
borderBottom: false
};
state = { collapsed: true };
toggleExpanded = () => {
this.setState(state => ({ collapsed: !state.collapsed }));
};
renderContent = () => {
const { items, onItemPressed, chosenItems, maxChoices } = this.props;
return (
<View>
{items.map(item => {
const disabled =
!chosenItems.includes(item.uuid) &&
chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length ===
maxChoices;
return (
<View style={styles.itemContainer} key={item.uuid}>
<TouchableOpacity
onPress={() => onItemPressed(item.uuid)}
style={[styles.itemButton, disabled ? styles.opaque : null]}
disabled={disabled}
>
{item.name && <Text style={styles.itemText}>{item.name}</Text>}
{item.price && (
<View style={styles.priceContainer}>
<Text style={styles.sizeText}>{item.label ? `${item.label} ` : ""}</Text>
<Text style={styles.sizeText}>
{item.size ? `${item.size.size}${item.size.unit}: ` : ""}
</Text>
<Text style={styles.priceText}>{item.price} €</Text>
</View>
)}
<View style={styles.checkMarkContainer}>
{chosenItems.includes(item.uuid) ? (
<Image
source={require("../../assets/icons/checkmark.png")}
resizeMode="contain"
style={[styles.checkmarkIcon, styles.active]}
/>
) : null}
</View>
</TouchableOpacity>
</View>
);
})}
</View>
);
};
render() {
const {
header,
items,
maxChoices,
minChoices,
chosenItems,
borderTop,
borderBottom
} = this.props;
const { collapsed } = this.state;
return (
<View
style={[
styles.container,
borderTop ? styles.borderTop : null,
borderBottom ? styles.borderBottom : null
]}
>
<TouchableOpacity onPress={this.toggleExpanded} style={styles.headerButton}>
<Text style={styles.header}>{header}</Text>
<Text style={[styles.subHeader, minChoices > 0 ? styles.mandatory : styles.optional]}>
{minChoices > 0
? collapsed
? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${minChoices})`
: chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length >=
maxChoices
? `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`
: collapsed
? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`}
</Text>
<Image
source={
collapsed
? require("../../assets/icons/rightArrow.png")
: require("../../assets/icons/downArrow.png")
}
resizeMode="contain"
style={[
styles.arrowIcon,
chosenItems.length > 0 &&
chosenItems.some(item => items.map(item => item.uuid).includes(item))
? styles.active
: styles.inActive
]}
/>
</TouchableOpacity>
<Collapsible collapsed={collapsed}>{this.renderContent()}</Collapsible>
</View>
);
}
}
export default AccordionList;
Looks like it's just a typo, you're missing a . in the component code.
Change this line:
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`
...to this:
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}.`
One other thing I noticed, your component uses a default for maxChoices of 1, but in your test there are two spots where you are referencing props.maxChoices where it hasn't been set. You'll probably want to change the two lines like this:
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
..to this:
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices || 1}.`)
to reflect the default assigned by the component.

mapDispatchToProps, id is not defined in action

I get id is not defined in fetchEvents, when trying to use button.value as a parameter.
I am using mapDispatchToProps and mapStateToProps in my component.
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: setSubCategory => dispatch(fetchEvents(id))
};
};
const mapStateToProps = state => {
return {
setCredentials: state.setCredentials,
categories: state.fetchCategories,
isLoading: state.isLoading
};
};
I then destructure my props to get my id:
const {
fetchEvents,
resetForm,
isLoading,
setCredentials: { setStudent, setGroup, setYear }
} = this.props;
const id = setStudent || setGroup || setYear;
However, when I dispatch my action:
const buttonOptions = [
{
key: 0,
label: "refresh",
value: Id,
icon: "undo"
},
{
key: 1,
label: "back",
value: Id,
icon: "caret-left"
}
];
return (
<View style={styles.container}>
{buttonOptions.map((button, i) => {
const style =
i == 0 ? styles.divContainerLeft : styles.divContainerRight;
return (
<View style={style} key={"view" + i}>
<TouchableOpacity
disabled={isLoading ? true : false}
key={"TouchableOpacity" + i}
// dispatch action here
onPress={i == 0 ? () => fetchEvents(button.value) : resetForm}
>
<Icon
name={button.icon}
style={styles.button}
color="white"
key={"icon" + i}
size={30}
/>
</TouchableOpacity>
</View>
);
})}
</View>
);
In your mapDispatchToProps, you are re-declaring the parameter name to setSubCategory, yet, in your fetch call you pass id.
Instead, try this:
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: (setSubCategory) => dispatch(fetchEvents(setSubCategory))
};
};
OR
const mapDispatchToProps = (dispatch) => {
return {
resetForm: () => dispatch(resetForm()),
fetchEvents: (id) => dispatch(fetchEvents(id))
};
};
This is because you are declaring and inlining fetchEvents as an anonymous function. The parameter names must match.

connect() does not re-render component

a component dispatches an action which modifies the Redux store and the other component should get the changed state to props and rerender.
The thing is, the component gets the props, and they are correct and modified, but the component is never rerendered.
Could someone help, been stuck too much..
Component who uses store:
on mount it does a http request,
and should rerender when the state is changed.
class CalendarView extends Component {
componentDidMount() {
axios.get('http://localhost:3000/api/bookings/get')
.then(foundBookings => {
this.props.getBookings(foundBookings);
})
.catch(e => console.log(e))
}
render() {
return (
<Agenda
items={this.props.items}
selected={this.props.today}
maxDate={this.props.lastDay}
onDayPress={this.props.setDay}
renderItem={this.renderItem}
renderEmptyDate={this.renderEmptyDate}
rowHasChanged={this.rowHasChanged}
/>
);
}
renderItem = (item) => {
return (
<View style={[styles.item, { height: item.height }]}>
<Text>Name: {item.name} {item.surname}</Text>
<Text>Time: {item.time}</Text>
</View>
);
}
renderEmptyDate = () => {
return (
<View style={styles.emptyDate}><Text>This is empty date!</Text></View>
);
}
rowHasChanged = (r1, r2) => {
console.log('hit')
return true;
}
}
const mapStateToProps = (state, ownProps) => {
return {
today: state.app.today,
lastDay: state.app.lastDay,
items: state.app.items
}
}
const mapDispatchToProps = (dispatch) => {
return {
setDay: date => dispatch(appActions.setSelectionDate(date.dateString)),
getBookings: data => dispatch(appActions.getBookings(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CalendarView);
Action Dispatching:
dispatches an action which modifies the state
onSubmit = (name, surname, selectionDate, selectionTime) => {
axios.post('http://localhost:3000/api/bookings/create', {
bookerName: name,
bookerSurname: surname,
bookerTime: selectionTime,
date: selectionDate
}).then(savedBookings => {
this.props.createBooking(savedBookings);
this.props.navigator.pop({
animationType: 'slide-down',
});
}).catch(e => console.log(e))
}
const mapStateToProps = state => {
//...
}
const mapDispatchToProps = (dispatch) => {
return {
createBooking: data => dispatch(appActions.createBooking(data))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewBookingScreen);
Reducer:
case types.CREATE_BOOKING: {
const { date , bookings } = action.savedBookings.data;
let dateArr = state.items;
// formatting a booking how needed
Object.keys(dateArr).forEach(key => {
if (key == date) {
dateArr[key] = [];
bookings.map(oneBooking => {
dateArr[key].push({
name: oneBooking.bookerName,
surname: oneBooking.bookerSurname,
time: oneBooking.bookerTime,
height: Math.max(50, Math.floor(Math.random() * 150))
});
})
}
});
return {
...state,
items: dateArr
};
}
full repo if needed: https://github.com/adtm/tom-airbnb/tree/feature/redux
Thank You in advance!
Your reducer is mutating the state, so connect thinks nothing has changed. In addition, your call to map() is wrong, because you're not using the result value.
Don't call push() on an array unless it's a copy. Also, please don't use any randomness in a reducer.
For more info, see Redux FAQ: React Redux ,Immutable Update Patterns, and Roll the Dice: Random Numbers in Redux .