Currently, I'm using functional components with hooks but still dispatching my actions with the connect HOC.
I read through the documentation with useDispatch but I'm unsure how to incorporate it in my code. From the examples, they are passing the the action types and payloads inside the component. Would I have to move myOfferActions functions back to the component in order to useDispatch?
MyOffers component
import React, { useEffect } from "react";
import { connect, useSelector } from "react-redux";
import "./MyOffers.scss";
import MyOfferCard from "../../components/MyOfferCard/MyOfferCard";
import { fetchMyOffers } from "../../store/actions/myOffersActions";
const MyOffers = (props) => {
const myOffers = useSelector((state) => state.myOffers.myOffers);
useEffect(() => {
props.fetchMyOffers();
}, []);
return (
<div className="my-offers-main">
<h1>My offers</h1>
{myOffers && (
<div className="my-offer-container">
{myOffers.map((offer) => (
<MyOfferCard key={offer.id} offer={offer} />
))}
</div>
)}
</div>
);
};
export default connect(null, { fetchMyOffers })(MyOffers);
offerActions
export const fetchMyOffers = () => async (dispatch) => {
const userId = localStorage.getItem("userId");
try {
const result = await axiosWithAuth().get(`/offers/${userId}`);
let updatedData = result.data.map((offer) => {
//doing some stuff
};
});
dispatch(updateAction(FETCH_MY_OFFERS, updatedData));
} catch (error) {
console.log(error);
}
};
offerReducer
import * as types from "../actions/myOffersActions";
const initialState = {
offerForm: {},
myOffers: [],
};
function myOffersReducer(state = initialState, action) {
switch (action.type) {
case types.FETCH_MY_OFFERS:
return {
...state,
myOffers: action.payload,
};
default:
return state;
}
}
export default myOffersReducer;
I don't think you need connect when using the redux hooks.
You just need to call useDispatch like:
const dispatch = useDispatch();
And use the function by providing the object identifying the action:
dispatch({ type: 'SOME_ACTION', payload: 'my payload'});
It should be working with redux-thunk too (I guess this is what you're using): dispatch(fetchMyOffers())
Related
I am new to this so please bear with me, I am trying to pass the products & the empty cart from (Context.js) to the (cartState) in apps.js file, through useReducer as intructed in minute 20 of this guide, the only difference is that he is using a faker to generate the Api and I am pulling data with Axios in a useEffect from Api with 13 products
I tried in many ways to receive the items inside the products array but always receiving an empty array in console log of the (carState) in App.js File, all help is welcome appreciated ! Github Repo
Context.js
import { createContext, useState, useEffect, useReducer, useContext } from 'react';
import { cartReducer } from './Reducers';
import { ApiServer } from '../services/Api/index'
const Cart = createContext();
const Context = ({ children }) => {
const [products, setProducts] = useState([]);
useEffect(() => {
ApiServer.get('/products/')
.then(response => {
if (response.data != null) {
setProducts(response.data);
} else {
console.log('O Array esta vazio')
}
})
.catch(error => {
console.log(error);
});
}, []);
console.log(products)
const [state, dispatch] = useReducer(cartReducer, {
products: products,
cart: [],
});
return <Cart.Provider value={{ state, dispatch }}> {children}</Cart.Provider>;
}
export default Context
export const CartState = () => {
return useContext(Cart);
};
Reducer.js
export const cartReducer = (state, action) => {
switch (action.type) {
default:
return state;
}
};
App.js
import React from 'react';
import Router from './routes';
import GlobalStyle from './assets/styles/globalStyles';
import ScrollToTop from './components/ScrollToTop';
import { BrowserRouter } from 'react-router-dom';
import { ContentProvider } from './useContext';
import { CartState } from './contexts/Context';
function App() {
const { state } = CartState();
console.log(state);
return (
<BrowserRouter>
<ContentProvider>
<GlobalStyle />
<Router />
<ScrollToTop />
</ContentProvider>
</BrowserRouter>
);
}
export default App;
What you are trying to do here will not work. The initial state of the reducer is just that -- the INITIAL state. It will use the value of initialState from the very first render, before your items has been updated with the fetch results. Changes to the variable initialState will not effect the reducer after it has already been created. You have to dispatch an action to store the items in your reducer. source
Solution:
As you are using useReducer() for state handling you don't need to use useState()
Write your useReducer like:
const [state, dispatch] = useReducer(reducer, {
products: [],
cart: []
});
Reducer function will be like:
export const reducer = (state, action) => {
switch (action.type) {
case "SET_PRODUCTS_DATA":
return { ...state, products: action.payload };
default:
return state;
}
}
Set product data from useEffect using dispatch like:
useEffect(() => {
ApiServer.get("/products/")
.then((response) => {
if (response.data != null) {
dispatch({ type: "SET_PRODUCTS_DATA", payload: response.data });
} else {
console.log("O Array esta vazio");
}
})
.catch((error) => {
console.log(error);
});
}, []);
It should work perfectly. Demo codesandbox Link
im new to the redux toolkit here is my problem
im fetching data every time by creating own custom useFetch file for handling loading , success, error status
i used the same way with redux toolkit by creating createSlice method for accesing reducers and actions
this is working successfully and getting data from this way by dispaching actions and reducers
but i did't used the createAsyncThunk from redux toolkit
my confusion is is this currect way to fetch data from custom useFetch or should i use createAsyncthunk
Im not sure how to use createAsyncThunk in custom useFetch
if anyone knows the answer that is so appreciatable
posted my all files below
if i get answer with createAsyncThunk in custom useFetch that is soo appreciable
thanks advance
App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import useFetchData from "./Components/useFetchData";
import { actions } from "./Slices/CounterSlice";
const App = () => {
useFetchData("https://jsonplaceholder.typicode.com/todos");
const apiData = useSelector((s) => s);
console.log(apiData.api);
return (
<>
{apiData.api.status !== "success" && <h1>hello</h1>}
{apiData.api.status === "success" &&
apiData.api.apiData.map((el) => {
return <h6 key={el.id}>{el.title}</h6>;
})}
</>
);
};
export default App;
custom useFetchData.js file
import React, { useCallback } from "react";
import { useDispatch } from "react-redux";
import { ApiActions } from "../Slices/ApiSlice";
const useFetchData = (link) => {
const dispatch = useDispatch();
const fetchData = useCallback(async () => {
try {
dispatch(ApiActions.loadingTime());
const getData = await fetch(link);
const toJson = await getData.json();
dispatch(ApiActions.successTime(toJson));
} catch {
dispatch(ApiActions.errorTime());
}
}, [link]);
React.useEffect(() => {
fetchData();
}, [fetchData]);
};
export default useFetchData;
this is createSlice file for creating actions and reducers
import { createSlice } from "#reduxjs/toolkit";
const apiSlice = createSlice({
name: "api",
initialState: {
status: "idle",
apiData: [],
error: false,
},
reducers: {
loadingTime: (state, action) => {
state.status = "Loading";
},
successTime: (state, action) => {
state.apiData = action.payload;
state.status = "success";
},
errorTime: (state, action) => {
state.apiData = [];
state.status = "error";
},
},
});
export const ApiActions = apiSlice.actions;
export const apiReducers = apiSlice.reducer;
You can use one way or the other. Example with createAsyncThunk:
export const checkIfAuthenticated = createAsyncThunk(
"auth/checkIsAuth",
async (_, thunkAPI) => {
const response = await axios.post(
"http://127.0.0.1:8080/dj-rest-auth/token/verify/",
{ token: localStorage.getItem("access") }
);
thunkAPI.dispatch(getUserInfo());
return response.data;
}
);
Then you handle loading, success, error status as before:
extraReducers: (builder) => {
builder
.addCase(checkIfAuthenticated.fulfilled, (state, action) => {
state.isAuthenticated = true;
})
.addCase(checkIfAuthenticated.rejected, (state, action) => {
state.isAuthenticated = false;
snackbar({ error: "Nie jesteÅ› zalogowany" });
})
},
However, if you have a lot of API queries, the optimal solution is to use redux toolkit query or react-query. These two libraries make queries a lot better.
If you care about code cleanliness and ease, check them out.
Need help, just started to learn React. I'm trying to pass variables with json data to a component for further use, but catching the errors. what should I change to use variables with json() data from Store.js in the product.js component? THanks for your time!
https://jsfiddle.net/constant101/xu7zdn26/3/ for better visibility
//Store export(receiving data from the server and assigning them to variables)
import React, {useState, useEffect} from 'react'
import axios from 'axios'
export const ListContext = React.createContext([]);
export const ItemContext = React.createContext([]);
function Store() {
const [storeProducts, setStoreProducts] = useState([]);
const [detailProduct, setDetailProduct] = useState([]);
useEffect(() => {
axios.get('/products/')
.then(res => {
console.log(res)
setStoreProducts(res.data)
})
},[])
console.log('storeProducts:', storeProducts)
useEffect(() => {
axios.get('/products/:productId')
.then(res => {
console.log(res)
setDetailProduct(res.data)
})
},[])
console.log('detail product:', detailProduct)
return (
<ListContext.Provider value={[storeProducts, setStoreProducts]}>
<ItemContext.Provider value={[detailProduct, setDetailProduct]}>
<product/>
</ItemContext.Provider>
</ListContext.Provider>
);
}
export const detailProduct
//product.js ( file that uses data from the fetch)
import React, { useReducer, createContext, useContext, useState } from 'react';
import {ListContext, ItemContext } from '../Store';
import { useProductActions } from '../actions';
import { SET_PRODUCT_DETAILS } from '../actions/types';
const [storeProducts] = useContext(ListContext);
const [detailProduct] = useContext(ItemContext);
let tempProducts = [];
storeProducts.forEach(item => tempProducts.push({ ...item })
);
const initialState = {
products: tempProducts,
productDetails: { ...detailProduct }
};
console.log(storeProducts)
const productReducer = (state, action) => {
switch (action.type) {
case SET_PRODUCT_DETAILS:
return {
...state,
productDetails: action.payload
};
default:
throw new Error('Invalid action type');
}
};
export const ProductContext = createContext(initialState);
export const useProductState = () => {
return useContext(ProductContext);
};
export const ProductProvider = ({ children }) => {
const [state, dispatch] = useReducer(productReducer, initialState);
const productActions = useProductActions(state, dispatch);
return (
<ProductContext.Provider value={{ productState: state, productActions }}>
{children}
</ProductContext.Provider>
);
};
Well, assuming your request is right, i saw a syntax mistake. You should pass
<ListContext.Provider value={{storeProducts, setStoreProducts}}> instead of
<ListContext.Provider value={[storeProducts, setStoreProducts]}>
The reason:
a provider requires a prop called value with an Object inside.
In that case, you were passing an array.
it would be the same if you did:
<ListContext.Provider
value={{
storeProducts: storeProducts,
setStoreProducts: setStoreProducts
}}
>
but to follow the DRY principle, it's recommended to do that way described earlier
I want to use redux hook useSelector to access the store and get rid of connect(), so I need to create a way to export my actions, and I'm thinking on a class with static methods, here is an example
export default class AuthActions {
static async login(userData) {
try {
const user = await axios.post('http://localhost:5000', userData);
dispatch({
type: AUTH.LOGIN,
payload: user.data
})
} catch (error) {
dispatch({
type: SET_ERROR,
payload: error
})
}
}
static setUser() {
console.log("SET USER")
}
static logout() {
console.log("Logout")
}
}
And then I use the action methods as follows:
import React from 'react';
import AuthActions from '../../redux/actions/AuthActions';
import { useSelector } from 'react-redux';
export default const Login = () => {
//More logic....
const { isAuth } = useSelector((state) => state.auth);
const submitHandler = e => {
e.preventDefault();
AuthActions.login(userData)
}
return (
<form onSubmit={submitHandler}>
My Login form ....
</form>
);
};
But I'm wondering if there is a disadvantage or performance issues to use redux in this way, or should I avoid the usage of a class and use a simple object instead?
Thank you in advance
this is my format of a reducer, inspired by ducks-modular-redux
for example, check out this darkMode reducer:
export const constants = {
TOGGLE: "darkMode/TOGGLE"
};
export const actions = {
toggleDarkMode: () => {
return {
type: constants.TOGGLE
};
}
};
export const thunks = {
toggleDarkMode: () => {
return (dispatch, getState) => {
dispatch(actions.toggleDarkMode());
const isDark = getState().darkMode.isDark;
localStorage.setItem("isDark", isDark);
};
}
};
const initialState = { isDark: localStorage.getItem("isDark") === "true" };
export default (state = initialState, action) => {
switch (action.type) {
case constants.TOGGLE:
return {
isDark: !state.isDark
};
default:
return state;
}
};
I am trying to use React context as a state manager in my React Native app.
Here's the context:
import React, { createContext, useState, useEffect } from "react";
import axios from "axios";
export const GlobalContext = createContext();
export const Provider = ({ children }) => {
const [tracksList, setTracksList] = useState([
{
track_list: []
}
]);
useEffect(() => {
axios
.get(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?page=1&page_size=10&country=us&f_has_lyrics=1&apikey=${
process.env.REACT_APP_MM_KEY
}`
)
.then(res => {
setTracksList([
{
track_list: res.data.message.body.track_list
}
]);
})
.catch(err => console.log(err));
}, []);
return (
<GlobalContext.Provider value={[tracksList, setTracksList]}>
{children}
</GlobalContext.Provider>
);
};
export const Consumer = GlobalContext.Consumer;
Child component. Here I'd like to make an API call to get users and set this users field to global context. I can get context value from consumer, but how to set the new one?
import React, { useContext } from "react";
import { GlobalContext } from "../../context/context";
const Demo = () => {
const contextValue = useContext(GlobalContext);
console.log(contextValue, "Context outside from JSX");
return <div>Content</div>;
};
export default Demo;
So, is it possible to add new value to React context from every child component, like in Redux? Thanks in advance!
You could use the useReducer effect to achieve Redux reducers:
// Create context
export const ApiContext = React.createContext();
// Create some reducer function
const reducer = (state, action) => {
if (action.type === 'some action name') {
return {
...state,
report: action.payload,
};
}
return state;
};
// Overwrite a context provider
const Provider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, {});
return (
<ApiContext.Provider
value={{
...state,
dispatch,
}}
>
{children}
</ApiContext.Provider>
);
};
Then you could use in components as follows:
const Component = () => {
const { dispatch, report } = React.useContext(ApiContext);
React.useEffect(() => {
const asyncPost = async () => {
const response = await fetch('some endpoint', {
method: 'POST',
});
const payload = await response.json();
// This will call a reducer action and update a state
dispatch({
type: 'some action name',
payload,
});
}
}, []);
...
};
So when Component is mounted, the state would be an empty object. Then when you update the state using the some action name action, the state becomes { report: some data from fetch }.