I am dispatching action addProducts on every mount of the ProductList component whereas i want to dispatch the action one timeusing useEffect hook and store the data in the redux and then use it.
Below are my actions file and ProductList component file.
actions.js file
export const addProducts = () => async (dispatch) => {
let Products = await axios.get("https://api.npoint.io/2a4561b816e5b6d00894");
return dispatch({
type: ADD_PRODUCTS,
payload: Products.data,
});
};
ProductList.js component file
import { addProducts } from "../actions/Index";
const ProductList = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(addProducts());
},[]);
const Products = useSelector((state) => state.products);
console.log(Products)
You could just dispatch the action in the component but in the thunk action do nothing if products are available:
export const addProducts = () => async (
dispatch,
getState,//thunk also get a getState function
) => {
//you should write a dedicated selector function so you could do:
// const productsInState = selectProducts(getState())
const productsInState = getState().products
//whatever would make state.products available
// reducers are missing in your question
if(productsInState){
//do nothing if products are already in state
return;
}
let Products = await axios.get(
'https://api.npoint.io/2a4561b816e5b6d00894',
);
return dispatch({
type: ADD_PRODUCTS,
payload: Products.data,
});
};
In your component you can just dispatch on each render, if your page has multiple components dispatching this action then you could make a grouped action.
You want to check if products are already in redux with an if(!products){...} e.g.
const addProducts = () => async (dispatch) => {
let Products = await axios.get("https://api.npoint.io/2a4561b816e5b6d00894");
return dispatch({
type: ADD_PRODUCTS,
payload: Products.data,
});
};
const ProductList = () => {
const dispatch = useDispatch();
const products = useSelector((state) => state.products);
useEffect(() => {
if (!products) {
dispatch(addProducts());
}
},[dispatch]);
return <p>foo</p>
}
export const addProducts = () => async dispatch => {
dispatch({
type: ADD_PRODUCTS_START,
payload: { loading: true },
});
const Products = await axios.get(
'https://api.npoint.io/2a4561b816e5b6d00894'
);
dispatch({
type: ADD_PRODUCTS_SUCCESS,
payload: { products: Products.data, loading: false },
});
};
const ProductList = ({ products, loading }) => {
useEffect(() => {
if (!products && !loading) {
dispatch(addProducts());
}
}, []);
};
const mapStateToProps = ({ products: { data, loading } }) => ({ products: data, loading });
Related
I'm trying to display the response from the API into my react component but it's not working. If I try to use it in the console, I can see the data and its value but not in the react component, it's empty when I try to show the value in a div.
Here is the code where I'm trying to display it in my react component:
const CharacterListing = () => {
const characters = useSelector(getAllCharacters);
console.log("Hello", characters);
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
<div>{value.name}</div>
})
return (
<div>
{renderCharacters}
</div>
);
};
export default CharacterListing;
This is the code for my Character Slice Component
const initialState = {
characters: {},
};
const characterSlice = createSlice({
name: 'characters',
initialState,
reducers: {
addCharacters: (state, { payload }) => {
state.characters = payload;
},
},
});
export const { addCharacters } = characterSlice.actions;
export const getAllCharacters = (state) => state.characters.characters;
export default characterSlice.reducer;
This is the code for my Home Component:
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchCharacters = async () => {
const response = await baseURL.get(`/characters`)
.catch(error => {
console.log("Error", error);
});
dispatch(addCharacters(response.data));
console.log("Success", response);
};
fetchCharacters();
}, [])
return (
<div>
Home
<CharacterListing />
</div>
);
};
export default Home;
Thank you
You forgot to return item into your map func
Try this :
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
return <div key={key}>{value.name}</div>
})
I have a react component that has a html button that when clicked calls a function that adds an element to a redux reducer and then redirects to another component. The component that is redirected to needs to set state from the reducer but it won't. I know that it is being added to the array in the reducer because I wrote it as an async await and it redirects after it gets added.
This is the original component
const Posts = () => {
const dispatch = useDispatch();
const getProfile = async (member) => {
await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
window.location.href='/member'
console.log('----------- member------------')
console.log(post)
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default Posts;
This is the 'socialNetworkContract' reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
and this is the component that the html button redirects to
const Member = () => {
const [user, setUser] = useState({})
const [profile, setProfile] = useState({});
const dispatch = useDispatch();
useEffect(async()=>{
try {
const pro = socialNetworkContract.members[0];
setUser(pro)
const p = await incidentsInstance.usersProfile(user, { from: accounts[0] });
const a = await snInstance.getUsersPosts(user, { from: accounts[0] });
console.log(a)
setProfile(p)
} catch (e) {
console.error(e)
}
}, [])
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div class="container">
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
{p.message}
{p.replies}
</tr>})}
</div>
</div>
</div>
</div>
)
}
export default Member;
This is the error I get in the console
Error: invalid address (arg="user", coderType="address", value={})
The functions I'm calling are solidity smart contracts and the have been tested and are working and the element I'm trying to retrieve out of the array is an ethereum address.
incidentsInstance and snInstance are declared in the try statement but I took a lot of the code out to make it easier to understand.
given setUser is async, your user is still an empty object when you make your request.
you could pass pro value instead:
useEffect(async () => {
try {
const pro = socialNetworkContract.members[0];
setUser(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
const a = await snInstance.getUsersPosts(pro, { from: accounts[0] });
setProfile(p)
} catch (e) {
console.error(e)
}
}, [])
or break your useEffect in two pieces:
useEffect(() => {
setUser(socialNetworkContract.members[0]);
}, [])
useEffect(async () => {
if (!Object.keys(user).length) return;
try {
const p = await incidentsInstance.usersProfile(user, { from: accounts[0] });
const a = await snInstance.getUsersPosts(user, { from: accounts[0] });
console.log(a)
setProfile(p)
} catch (e) {
console.error(e)
}
}, [user])
note: fwiw, at first sight your user state looks redundant since it's derived from a calculated value.
I have created this custom hook to fetch data:
const useSuggestionsApi = () => {
const [data, setData] = useState({ suggestions: [] });
const [url, setUrl] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
const fetchData = () => {
setError(false);
setLoading(true);
if(url) {
fetch(url).then((res) => {
if (res.status !== 200) {
console.error(`It seems there was an problem fetching the result. Status Code: ${res.status}`)
return;
}
res.json().then((fetchedData) => {
setData(fetchedData)
})
}).catch(() => {
setError(true)
})
setLoading(false);
};
}
fetchData();
}, [url]);
return [{ data, loading, error }, setUrl];
}
export default useSuggestionsApi;
It used used in this component to render the response (suggestions).
const SearchSuggestions = ({ query, setQuery}) => {
const [{ data }, doFetch] = useSuggestionsApi();
const { suggestions } = data;
useEffect(() => {
const encodedURI = encodeURI(`http://localhost:3000/search?q=${query}`);
doFetch(encodedURI);
}, [doFetch, query]);
return (
<div className="search-suggestions__container">
<ul className="search-suggestions__list">
{suggestions.map((suggestion) => {
return (
<li className="search-suggestions__list-item" key={uuid()}>
<span>
{suggestion.searchterm}
</span>
</li>
)
})}
</ul>
</div>
);
};
export default SearchSuggestions;
Now I would like to write some unit test for the SearchSuggestions component but I am lost on how to mock the returned data from useSuggestionApi. I tried importing useSuggestionApi as a module and then mocking the response like this but with no success:
describe('SearchSuggestions', () => {
const wrapper = shallow(<SearchSuggestions/>)
it('test if correct amount of list-item elements are rendered', () => {
jest.mock("../hooks/useSuggestionsApi", () => ({
useSuggestionsApi: () => mockResponse
}));
expect(wrapper.find('.search-suggestions__list').children()).toHaveLength(mockResponse.data.suggestions.length);
});
})
I am new to testing React components so very grateful for any input!
This works:
jest.mock('../hooks/useSuggestionsApi', () => {
return jest.fn(() => [{data: mockResponse}, jest.fn()]
)
})
describe('SearchSuggestions', () => {
const wrapper = shallow(<SearchSuggestions query="jas"/>)
it('correct amount of list-items gets rendered according to fetched data', () => {
expect(wrapper.find('.search-suggestions__list').children()).toHaveLength(mockResponse.suggestions.length);
});
})
I'm learning Redux, and I am very confused about what is going on here. I am using thunk and GET_ITEMS is in my reducer so I'm not sure what I have done wrong? The error is in the dispatch(getItemsAction());
Redux.js
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) {
axios.get('api/items').then(response => {
console.log(response);
dispatch({ type: 'GET_ITEMS', payload: response.data });
});
},
});
ShoppingList.js
import { addItemAction, deleteItemAction, getItemsAction } from '../redux';
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(() => {
dispatch(getItemsAction());
}, []);
in the top code you returned the dispatch in incorrect way
but actually you need to call dispatch like cb
for example in javascript we do somthing like this
const myfunc = () => cb => {
cb('OK')
};
its callback in javascript and you have to return dispatch like callback to work correct
export const getItemsAction = () => dispatch => {
axios.get('api/items').then(response => {
dispatch({
type: 'GET_ITEMS',
payload: response.data
})
});
};
at the end dont forgot to get axios response data with response.data
the correct syntax for the action is
export const getItemsAction = () => dispatch => {
axios.get('/api/items').then(res =>
dispatch({
type: 'GET_ITEMS',
payload: res.data,
})
);
};
I have created a custom Hook which fetches data from the server, sends dispatch to the store and returns data. It is usable if I want to list all comments in my app, however, I wanted to reuse it in the component where I need to fetch all comment replies, and that should happen only when certain button is clicked.
This is the hook down below.
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
const useFetch = (url, options, actionType, dataType) => {
const [response, setResponse] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
(async () => {
const res = await fetch(url);
const json = await res.json();
setResponse(json);
})();
}, []);
useEffect(() => {
dispatch({ payload: response, type: actionType });
}, [response]);
const data = useSelector(state => state[dataType]);
return data;
};
export default useFetch;
Inside of my component I need to fetch replies when a button is clicked
const ParentComment = ({ comment }) => {
const handleShowMoreReplies = (e) => {
e.preventDefault();
}
let replies = useFetch(
`/api/comment_replies?comment_id=${comment.id}`,
null,
"REPLIES_RECEIVED",
"replies"
);
return (
<div>
<Comment comment={comment} />
<div className="replies">
{replies.map(reply => (
<Comment key={reply.id} comment={reply} />
))}
<a href="#" className="show_more" onClick={handleShowMoreReplies}>
Show More Replies ({comment.replies_count - 1})
</a>
</div>
</div>
);
};
If I put useFetch call inside of the handler I hget an error that Hooks can't be called there, but I need to call it only when the button is clicked so I don't know if there is a way to implement that.
I think you have subtle problems in your useFetch hook
1.your useEffect is having dep of ${url} and ${actionType } which you need to define.
2.In order to call this hook by clicking the button, you need to expose the setUrl as follows
const useFetch = ( initialUrl, options, actionType, dataType) => {
const [url, setUrl ] = useState(initialUrl);
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
const data = await res.json();
dispatch({ payload: data, type: actionType });
} catch (error) {
console.log(error);
}
};
fetchData();
}, [url, actionType]);
const data = useSelector(state => state[dataType]);
return [ data, setUrl ];
};
export default useFetch;
Then when you are trying to use this hook, you can
const [data, fetchUrl] = useFetch(
`/api/comment_replies?comment_id=${comment.id}`,
null,
"REPLIES_RECEIVED",
"replies"
);
Then every time you have a button you can simply call
fetchUrl(${yourUrl}).
your hook will receive the new URL, which is the dep of your hook and rerender it.
Here is an related article
https://www.robinwieruch.de/react-hooks-fetch-data