2
votes

I have attempted to create a reducer, following the same logic of an example I had working with git repos

Here is said reducer:

/**
 * Created by kenji on 3/9/18.
 */
/**
 * /stock?search=String
 * Searches Stock table, checking for given search value
 * and returns an array that contains that value
 * @action GET
 * @returns [array]
 * @type {string}
 */
export const GET_STOCK_LIST = 'ReduxStarter/stock_list/LOAD';
export const GET_STOCK_LIST_SUCCESS = 'ReduxStarter/stock_list/LOAD_SUCCESS';
export const GET_STOCK_LIST_FAIL = 'ReduxStarter/stock_list/LOAD_FAILURE';

/**
 * /stock/${id}
 * Gets an ID passed to it and returns an object if it exists
 * @action GET
 * @returns {object}
 * @type {string}
 */
export const GET_STOCK = 'ReduxStarter/stock/LOAD';
export const GET_STOCK_SUCCESS = 'ReduxStarter/stock/LOAD_SUCCESS';
export const GET_STOCK_FAIL = 'ReduxStarter/stock/LOAD_FAIL';

/**
 * /stock/${id}
 * Updates Stock by passing an object with new variables to update with
 * @action PUT
 * @returns {null}
 * @type {string}
 */
export const PUT_STOCK = 'ReduxStarter/stock/CHANGE';
export const PUT_STOCK_SUCCESS = 'ReduxStarter/stock/CHANGE_SUCCESS';
export const PUT_STOCK_FAIL = 'ReduxStarter/stock/CHANGE_FAIL';

/**
 * /stock/${id}/barcodes
 * Gets the barcodes of an a stock item if the ID is valid
 * @action: GET
 * @returns [array]
 * @type {string}
 */
export const GET_BARCODES = 'ReduxStarter/stock/barcodes/LOAD';
export const GET_BARCODES_SUCCESS = 'ReduxStarter/stock/barcodes/LOAD_SUCCESS';
export const GET_BARCODES_FAIL = 'ReduxStarter/stock/barcodes/LOAD_FAIL';

/**
 * /stock/barcode/${barcode}
 * Gets a stock item from the supplied barcode
 * @action: GET
 * @returns [array]
 * @type {string}
 */
export const GET_STOCK_FROM_BARCODE = 'ReduxStarter/stock/barcode/LOAD';
export const GET_STOCK_FROM_BARCODE_SUCCESS = 'ReduxStarter/stock/barcode/LOAD_SUCCESS';
export const GET_STOCK_FROM_BARCODE_FAIL = 'ReduxStarter/stock/barcode/LOAD_FAIL';

const initialState = {
    stockList: [],
    stock: {},
    result: {},
    barcodeList: [],
    stockFromBarcode: {},
};

export default function reducer(state = initialState, action) {
    switch(action.type) {
        case GET_STOCK_LIST:
            return { ...state, loadingStockList: true };
        case GET_STOCK_LIST_SUCCESS:
            return { ...state, loadingStockList: false, stockList: action.payload.data };
        case GET_STOCK_LIST_FAIL:
            return { ...state, loadingStockList: false, stockListError: 'Failed to retrieve stock list' };
        case GET_STOCK:
            return { ...state, loadingStock: true };
        case GET_STOCK_SUCCESS:
            return { ...state, loadingStock: false, stock: action.payload.data };
        case GET_STOCK_FAIL:
            return { ...state, loadingStock: false, stockError: 'Failed to retrieve stock list' };
        case PUT_STOCK:
            return { ...state, loadingStockUpdate: true };
        case PUT_STOCK_SUCCESS:
            return { ...state, loadingStockUpdate: false, result: action.payload.data };
        case PUT_STOCK_FAIL:
            return { ...state, loadingStockUpdate: false, stockUpdateError: 'Failed to update stock list' };
        case GET_BARCODES:
            return { ...state, loadingBarcodes: true };
        case GET_BARCODES_SUCCESS:
            return { ...state, loadingBarcodes: false, barcodeList: action.payload.data };
        case GET_BARCODES_FAIL:
            return { ...state, loadingBarcodes:false, barcodesError: 'Failed to load barcodes' };
        case GET_STOCK_FROM_BARCODE:
            return { ...state, loadingStockFromBarcode: true };
        case GET_STOCK_FROM_BARCODE_SUCCESS:
            return { ...state, loadingStockFromBarcode: false, stockFromBarcode: action.payload.data };
        case GET_STOCK_FROM_BARCODE_FAIL:
            return { ...state, loadingStockFromBarcode: false, stockFromBarcodeError: 'Failed to get stock item from barcode' };
        default:
            return state;
    }
}

