So I just started learning hooks and I'm trying to use Redux with them. Ive been trying redux new api but now I can't seem to get the data that I want. When I do console.log() I see the promise getting resolved and inside the [[PromiseValue]] I see the data but how do I get it out of there into my Component.
const Main = ({props}) => {
const [cloth,setCloth]=useState([])
const items = useSelector((state) => state);
const dispatch = useDispatch()
let getItems = getAllItems(
() => dispatch({ type: 'GET_ITEMS' }),
[dispatch]
)
console.log(getItems)
here is the pic of my console.log()
first to use redux with async actions you need to use redux-thunk middleware:
npm i redux-thunk
and then use it like so:
import { createStore, applyMiddleware } from 'redux';
const store = createStore(rootReducer, applyMiddleware(thunk));
now for the actions here a simple example:
first action to fetch Items and then action to set Items in store:
//actions.js
const setItems = (payload)=>({type:SET_ITEMS,payload})
export const fetchItem = ()=>{
return dispatch =>{
axios.get("/items").then(response=>
dispatch(setItems(response.data))).catch(error=>throw error)
}
}
//reducer.js
const state = {items:[]}
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case "SET_ITEMS":
return {
...state,
items: payload
};
default:
return state;
}
}
now from react component we fetch items on mount :
import React,{useEffect} from "react"
import {useDispatch,useSelector} from "react-redux"
import {fetchItems} from "action.js"
const Items = ()=>{
const items= useSelector(state=>state.items)
const dispatch = useDispatch()
useEffect(()=> dispatch(fetchItems()),[])
return items?items.map(item =><p key={item.name}>{item.name}</p>):<p>items not available</p>
hope this what you are looking for.
Related
I have a redux toolkit slice as follow
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
value: null,
};
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setAuth: (state, action) => {
state.value = action.payload;
},
},
});
export const { setAuth } = authSlice.actions;
export default authSlice.reducer;
I can use useDispatch in function component as follow to setAuth
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth.value);
const setAuthValue = (value) => {
dispatch(setAuth(value));
};
However, I have a scenario where I am not able to useDispatch hooks for this. (eg: inside axios interceptor)
Is that possible that I directly use store.dispatch({'type': '', payload: {}} like normal redux with above redux toolkit slice?
Or what is the better approach for it?
I had the same question and just got it working. In the plain js file (outside a component function context), just import your setAuth action that you're exporting from your slice. Then, you can dispatch it like so: store.dispatch(setAuth(payload))
This is what useDispatch function in react-redux library:
function useDispatch() {
const store = useStore()
return store.dispatch
}
You can export the store from store.js and use store.dispatch
I fetched some data from my api by react-redux. My problem is that, since it is async I have to wait for the state to update its inital value in order to use them in the app. For example I have to use
products && products.length && products[n].img
syntax not to get undefined error when I try to access the fetched data. But when I use them at the first render just as
products[n].img
the app gives undefined as it should because redux fetches the data asynchronously. How can I bypass these steps so that I can use my desired state immediately?
React code
import React, { useEffect } from "react";
import {useDispatch, useSelector} from 'react-redux'
import { listPoduct } from "../actions/productActions";
const Examples = () => {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const {loading, error, products} = productList
useEffect(()=>{
dispatch(listPoduct())
},[dispatch])
console.log(products && products.length && products[0].img)
return(
<div>
...
</div>
)
}
export default Examples
Action
export function listPoduct() {
return (dispatch) => {
const baseUrl = "/api/images"
fetch(`${baseUrl}`)
.then(res => res.json())
.then(res => {
dispatch({
type: PRODUCT_LIST_SUCCESS,
payload: res
})
})
}
}
Reducer
export const productListReducer = (state = { products: [] }, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return {loading:true, products:[]}
case PRODUCT_LIST_SUCCESS:
return {loading:false, products: action.payload}
case PRODUCT_LIST_FAIL:
return {loading:false, error: action.payload}
default:
return state
}
}
Store
import {createStore, combineReducers, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import {productListReducer, productDetailsReducer} from './reducers/productReducer'
const reducer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer
})
const initialState = {}
const middleware = [thunk]
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
export default store
The short answer is that you cannot. Sadly.
Your request is asynchronous, so there's just no data available immediately.
In your particular case, my advice would be to render some kind of spinner-loader conditionally (if loading is set to true) and only the loader.
In this case, if you have loading set to true, you will not reach the place where you can actually read the data (you will render-and-return before). And once loading switches back to false, you can now display the data safely as the request is finished and the data is in the right place.
The same applies to the failure state (as there's also no data available if the request failed).
Here's your modified code (as an example):
const Examples = () => {
const dispatch = useDispatch()
const productList = useSelector(state => state.productList)
const {loading, error, products} = productList
useEffect(()=>{
dispatch(listPoduct())
},[dispatch]);
if (loading) {
return (<div>Loading...</div>);
}
if (error) {
return (<div>Error: {error}</div>);
}
console.log(products && products.length && products[0].img)
return(
<div>
...
</div>
)
}
I'm struggling to understand how I can get around this issue?
I want to get data that I can share globally using redux**(using redux as I'm using it for other use cases in my app)**. my problem is I'm using getStaticProps to try and dispatch my ReduxThunk but I can't use Hooks inside getStaticProps and I have no idea what the workaround would be if anyone could point me to some docs I would appreciate it
Slice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchData = createAsyncThunk(
"fetchCoinData",
async (url, thunkApi) => {
const data = await fetch(url).then((res) => res.json());
return data;
}
);
const initialState = {
data: [],
status: null,
};
const getData = {};
export const dataSlice = createSlice({
name: "datafetch",
initialState,
extraReducers: {
[getData.pending]: (state) => {
state.status = "Loading!";
},
[getData.fulfilled]: (state, { payload }) => {
state.data = payload;
state.status = "Sucsess!";
},
[getData.rejected]: () => {
state.status = "Failed";
},
},
});
// Action creators are generated for each case reducer function
export const {} = dataSlice.actions;
export default dataSlice.reducer;
cardano.js
import React from "react";
import { useDispatch } from "react-redux";
import BasicCard from "../../Components/UI/Cards/BasicCard";
import { UsersIcon } from "#heroicons/react/outline";
import { fetchData } from "../../redux/slice/DataSlice";
const cardano = (props) => {
return (
<div>
<h1>Header</h1>
</div>
);
};
//PROBLEM IS HERE
export async function getStaticProps(context) {
const dispatch = useDispatch();
const priceQuery =
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin%2Ccardano%2Cethereum&vs_currencies=USD";
const res = await dispatch(fetchData(priceQuery));
return {
props: {
theData: res,
}, // will be passed to the page component as props
};
}
export default cardano;
To use Hooks, it has to be located at the highest level. Another alternative that you may try is the lifecycle of React. They are the same as Hooks.
For something like this, you probably want to use https://github.com/kirill-konshin/next-redux-wrapper .
Generally you have to be aware though: just putting something in the global state during SSR does not mean your client has it - your client will have it's own Redux state, and each page that is server-side-rendered also has their own isolated Redux state and you will think about what of that you will "hydrate" from the server to the client to make it available there.
The docs of next-redux-wrapper go more into this than I could possibly explain myself, so give those a read!
I want to navigate to App screen or Auth screen, depending on the isUser prop after fetching it from the server and updating the redux store.
My first component AuthLoading.js which looks like this:
const AuthLoading = (props) => {
const isUser = useSelector((state) => state.authReducer.isUserExists);
const dispatch = useDispatch();
const fetchData = async () => {
const token = await TokensHandler.getTokenFromDevice();
dispatch(isTokenExists(token));
props.navigation.navigate(isUser ? "App" : "Auth");
};
useEffect(() => {
fetchData();
}, []);
My authActions.js looks like this:
export const isTokenExists = (token) => {
return (dispatch) => {
return HttpClient.get(ApiConfig.IDENTITY_PORT, "api/identity", {
userId: token,
}).then((response) => {
console.log(response);
dispatch({
type: IS_USER_EXISTS,
payload: response,
});
});
};
};
My authReducer.js looks like this:
const authReducer = (state = initialState, action) => {
switch (action.type) {
case IS_USER_EXISTS:
return {
...state,
isUserExists: action.payload,
};
default:
return state;
}
};
And the store:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../reducers";
const configureStore = () => {
return createStore(rootReducer, applyMiddleware(thunk));
};
export default configureStore;
Unfortunately, the code inside AuthLoading.js isn't asynchronous, and isn't waiting for the updated isUser value and running the next line without it.
I've tried to .then after dispatch which still doesn't work.
I've tried changing async states and still couldn't find the problem.
I have no idea how to fix this.
Thanks for the help.
You can use another useEffect hook that runs when isUser changes and navigate inside it.
This useEffect runs once the component mounts and every time isUser changes, so someCondition can be anything that determines whether the navigation should happen or not.
useEffect(() => {
if(someCondition) {
props.navigation.navigate(isUser ? "App" : "Auth");
}
}, [isUser]); // add all required dependencies
I am working on a react application using redux with hooks.
Here is my action creator below
PostAction
***********
import * as types from "./actionTypes";
import axios from 'axios';
const ROOT_URL = 'http://dotsuper.com/api'
export function fetchPosts(){
const request = axios.get(`${ROOT_URL}/post/getposts`)
return {
type: types.GETALL_POSTS,
payload: request
}
}
Here is my reducer below
PostReducer
************
import _ from 'lodash';
import * as types from "../actions/actionTypes";
export default function postReducer(state = [], action) {
switch (action.type) {
case types.GETALL_POSTS:
debugger;
console.log(action.payload.data);
return _.mapKeys(action.payload.data, 'id');
default:
return state;
}
}
Here is what my store configuration looks like
configureStore
***************
import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "./reducers";
import reduxImmutableStateInvariant from "redux-immutable-state-invariant";
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; //add support for redux dev tools.
return createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(thunk, reduxImmutableStateInvariant()))
);
}
Here is what my component looks like.
My question is when I look at my devtools, the state for posts is
completely empty. When I set a debugger, my PostReducer is not getting hit. I think I am still
missing something. I don't think you can use connect with hooks. What do I need to do below
to have data in my state and be able to hit my post reducer?
PostPage
**************
import React, { useState, useEffect } from "react";
import {fetchPosts} from "../../redux/actions/postActions";
const PostsPage = () => {
const [getPosts, setGetPosts] = useState([]);
async function fecthData(){
const res = fetchPosts()
}
useEffect( () => {
fecthData();
},[]);
return (
<div>
<h2>Posts</h2>
<p>
This page is for all the posts.
</p>
</div>
);
}
export default PostsPage;
You're calling the action generator fetchPosts() inside your component, but you actually never dispatch any change into your state. If you look closely you'll see that you're fetchPosts() returns an object commonly known as actions:
{
type: types.GETALL_POSTS,
payload: request
}
So basically when you call the fetchPosts, you fetch something and you return this object. No touching to the Redux state so far
In the next step you should actually take this object and dispatch it to your store, like this:
const action = await fetchPosts();
dispatch(action);
Which when you use connect with mapDispatchToProps the connect will take care of it for you.
Check here to get a better grasp of the concept.
When using with hooks however, you can import these from react-redux:
useDispatch instead of mapDispatchToProps and,
useSelector instead of mapStateToProps
import {useDispatch, useSelector} from 'react-redux';
import myAction from 'path/to/my/action';
const MyComponent = (props) => {
const myState = useSelector(state => state.myState);
const dispatch = useDispatch();
const handleClick = () => {
dispatch(myAction());
}
return (
...
)
}
Check inside fetchPosts method, axios.get returns promise. you need to make
the method async and handle async data.
You need to dispatch the action in order to bind the action with the redux state.
// PostAction
import * as types from "./actionTypes";
import axios from 'axios';
const ROOT_URL = 'http://dotsuper.com/api'
export function fetchPosts(){
return async (dispatch) => {
const request = await axios.get(`${ROOT_URL}/post/getposts`); // returns promise.
dispatch({
type: types.GETALL_POSTS,
payload: request
});
}
}
// PostPage
import React, { useState, useEffect } from "react";
import {useDispatch} from "react-redux";
import {fetchPosts} from "../../redux/actions/postActions";
const PostsPage = () => {
const [getPosts, setGetPosts] = useState([]);
const dispatch = useDispatch();
useEffect( () => {
dispatch(fetchPosts());
},[]);
return (
<div>
<h2>Posts</h2>
<p>
This page is for all the posts.
</p>
</div>
);
}
export default PostsPage;