Reputation: 125
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)}
>
×
</button>
{name}
</li>
</CSSTransition>
))}
</TransitionGroup>
</ul>
</div>
);
}
Upvotes: 4
Views: 1829
Reputation: 1939
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.
Upvotes: 2