export function listStockArray(searchQuery, pageSize = 50, pageNumber = 1) {
    return {
        type: GET_STOCK_LIST,
        payload: {
            request: {
                url: `/stock?search=${searchQuery}&pageSize=${pageSize}&pageNumber=${pageNumber}`
            }
        }
    };
}

export function listStockItem(stockID) {
    return {
        type: GET_STOCK_LIST,
        payload: {
            request: {
                url: `/stock/${stockID}`
            }
        }
    };
}

export function updateStock(stockID, data) {
    return {
        type: GET_STOCK_LIST,
        payload: {
            request: {
                url: `/stock/${stockID}`,
                method: `PUT`,
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                data: data,
            }
        }
    };
}

export function listStockBarcodes(stockID) {
    return {
        type: GET_BARCODES,
        payload: {
            request: {
                url: `/stock/${stockID}/barcodes`
            }
        }
    };
}

export function listStockFromBarcode(barcode) {
    return {
        type: GET_BARCODES,
        payload: {
            request: {
                url: `/stock/barcode/${barcode}`
            }
        }
    };
}

Now i get the error as mentioned in the header: **TypeError: Cannot read property 'map' of undefined **

Here is the implimentation of the reducers to the app:

import reducers from './redux/reducers/lots_reducer';
import StockList from './components/StockList';

const client = axios.create({
    baseURL: 'https://api.github.com',
    responseType: 'json'
});

const store = createStore(reducers, applyMiddleware(axiosMiddleware(client)));

const Stack = createStackNavigator({
    StockList: {
        screen: StockList
    },
});

export default class App extends Component {
    render() {
        return (
            <Provider store={store}>
                <View style={styles.container}>
                    <Stack/>
                </View>
            </Provider>
        );
    }
}

And finally the actual component:

import { connect } from 'react-redux';

import { listStockArray } from '../redux/reducers/lots_reducer';

class StockList extends Component {

    componentDidMount() {
        this.props.listStockArray('panadol');
    }

    renderItem = ({ stockList }) => (
        <TouchableOpacity
            style={styles.item}
            onPress={() => this.props.navigation.navigate(`Detail`, { name: stockList.StockID })}
        >
            <Text>{stockList.TradeName}</Text>
        </TouchableOpacity>
    );

    render() {
        const { stockList } = this.props;
        return (
            <FlatList
                styles={styles.container}
                data={stockList}
                renderItem={this.renderItem}
            />
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1
    },
    item: {
        padding: 16,
        borderBottomWidth: 1,
        borderBottomColor: '#ccc'
    }
});

const mapStateToProps = state => {
    let storedRepositories = state.repos.map(repo => ({ key: (repo.id).toString(), ...repo }));
    return {
        repos: storedRepositories
    };
};

const mapDispatchToProps = {
    listStockArray
};

export default connect(mapStateToProps, mapDispatchToProps)(StockList);

Sorry about the large amount of code dumped, its just all related and I believe the error lies somewherewithin having an undefined initialState but I cant see what I have missed defining

3
I don't see repos defined anywhere in your reducer. Error is pretty clear to me, it tries to call map() on something repos which in not defined.Andrei Olar

3 Answers

1
votes

This code

const mapStateToProps = state => {
    let storedRepositories = state.repos.map(repo => ({ key: (repo.id).toString(), ...repo }));
    return {
        repos: storedRepositories
    };
};

means that you want transform (change id to key property) state.repos using map function and return as repos using intermediate variable storedRepositories. These parts looks as original, not changed from repos example.

Error appears because state doesn't contain repos array. You have a few other arrays in state (initialState in reducer) but none of them is repos.

In reducer you're storing payloads (fetched data) in store arrays, f.e.

    case GET_STOCK_LIST_SUCCESS:
        return { ...state, loadingStockList: false, stockList: action.payload.data };

stores data in stockList. For other succes actions you have other array names.

Component doesn't have to use all store data, you can use only parts it's interested in - this is a reason for mapStateToProps mapping. You can have it as simple as

const mapStateToProps = state => {
    return {
        stockList: state.stockList, 
        stock: state.stock
    };
};

These values will be available as this.props.stockList and this.props.stock.

0
votes

Seems like you're not defining repos , how do you want to map it ?

const initialState = {
  stockList: [],
  stock: {},
  result: {},
  barcodeList: [],
  stockFromBarcode: {},
  repos: []
};

Or alternativly , you meant another field .

0
votes

Because you're trying to use map function which works with an array but you repo which not array. There is a possibility that repo is not when you are mapping it (might be before assigning value).

Approach I just check where it is an Array using Array.isArray(), if Array then may else return empty array.

As @sagi mentioned, better approach initialize your state with repo field as an empty array. basically, this kind of error occurs when

you try to use array function to non-array element