I have an app which has multiple stack navigators, one of which has a createMaterialTopTabNavigator inside it which shows me a list. Now for each of the tabs i get a count of the items inside it, i fetch these count through a separate API call (Count for all tabs is fetched through a single API). By default i am able to show a static tabLabel.
What i need to do is to show the count of each of the tabs in their labels(tab titles).
Navigator Code:
import React from "react";
import { View } from "react-native";
import { createMaterialTopTabNavigator } from "react-navigation";
import SellerListingScreen from "screens/App/SellerListingScreen/SellerListingScreen";
const SellerListingNavigator = createMaterialTopTabNavigator(
{
PendingSellers: {
screen: () => <SellerListingScreen type={0} />,
navigationOptions: {
title: "Pending(<show count here>)"
}
},
CompletedSellers: {
screen: () => <SellerListingScreen type={1} />,
navigationOptions: {
title: "Completed(<show count here>)"
}
}
},
{
tabBarOptions: {
style: {
backgroundColor: "#00cc99"
}
},
lazy: true
}
);
export default SellerListingNavigator;
With react-navigation, navigationOptions can be a static object like in your current example or a function that take an object containing the navigation object. In your case, you can easily rewrite your navigationOptions like this:
({navigation}) => {
const fetchDone = navigation.getParam('countFetchDone');
const currentCount = navigation.getParam('count');
if (!fetchDone) {
navigation.setParam('countFetchDone', true);
fetch(YOUR_FETCH_OPTIONS_HERE)
.then((r) => r.json())
.then((data) => {
navigation.setParam('count', data.count);
});
}
if (currentCount !== undefined) {
return {
title: 'My list (' + currentCount + ')'
};
} else {
return {
title 'My list (...)'
};
}
}
You need to save the state of the query as params so that the header updates correctly (as it updates only when params change). fetchDone is used to be sure the query is done once.
Ok i managed to solve it by creating a custom navigator white extending my existing tabNavigator & passing the required params to screenProps
import React from "react";
import { createMaterialTopTabNavigator } from "react-navigation";
// #ts-ignore
import SellerListingScreen from "screens/App/SellerListingScreen/SellerListingScreen";
// #ts-ignore
import { getItem } from "utils/interactAsyncStorage";
const SellerListingNavigator = createMaterialTopTabNavigator(
{
PendingSellers: {
screen: () => <SellerListingScreen type={0} />,
navigationOptions: ({ screenProps }) => ({
title: `Pending (${screenProps.pending})`
})
},
CompletedSellers: {
screen: () => <SellerListingScreen type={1} />,
navigationOptions: ({ screenProps }) => ({
title: `Completed (${screenProps.completed})`
})
}
},
{
tabBarOptions: {
style: {
backgroundColor: "#00cc99"
}
},
lazy: true
}
);
class customSellerListingNavigator extends React.Component {
constructor(props) {
super(props);
this.state = { pending: 0, completed: 0 };
}
static router = SellerListingNavigator.router;
_fetchPickupCounts = async () => {
const userData = await getItem("UserData");
const headers = {
"Content-Type": "application/json",
"Session-Token": userData.sessionToken,
};
const baseUrl = "baseurl here";
const url = `${baseUrl}/pickupCount/`;
return await fetch(url, {
method: "post",
headers: headers
})
.then(response => response.json())
.then(responseJson => {
this.setState({
pending: responseJson.pending,
completed: responseJson.completed
});
})
.catch(error => {
console.error(error);
});
};
componentDidMount() {
this._fetchPickupCounts();
}
render() {
const { navigation } = this.props;
return (
<SellerListingNavigator
navigation={navigation}
screenProps={{
pending: this.state.pending,
completed: this.state.completed
}}
/>
);
}
}
export default customSellerListingNavigator;
Related
The component does not re render after successfully update state in redux
i have tried to do some condition in componentShouldUpdate end up with loading true without change
reducer.js
import * as types from "./actionsType";
const INITIAL_STATE = {
slide_data: [],
error: null,
loading: false,
};
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties,
};
};
const slideReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.SLIDES_FETCH_START:
return updateObject(state, {
error: null,
loading: true,
});
case types.SLIDES_FETCH_SUCSSES:
return updateObject(state, {
slide_data: action.payload,
error: null,
loading: false,
});
case types.SLIDES_FETCH_FAIL:
return updateObject(state, {
error: action.error,
loading: false,
});
default:
return state;
}
};
export default slideReducer;
actions.js
import * as types from "./actionsType";
import axios from "axios";
import { selectSlides } from "./slides.selectors";
export const slidesStart = () => {
return {
type: types.SLIDES_FETCH_START,
};
};
export const slidesSucces = (slides) => {
return {
type: types.SLIDES_FETCH_SUCSSES,
payload: slides,
};
};
export const slidesFail = (error) => {
return {
type: types.SLIDES_FETCH_FAIL,
error: error,
};
};
export const fetchSlides = () => {
return (dispatch) => {
console.log("fetch Start");
dispatch(slidesStart());
axios
.get("http://127.0.0.1:8000/slides/", {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
dispatch(slidesSucces(res.data));
})
.catch((err) => dispatch(slidesFail(err)));
};
};
component
class IntroPage extends Component {
constructor(props) {
super(props);
this.tlitRef = React.createRef();
this.titlelRef = React.createRef();
this.subTitleRef = React.createRef();
this.showcase = React.createRef();
}
componentDidMount() {
this.props.fetchSlides();
}
render() {
const { slides, loading } = this.props;
if (loading) {
return <h1>Loading</h1>;
}
return (
<div className="intro">
<div className="wrapper">
{slides.map((data) => (
<SwiperSlides data={data} key={data.name} />
))}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.slides.loading,
error: state.slides.error,
slides: state.slides.slide_data,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchSlides: () => dispatch(fetchSlides()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(IntroPage);
Register the redux-logger correctly. The data was returned but nothing changes when I do redux-persist and try to reload data come through
update ::
when I change the size of the browser data it correctly appears what is this !!
update : this problem related to swiperjs
and the solution will be like that:
1 - assign swiper instance to React.CreateRef(null) : this.swiper = React.CreateRef(null)
2 - in componentDidUpdate() make a swiper update : this.swiper.current.update()
4 - use a arrow function syntax in swiper on functions to refer to the outer scope
GIF of Rerender occuring
I'm not sure how to proceed. As you can see, the Header's state (as passed down via context) is switching from the user's data --> undefined --> same user's data. This occurs every time there's a url change, and doesn't happen when I do things that don't change the url (like opening the cart for example).
Is this expected behaviour? Is there any way I can get the query in my context to only be called when there is no user data or when the user data changes? I tried using useMemo, but to no avail.
auth.context
import React, { useState} from "react";
import {
CURRENT_USER,
GET_LOGGED_IN_CUSTOMER,
} from "graphql/query/customer.query";
import { gql, useQuery, useLazyQuery } from "#apollo/client";
import { isBrowser } from "components/helpers/isBrowser";
export const AuthContext = React.createContext({});
export const AuthProvider = ({ children }) => {
const [customer, { data, loading, error }] = useLazyQuery(
GET_LOGGED_IN_CUSTOMER,
{
ssr: true,
}
);
const { data: auth } = useQuery(CURRENT_USER, {
onCompleted: (auth) => {
console.log(auth);
customer({
variables: {
where: {
id: auth.currentUser.id,
},
},
});
},
ssr: true,
});
console.log(data);
const isValidToken = () => {
if (isBrowser && data) {
const token = localStorage.getItem("token");
if (error) {
console.log("error", error);
}
if (token && data) {
console.log("token + auth");
return true;
} else return false;
}
};
const [isAuthenticated, makeAuthenticated] = useState(isValidToken());
function authenticate() {
makeAuthenticated(isValidToken());
}
function signout() {
makeAuthenticated(false);
localStorage.removeItem("token");
}
return (
<AuthContext.Provider
value={{
isAuthenticated,
data,
authenticate,
auth,
signout,
}}
>
{children}
</AuthContext.Provider>
);
};
(In Header, userData is equal to data just passed through an intermediary component (to provide to mobile version)).
header.tsx
import React, { useContext } from "react";
import Router, { useRouter } from "next/router";
import { useApolloClient } from "#apollo/client";
import { openModal } from "#redq/reuse-modal";
import SearchBox from "components/SearchBox/SearchBox";
import { SearchContext } from "contexts/search/search.context";
import { AuthContext } from "contexts/auth/auth.context";
import LoginModal from "containers/LoginModal";
import { RightMenu } from "./Menu/RightMenu/RightMenu";
import { LeftMenu } from "./Menu/LeftMenu/LeftMenu";
import HeaderWrapper from "./Header.style";
import LogoImage from "image/hatchli-reduced-logo.svg";
import { isCategoryPage } from "../is-home-page";
type Props = {
className?: string;
token?: string;
pathname?: string;
userData?: any;
};
const Header: React.FC<Props> = ({ className, userData }) => {
const client = useApolloClient();
const { isAuthenticated, signout } = useContext<any>(AuthContext);
const { state, dispatch } = useContext(SearchContext);
console.log(isAuthenticated);
console.log(userData);
const { pathname, query } = useRouter();
const handleLogout = () => {
if (typeof window !== "undefined") {
signout();
client.resetStore();
Router.push("/medicine");
}
};
const handleJoin = () => {
openModal({
config: {
className: "login-modal",
disableDragging: true,
width: "auto",
height: "auto",
animationFrom: { transform: "translateY(100px)" },
animationTo: { transform: "translateY(0)" },
transition: {
mass: 1,
tension: 180,
friction: 26,
},
},
component: LoginModal,
componentProps: {},
closeComponent: "",
closeOnClickOutside: true,
});
};
const onSearch = (text: any) => {
dispatch({
type: "UPDATE",
payload: {
...state,
text,
},
});
};
const { text } = state;
const onClickHandler = () => {
const updatedQuery = query.category
? { text: text, category: query.category }
: { text };
Router.push({
pathname: pathname,
query: updatedQuery,
});
};
const showSearch = isCategoryPage(pathname);
return (
<HeaderWrapper className={className}>
<LeftMenu logo={LogoImage} />
{showSearch && (
<SearchBox
className="headerSearch"
handleSearch={(value: any) => onSearch(value)}
onClick={onClickHandler}
placeholder="Search anything..."
hideType={true}
minimal={true}
showSvg={true}
style={{ width: "100%" }}
value={text || ""}
/>
)}
<RightMenu
isAuth={userData}
onJoin={handleJoin}
onLogout={handleLogout}
avatar={userData && userData.user && userData.user.avatar}
/>
</HeaderWrapper>
);
};
export default Header;
I am trying to make live search for name in table but i can't make live search i don't know how to do this i wrote my code like this as i mentioned please help me how to make live search on name field foe table and in Search Page i used onSubmit={this.props.loaddata like this thanks
import React, { Component } from "react";
import Search from "../../views/Cars/Search";
class Search1 extends Component {
constructor(props) {
super(props);
this.state = {
query: []
};
}
// Get Data from filter date
getData = async e => {
try {
const search = e.target.elements.search.value;
e.preventDefault();
const res = await fetch(`https://swapi.co/api/people/?search=${search}`);
const query = await res.json();
console.log(query);
this.setState({
query: query.results
});
} catch (e) {
console.log(e);
}
};
async componentDidMount() {
// let authToken = localStorage.getItem("Token");
try {
const res = await fetch(`https://swapi.co/api/people/`);
const query = await res.json();
// console.log(movie);
this.setState({
query: query.results
});
} catch (e) {
console.log(e);
}
}
render() {
const options = this.state.query.map(r => <li key={r.id}>{r.name}</li>);
return (
<div>
<Search loaddata={this.getData} />
{options}
</div>
);
}
}
export default Search1;
Genrally You can try React-Search
import Search from 'react-search'
import ReactDOM from 'react-dom'
import React, { Component, PropTypes } from 'react'
class TestComponent extends Component {
HiItems(items) {
console.log(items)
}
render () {
let items = [
{ id: 0, value: 'ruby' },
{ id: 1, value: 'javascript' },
{ id: 2, value: 'lua' },
{ id: 3, value: 'go' },
{ id: 4, value: 'julia' }
]
return (
<div>
<Search items={items} />
<Search items={items}
placeholder='Pick your language'
maxSelected={3}
multiple={true}
onItemsChanged={this.HiItems.bind(this)} />
</div>
)
}
}
Made few changes to your component. Send e.target.value from your child component
class Search1 extends Component {
constructor(props) {
super(props);
this.state = {
query: []
};
}
// Get Data from filter date
getData = search => {
const url = `https://swapi.co/api/people${search ? `/?search=${search}` : ``}`;
// e.preventDefault();
fetch(url)
.then(res => res.json())
.then(data =>
this.setState({
query: data.results || []
})).catch(e => console.log(e));
};
async componentDidMount() {
// let authToken = localStorage.getItem("Token");
this.getData();
}
render() {
const options = this.state.query.map(r => <li key={r.id}>{r.name}</li>);
return (
<div>
<Search loaddata={this.getData} />
{options}
</div>
);
}
}
export default Search1;
For Gettind Data from Api you can follow this code of react-search
import Search from 'react-search'
import ReactDOM from 'react-dom'
import React, { Component, PropTypes } from 'react'
class TestComponent extends Component {
constructor (props) {
super(props)
this.state = { repos: [] }
}
getItemsAsync(searchValue, cb) {
let url = `https://api.github.com/search/repositories?q=${searchValue}&language=javascript`
fetch(url).then( (response) => {
return response.json();
}).then((results) => {
if(results.items != undefined){
let items = results.items.map( (res, i) => { return { id: i, value: res.full_name } })
this.setState({ repos: items })
cb(searchValue)
}
});
}
render () {
return (
<div>
<Search items={this.state.repos}
multiple={true}
getItemsAsync={this.getItemsAsync.bind(this)}
onItemsChanged={this.HiItems.bind(this)} />
</div>
)
}
I got one container connected to one component. Its a select-suggestion component. The problem is that both my container and component are getting too much repeated logic and i want to solve this maybe creating a configuration file or receiving from props one config.
This is the code:
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { goToPageRequest as goToPageRequestCompetitions } from '../ducks/competitions/index';
import { getSearchParam as getSearchCompetitionsParam, getCompetitionsList } from '../ducks/competitions/selectors';
import { goToPageRequest as goToPageRequestIntermediaries } from '../ducks/intermediaries/index';
import { getSearchParam as getSearchIntermediariesParam, getIntermediariesList } from '../ducks/intermediaries/selectors';
import SelectBox2 from '../components/SelectBox2';
export const COMPETITIONS_CONFIGURATION = {
goToPageRequest: goToPageRequestCompetitions(),
getSearchParam: getSearchCompetitionsParam(),
suggestions: getCompetitionsList()
};
export const INTERMEDIARIES_CONFIGURATION = {
goToPageRequest: goToPageRequestIntermediaries(),
getSearchParam: getSearchIntermediariesParam(),
suggestions: getIntermediariesList()
};
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(SelectBox2));
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Flex, Box } from 'reflexbox';
import classname from 'classnames';
import styles from './index.scss';
import Input from '../Input';
import { AppButtonRoundSquareGray } from '../AppButton';
import RemovableList from '../RemovableList';
const MIN_VALUE_TO_SEARCH = 5;
const NO_SUGGESTIONS_RESULTS = 'No results found';
class SelectBox extends Component {
/**
* Component setup
* -------------------------------------------------------------------------*/
constructor(props) {
super(props);
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
}
/**
* Component lifecycle
* -------------------------------------------------------------------------*/
componentWillMount() {
console.log(this.props);
document.addEventListener('mousedown', this.onClickOutside, false);
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
}
componentWillUnmount() {
console.log(this.props);
document.removeEventListener('mousedown', this.onClickOutside, false);
}
componentWillReceiveProps(nextProps){
console.log(this.props);
if (this.props.suggestionsType === 'competition') {
this.state.suggestions = nextProps.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries') {
this.state.suggestions = nextProps.intermediariesSuggestions;
}
}
/**
* DOM event handlers
* -------------------------------------------------------------------------*/
onButtonClick = (ev) => {
ev.preventDefault();
const itemIncluded = this.state.items.find(item => item.id === this.state.selection);
if (this.state.selection && !itemIncluded) {
const item =
this.state.suggestions.find(suggestion => suggestion.id === this.state.selection);
this.setState({ items: [...this.state.items, item] });
}
};
onChangeList = (items) => {
const adaptedItems = items
.map(item => ({ label: item.name, id: item.itemName }));
this.setState({ items: adaptedItems });
};
onClickOutside = (ev) => {
if (this.wrapperRef && !this.wrapperRef.contains(ev.target)) {
this.setState({ displayBox: false });
}
};
onSuggestionSelected = (ev) => {
this.setState({
displayBox: false,
value: ev.target.textContent,
selection: ev.target.id });
};
onInputChange = (ev) => {
this.generateSuggestions(ev.target.value);
};
onInputFocus = () => {
this.generateSuggestions(this.state.value);
};
/**
* Helper functions
* -------------------------------------------------------------------------*/
setWrapperRef = (node) => {
this.wrapperRef = node;
};
executeSearch = (value) => {
if (this.props.suggestionsType === 'competition'){
this.props.searchCompetitions(value);
}
if (this.props.suggestionsType === 'intermediaries'){
this.props.searchIntermediaries(value);
}
};
generateSuggestions = (value) => {
if (value.length > MIN_VALUE_TO_SEARCH) {
this.executeSearch(value);
this.setState({ displayBox: true, value, selection: '' });
} else {
this.setState({ displayBox: false, value, selection: '' });
}
};
renderDataSuggestions = () => {
const { listId } = this.props;
const displayClass = this.state.displayBox ? 'suggestions-enabled' : 'suggestions-disabled';
return (
<ul
id={listId}
className={classname(styles['custom-box'], styles[displayClass], styles['select-search-box__select'])}
>
{ this.state.suggestions.length !== 0 ?
this.state.suggestions.map(suggestion => (<li
className={classname(styles['select-search-box__suggestion'])}
onClick={this.onSuggestionSelected}
id={suggestion.get(this.props.suggestionsOptions.id)}
key={suggestion.get(this.props.suggestionsOptions.id)}
>
<span>{suggestion.get(this.props.suggestionsOptions.label)}</span>
</li>))
:
<li className={(styles['select-search-box__no-result'])}>
<span>{NO_SUGGESTIONS_RESULTS}</span>
</li>
}
</ul>
);
};
renderRemovableList = () => {
if (this.state.items.length > 0) {
const adaptedList = this.state.items
.map(item => ({ name: item.name, itemName: item.id }));
return (<RemovableList
value={adaptedList}
className={classname(styles['list-box'])}
onChange={this.onChangeList}
uniqueIdentifier="itemName"
/>);
}
return '';
};
render() {
const input = {
onChange: this.onInputChange,
onFocus: this.onInputFocus,
value: this.state.value
};
return (
<Flex className={styles['form-selectBox']}>
<Box w={1}>
<div
ref={this.setWrapperRef}
className={styles['div-container']}
>
<Input
{...this.props}
input={input}
list={this.props.listId}
inputStyle={classname('form-input--bordered', 'form-input--rounded', styles.placeholder)}
/>
{ this.renderDataSuggestions() }
</div>
</Box>
<Box>
<AppButtonRoundSquareGray type="submit" className={styles['add-button']} onClick={this.onButtonClick}>
Add
</AppButtonRoundSquareGray>
</Box>
<Box>
{ this.renderRemovableList() }
</Box>
</Flex>
);
}
}
SelectBox.propTypes = {
items: PropTypes.instanceOf(Array),
placeholder: PropTypes.string,
listId: PropTypes.string,
className: PropTypes.string
};
SelectBox.defaultProps = {
items: [],
placeholder: 'Choose an option...',
listId: null,
className: ''
};
export default SelectBox;
As you see, in many places i am validating the type of suggestions and do something with that. Its suppose to be a reusable component, and this component could accept any kind of type of suggestions. If this grows, if will have very big validations and i don't want that. So i think that i want something similar to this:
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
How can i make something similar to that?
Here are a few things to consider:
The purpose of using Redux is to remove state logic from your components.
What you've currently got has Redux providing some state and your component providing some state. This is an anti-pattern (bad):
// State from Redux: (line 22 - 24)
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
// State from your component: (line 65 - 71)
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
If you take another look at your SelectBox component - a lot of what it is doing is selecting state:
// The component is parsing the state and choosing what to render (line 79 - 86)
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
Turns out, this is precisely what mapStateToProps() is for. You should move this selection logic to mapStateToProps(). Something like this:
const mapStateToProps = (state) => {
let suggestions = null;
switch (state.suggestionType) {
case 'competition':
suggestions = state.suggestions.competition;
break;
case 'intermediaries':
suggestions = state.suggestions.intermediaries;
break;
default:
break;
}
return {
suggestions
};
};
Every time the state updates (in Redux) it will pass new props to your component. Your component should only be concerned with how to render its part of the state. And this leads me to my next point: When your application state is all being managed by Redux and you don't have state logic in your components, your components can simply be functions (functional components).
const SelectBox3 = ({ suggestions }) => {
const onClick = evt => { console.log('CLICK!'); };
const list = suggestions.map((suggestion, index) => {
return (
<li key={index} onClick={onClick}>suggestion</li>
);
});
return (
<ul>
{list}
</ul>
);
};
Applying these patterns, you get components that are very easy to reason about, and that is a big deal if you want to maintain this code into the future.
Also, by the way, you don't need to use mergeProps() in your example. mapDispatchToProps can just return your search function since connect() will automatically assemble the final props object for you.:
const mapDispatchToProps = (dispatch, ownProps) => ({
// 'search' will be a key on the props object passed to the component!
search: searchParam => {
dispatch(ownProps.reduxConfiguration.goToPageRequest({ searchParam });
// (also, your 'reduxConfiguration' is probably something that belongs in
// the Redux state.)
}
});
I highly recommend giving the Redux docs a good read-through. Dan Abramov (and crew) have done a great job of laying it all out in there and explaining why the patterns are the way they are.
Here's the link: Redux.
Also, look into async actions and redux-thunk for dealing with asynchronous calls (for performing a search on a server, for example).
Finally let me say: you're on the right track. Keep working on it, and soon you will know the joy of writing elegant functional components for your web apps. Good luck!
I am trying to implement the following example in my project:
https://github.com/reactjs/redux/tree/master/examples/async
But I keep running into the error: dispatch is not function
when executing the dispatch function from my actions class.
It seems as if Dispatcher is not getting passed down to my actions but I followed the flow in the example:
actions/index.js file:
export const REQUEST_LOCAL_POSTS = 'REQUEST_LOCAL_POSTS';
export const RECEIVE_LOCAL_POSTS = 'RECEIVE_LOCAL_POSTS';
export const REQUEST_CURRENT_LOCATION = 'REQUEST_CURRENT_LOCATION';
export const RECEIVE_CURRENT_LOCATION = 'RECEIVE_CURRENT_LOCATION';
export const requestCurrentLocation = () => ({
type: REQUEST_CURRENT_LOCATION
})
export const receiveCurrentLocation = currentLocation => ({
type: RECEIVE_CURRENT_LOCATION,
currentLocation,
receivedCurrentLocationAt: Date.now()
})
export const requestLocalPosts = () => ({
type: REQUEST_LOCAL_POSTS
})
export const receiveLocalPosts = json => ({
type: RECEIVE_LOCAL_POSTS,
posts: json,
receivedAt: Date.now()
})
export const fetchLocalPosts = dispatch => {
dispatch(requestCurrentLocation())
navigator.geolocation.getCurrentPosition(
(position) => {
dispatch(requestLocalPosts()) // getting dispatch is not a function here
return fetch(`http://192.168.1.3:9000/posts?lng=${position.coords.longitude}&lat=${position.coords.latitude}&radius=1000`)
.then(response => response.json())
.then(json => dispatch(receiveLocalPosts(json)))
},
(error) => this.setState({ error: error.message }),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 },
)
home.js (component):
import React, { Component } from 'react';
import {Text, View, ActivityIndicator, FlatList, Image, TouchableHighlight} from 'react-native';
import styles from './styles';
import MapView from 'react-native-maps';
import images from '../../config/images';
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { fetchLocalPosts } from '../../actions'
class Home extends Component {
static navigationOptions = ({ navigation }) => {
//const {dispatch , state, setParams} = navigation;
return {
title:<Text style={styles.title}>Localized</Text>,
headerStyle: {backgroundColor: '#2c6da4'},
headerRight: (
<TouchableHighlight onPress={() => navigation.dispatch({ type: 'MAP_EXPLORE' })}>
<Image
source={images.icons.map}
/>
</TouchableHighlight>
),
};
};
static propTypes = {
posts: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
lastUpdated: PropTypes.number,
dispatch: PropTypes.func.isRequired
}
componentDidMount() {
console.log(this.props)
const { dispatch } = this.props
dispatch(fetchLocalPosts()) // calling action from here
}
render() {
return (
<View style={styles.container}>
// i know posts isnt accessed here yet, still trying
to get past error
<FlatList
data={this.state.data}
refreshing={this.state.refreshing}
showsVerticalScrollIndicator={false}
ListHeaderComponent={this.renderHeader}
onRefresh={this._onRefresh.bind(this)}
renderItem={({item}) => <View><FlatList
data={item.posts}
horizontal={true}
snapToAlignment='center'
showsHorizontalScrollIndicator={false}
renderItem={({item}) => <Text style={styles.item}>{item.title}{"\n"}{item.snippet}</Text>}/>
<Text style={styles.subItem}>{item.Name}{"\n"}{item.Address}</Text>
</View>}
/>
</View>
);
}
}
const mapStateToProps = state => {
const { postsByBusiness } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByBusiness || {
isFetching: true,
items: []
}
return {
posts,
isFetching,
lastUpdated
}
}
export default connect(mapStateToProps)(Home)
reducer/index.js:
import { combineReducers } from 'redux';
import { NavigationActions } from 'react-navigation';
import { AppNavigator } from '../navigators/AppNavigator';
import { RECEIVE_LOCAL_POSTS, REQUEST_LOCAL_POSTS } from '../actions';
const initialNavState=AppNavigator.router.getStateForAction(NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
routeName: 'Home',
}),
],
}));
const MAP_EXPLORE = 'MAP_EXPLORE';
const LIST_EXPLORE = 'LIST_EXPLORE';
function nav(state = initialNavState, action) {
let nextState;
switch(action.type) {
case MAP_EXPLORE:
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Map'}),
state
);
break;
case LIST_EXPLORE:
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'List'}),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
return nextState || state;
}
function postsByBusiness(state = { }, action) {
switch(action.type) {
case RECEIVE_LOCAL_POSTS:
return {
...state,
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAts
}
default:
return state
}
}
const AppReducer = combineReducers({
nav,
postsByBusiness
});
export default AppReducer;
fetchLocalPosts should be returning a function that takes dispatch as an argument. Changing it to the following should fix it.
export const fetchLocalPosts = () => dispatch => {
...
It's another way of doing this:
export const fetchLocalPosts = () => {
return function (dispatch) {
...