4

I am learning Redux so this may be a basic question and answer but here we go.

I am trying to fetch a list of items from my backend api (/api/items) and pass them into my ShoppingList.js component so that when the component loads the list of items will be displayed. I think that I have to fetch the data in the action, and then call that action in the useEffect hook in the react component, but I am not too sure as I can't seem to get it to work.

Can anyone help me figure this out?

Redux.js

import { createStore } from 'redux';
import axios from 'axios';

const initialState = {
    items: [],
    loading: false,
};

export const store = createStore(
    reducer,
    initialState,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

function reducer(state, action) {
    switch (action.type) {
        case 'GET_ITEMS':
            return {
                ...state,
                items: action.payload,
                loading: false,
            };
        case 'ADD_ITEM':
            return {
                ...state,
                items: [...state.items, action.payload],
            };
        case 'DELETE_ITEM':
            return {
                ...state,
                items: state.items.filter(item => item.id !== action.payload),
            };
        case 'ITEMS_LOADING':
            return {
                ...this.state,
                loading: true,
            };
        default:
            return state;
    }
}

export const getItemsAction = () => ({
    return(dispatch) {
    console.log('here');
    axios.get('api/items').then(response => {
        console.log(response);
        dispatch({ type: 'GET_ITEMS', payload: response.data });
    });
},
});

export const addItemAction = item => ({
    type: 'ADD_ITEM',
    payload: item,
});

export const deleteItemAction = item => ({
    type: 'DELETE_ITEM',
    payload: item,
});

export const setItemsLoading = () => ({
    type: 'ITEMS_LOADING',
});

ShoppingList.js

export default function ShoppingList() {
    const items = useSelector(state => state.items);

    const dispatch = useDispatch();
    const addItem = name => dispatch(addItemAction(name));
    const deleteItem = id => dispatch(deleteItemAction(id));

    useEffect(() => {
        //call get items dispatch?
        getItemsAction();
    });

    return (
        <div className="container mx-auto">
            <button
                onClick={() => {
                    const name = prompt('Enter Item');
                    if (name) {
                        // setItems([...items, { id: uuid(), name: name }]);
                        addItem({
                            id: uuid(),
                            name: name,
                        });
                    }
                }}
                className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-4"
            >
                Add Item
            </button>

            <ul className="mt-4">
                <TransitionGroup className="shopping-list">
                    {items.map(({ id, name }) => (
                        <CSSTransition
                            key={id}
                            timeout={500}
                            classNames="fade"
                            style={{ marginBottom: '0.5rem' }}
                        >
                            <li>
                                {' '}
                                <button
                                    className="bg-red-500 rounded px-2 mr-2 text-white"
                                    onClick={deleteItem.bind(this, id)}
                                >
                                    &times;
                                </button>
                                {name}
                            </li>
                        </CSSTransition>
                    ))}
                </TransitionGroup>
            </ul>
        </div>
    );
}

1 Answer 1

2

You are close to where you need to be, the missing piece is that redux is synchronous, so you need to use something like redux-thunk or redux-saga to handle async actions such as network requests.

Once you have setup whatever library you want, you would call it similarly to calling a redux action, from a useEffect like you suggest.

So for example, if you use a thunk:

export const getItemsAction = () => ({
    return (dispatch) => {
        axios.get('api/items').then(response => {
            console.log(response)
            dispatch({ type: 'GET_ITEMS_SUCCESS', payload: response.data })
        }).catch(err => dispatch({ type: 'GET_ITEMS_ERROR', error: err })
    },
})

Then you could call it from your effect:

    useEffect(() => {
        dispatch(getItemsAction())
    }, []);

Make sure you add an empty dependency array if you only want to call it once, otherwise it will get called every time your component refreshes.

Note that the thunk is able to dispatch other redux actions, and I changed a few of your actions types to make it more clear what is going on; GET_ITEMS_SUCCESS is setting your successful response & GET_ITEMS_ERROR sets any error if there is one.

Sign up to request clarification or add additional context in comments.

5 Comments

so would that look something like this? export const getItemsAction = () => ({ return(dispatch) { axios.get('api/items').then(response => { console.log(response); dispatch({ type: 'GET_ITEMS', payload: response.data }); }); }, });
Yeah, I edited my answer to help explain how I would do it with thunks
Okay thank you, I have made the changes you suggested but now i get an Actions may not have an undefined "type" property. error in the useEffect hook. Any idea why that is happening? The type is set as 'GET_ITEMS' with the case for 'GET_ITEMS' in my reducer.
You need to make sure any actions types dispatched by the thunk are in your reducer, so make sure GET_ITEMS_SUCCESS and GET_ITEMS_ERROR are in your reducer. GET_ITEMS itself is not needed
Right, the GET_ITEMS_SUCCESS and GET_ITEMS_ERROR are in my reducer but I still get the same error message at dispatch(getItemsAction());

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.