I am learning how to create a React reducer to be used in a React context.
Can someone explain why the instructor created the {currentUser: null} object twice? Once in the context (line 7), once as an INITIAL_STATE to be used in the reducer (lines 26 and 29). Is any of it redundant?
import { createContext, useReducer, useEffect } from "react";
import {
onAuthStateChangedListener,
createUserDocumentFromAuth,
} from "../utils/firebase/firebase.utils";
export const UserContext = createContext({
currentUser: null,
setCurrentUser: () => null,
});
export const USER_ACTION_TYPES = {
SET_CURRENT_USER: "SET_CURRENT_USER",
};
const userReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "SET_CURRENT_USER":
return {
...state,
currentUser: payload,
};
default:
throw new Error(`Unhandled type ${type} in userReducer`);
}
};
const INITIAL_STATE = {
currentUser: null,
};
export const UserProvider = ({ children }) => {
const [state, dispatch] = useReducer(userReducer, INITIAL_STATE);
const { currentUser } = state;
const setCurrentUser = (user) => {
dispatch({ type: USER_ACTION_TYPES.SET_CURRENT_USER, payload: user });
};
const value = { currentUser, setCurrentUser };
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((user) => {
if (user) {
createUserDocumentFromAuth(user);
}
setCurrentUser(user);
});
return unsubscribe;
}, []);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
Thanks!
No none is redundant. The currentUser property in the INITIAL_STATE object is the initial state for your useReducer hook while the other currentUser is the initial state for your useContext hook.
Related
useReducer inside context provider is only returning the initial state instead of updated state after dispatching and running reducer function.
When I console log the action inside reducer function, developer console logs the action object that I passed but it is not updated in the return of if statement.
GlobalContext.js
import { createContext } from 'react';
const GlobalContext = createContext({
auth: {
user: Object,
token: String,
addUserData: Function,
},
});
export default GlobalContext;
GlobalProvider.js
import { useReducer } from 'react';
import GlobalContext from './GlobalContext';
const initState = {
auth: {
user: {},
token: null,
},
};
const reducer = (state, action) => {
if (action.type === 'ADD_USER_DATA') {
console.log(action);
return {
...state,
auth: {
user: JSON.parse(action.payload.user),
token: action.payload.token,
},
};
}
};
const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initState);
const value = {
auth: {
user: state.auth.user,
token: state.auth.token,
addUserData: (user, token) => {
dispatch({ type: 'ADD_USER_DATA', payload: { user, token } });
},
},
};
return (
<GlobalContext.Provider value={value}>{children}</GlobalContext.Provider>
);
};
export default GlobalProvider;
I'm working on a react project. I have my own API to fetch information. I'm using the useEffect hook to fetch profile information from API. My problem is when page mounts for the first time i can fetch the data with no problem but if i refresh the page it doesn't work at all. I know i have to give a second parameter to useEffect. I tried to put profile as the second argument even dispatched the getCurrentProfile function but when i do that it constantly fires off fetch request. I would be glad if anyone can help me with that. Thanks.
Here is my Profile component:
export const Profile = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getCurrentProfile());
}, [])
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const { user } = authReducer;
console.log("loading", loading)
console.log("profile", profile)
return loading && profile === null ? (
<div >
<Spinner />
</div>
) :
Here is my Profile action:
export const getCurrentProfile = () => async dispatch => {
try {
const res = await axios.get("/api/profile/me");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
})
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
})
}
}
Here is my profile reducer:
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
}
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
}
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
}
default:
return state;
}
}
You might want to try adding conditional logic within the useEffect so you only trigger the dispatch if you don't already have a profile.
import "./styles.css";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useCallback } from "react";
import { getCurrentProfile } from "./action";
export const Profile = () => {
const dispatch = useDispatch();
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
// read more about this here: https://stackoverflow.com/questions/58624200/react-hook-useeffect-has-a-missing-dependency-dispatch
const stableDispatch = useCallback(dispatch, []);
useEffect(() => {
if (!profile) {
stableDispatch(getCurrentProfile());
}
}, [profile, stableDispatch]);
const { user } = authReducer;
console.log("loading", loading);
console.log("profile", profile);
return loading && profile === null ? <div>Spinner</div> : "Actual Profile";
};
export default Profile;
Also, it doesn't seem like you're currently doing anything with the loading piece of state–at least from what you've shared here. You might want to dispatch an action indicating that you're loading before you start the fetch and then it will be set to false when you get the response.
Check out this codesandbox for reference: https://codesandbox.io/s/focused-kilby-gd2nr?file=/src/App.js
Reducers:
const initialState = {
profile: null,
loading: false
};
export const profile = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "LOADING_PROFILE":
return {
...state,
loading: true
};
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
};
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
};
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
};
default:
return state;
}
};
export const auth = (state = {}, action) => {
return state;
};
Action Creator:
import axios from "axios";
export const getCurrentProfile = () => async (dispatch) => {
try {
dispatch({ type: "LOADING_PROFILE" });
const res = await axios.get("https://jsonplaceholder.typicode.com/users/1");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
});
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { profile, auth } from "./reducers";
import App from "./App";
import thunk from "redux-thunk";
const store = createStore(
combineReducers({
profile,
auth
}),
applyMiddleware(thunk)
);
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Well i solved it by dispatching 'getCurrentProfile' not 'getCurrentProfile()' turns out using it like a function causes continuously firing off.
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const dispatch = useDispatch();
useEffect(() => {
if (!profile) {
console.log("It worked")
dispatch(getCurrentProfile());
}
}, [dispatch(getCurrentProfile)])
I see a lot of post of this, but my problem persist. I probe a lot of manners to manipulate array with objects on redux. The console.log output undefined, and in the browser I see "cannot read property '0' of undefined'
Its an Ecommerce
CartReducer
import {
GET_USUARIO_CARRITO,
POST_USUARIO_CARRITO,
GET_VISITANTE_CARRITO,
POST_VISITANTE_CARRITO,
EDITAR_VISITANTE_CARRITO,
DELETE_CARRITO,
PUT_CATEGORIA,
} from "../actions/ActionTypes";
const initialState = {
CarritoCompleto: {},
Res: {},
};
export default function carritoReducer(state = initialState, action) {
switch (action.type) {
case GET_USUARIO_CARRITO:
const carrito = action.payload;
const lineaDeOrdens = action.payload.lineaDeOrdens;
return {
...state,
CarritoCompleto: {
...carrito,
lineaDeOrdens: Object.assign({}, lineaDeOrdens)
},
};
case POST_USUARIO_CARRITO:
return {
...state,
Res: action.payload,
};
case GET_VISITANTE_CARRITO:
//falta construir
case DELETE_CARRITO:
return;
default:
return state;
}
}
CartAction
import {
GET_USUARIO_CARRITO,
POST_USUARIO_CARRITO,
GET_VISITANTE_CARRITO,
POST_VISITANTE_CARRITO,
EDITAR_VISITANTE_CARRITO,
DELETE_CARRITO,
PUT_CATEGORIA,
} from "./ActionTypes";
import axios from "axios";
//NO TOCAR - MODIFICANDO EN BASE A RUTAS
//Esta esta totalmente incompleta,
export const getUsuarioCarrito = (usuarioId) => (dispatch) => {
axios.get(`http://localhost:3001/usuario/${usuarioId}/cart`) //falta url
.then((res) => {
dispatch({
type: GET_USUARIO_CARRITO,
payload: res.data,
});
})
.catch((err) => {
dispatch(postUsuarioCarrito);
});
};
//post a usuario
//revisar que este bien igual que las demas.
export const postUsuarioCarrito = (usuarioId) => (dispatch) => {
axios
.post(`http://localhost:3001/usuario/${usuarioId}/cart`)
.then((res) => {
dispatch({
type: POST_USUARIO_CARRITO,
payload: res.data,
});
dispatch(postUsuarioCarrito());
})
.catch((err) => {
const error = err.res.data;
dispatch(error);
});
};
//delete carrito
export const deleteCarrito = () => (dispatch) => {
dispatch({
type: DELETE_CARRITO,
payload: null,
});
};
export default { getUsuarioCarrito, postUsuarioCarrito, deleteCarrito};
Component Cart
import React, { useEffect, useState } from "react";
import "./carrito.css";
import Miniprod from "./miniproduct"
import Loader from "../Loader/Loader"
import { useSelector, useDispatch, connect } from 'react-redux'
import allActions from '../../redux/actions/allActions'
export default function Carrito() {
// ================== ESTADO REDUX ======================//
const usuario = useSelector(state => state.usuario.id)
const productoCarrito = useSelector(state => state.carrito.CarritoCompleto.lineaDeOrdens)
const dispatch = useDispatch()
// ================== ESTADO COMOPONENTES ===================== //
const [total, setTotal] = useState(0);
const [envio, setEnvio] = useState(0);
const [subtotal, setSubTotal] = useState(0);
const [listaproductos, setListaProductos] = useState({});
const [user, setUser] = useState(0)
const descuento = 0.8;
console.log(Object.values(productoCarrito)) =============> I probe the error like this
// ================== USE EFFECT ========================//
useEffect(
() => {
dispatch(allActions.getUsuarioCarrito(usuario))
dispatch(allActions.login)
},[])
return (
<div>
...
</div>
)
}
Where is the error? or, how can access to the array of objects an iterate all, for a post render
Thanks a lot for solutions!
I have a problem. As I understood hook useEffect doen't run.
I have action that should take data from server.
export const getProducts = () => {
return dispatch => {
dispatch(getProductsStarted());
fetch('https://shopserver.firebaseapp.com/get-products')
.then(res => {
dispatch(getProductsSuccess(res.json()));
})
.catch(err => {
dispatch(getProductsFailure(err.message));
});
}
}
const getProductsSuccess = todo => ({
type: "ADD_TODO_SUCCESS",
payload: {
...todo
}
});
const getProductsStarted = () => ({
type: "ADD_TODO_STARTED"
});
const getProductsFailure = error => ({
type: "ADD_TODO_FAILURE",
payload: {
error
}
});
I have a reducer.
const initialState = {
loading: false,
products: [],
error: null
}
export const ProductReducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_TODO_SUCCESS":
return {
...state,
loading: false,
error: null,
todos: [...state.products, action.payload.products]
}
case "ADD_TODO_STARTED":
return {
...state,
loading: true
}
case "ADD_TODO_FAILURE":
return {
...state,
loading: false,
error: action.payload.error
}
default:
return state
}
}
And I have a Component where I want to render a result.
import React from 'react';
import { CardItem } from "./cardItem";
import { useSelector } from 'react-redux';
import { useEffect } from 'react';
import { getProducts } from '../Redux/Actions/productAction'
export const ProductCard = () => {
useEffect(() => {
getProducts();
console.log('111111')
})
const data = useSelector(state => state.ProductReducer.products);
return (
<div>
{data.map( element =>
CardItem (element)
)}
</div>
)
}
After rendering page nothing happens. ReduxDevTools shows that there was no send actions. Please, help me to fix it. Thank you.
I think you should be calling your async action like this :
import { useDispatch, useSelector } from 'react-redux';
[...]
export const ProductCard = () => {
const dispatch = useDispatch();
useEffect(() => {
// I guess getProducts is an async redux action using redux-thunk
dispatch(getProducts());
console.log('111111')
}, []);
[...]
}
I assume you want to load products only when component is born, so I pass an empty array as second argument for useEffect (https://reactjs.org/docs/hooks-reference.html#useeffect).
I was going over the updated Stephen Girder react-native course and saw that he used a single createDataContext file so that his Reducer has access to Context and Provider.
his createDataContext file looks like this:
import React, {useReducer} from 'react';
export default (reducer, actions, initialState) => {
const Context = React.createContext(reducer, initialState);
console.log('show me the initial state: ', initialState)
// actions === {addBlogPost: (dispatch) => { return ()=> {} }}
const Provider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{state, ...boundActions}}>
{children}
</Context.Provider>
);
};
return {Context, Provider};
};
BlogPostContext
import createDataContext from './createDataContext';
const blogReducer = (state, action) => {
console.log('show me the state inside the reducer: ', action);
switch (action.type) {
case 'add_blogpost':
return [
...state,
{
id: Math.floor(Math.random() * 999),
title: action.payload.title,
content: action.payload.content,
},
];
case 'delete_blogpost':
return state.filter(blogPost => blogPost.id !== action.payload);
case 'edit_blogpost':
return state.map(blogPost => {
return blogPost.id === action.payload.id ? action.payload : blogPost;
});
default:
return state;
}
};
const addBlogPost = dispatch => {
return (title, content, callback) => {
console.log("inside addBlogpost", title, content);
dispatch({
type: "add_blogpost",
payload: { title, content }
});
callback();
};
};
const deleteBlogPost = dispatch => {
return id => {
dispatch({type: 'delete_blogpost', payload: id});
};
};
const editBlogPost = dispatch => {
return (id, title, content) => {
dispatch({
type: "edit_blogpost",
payload: { id, title, content }
});
};
};
export const {Context, Provider} = createDataContext(
blogReducer,
{addBlogPost, deleteBlogPost, editBlogPost},
[],
);
Methods that need to be accessed on a particular page are simply de-destructured on the page that they're needed:
import React, {useContext} from 'react';
import {StyleSheet} from 'react-native';
import {Context} from '../context/BlogContext';
import BlogPostForm from '../components/BlogPostForm';
const CreateScreen = ({navigation}) => {
const {addBlogPost} = useContext(Context);
return (
<BlogPostForm
onSubmit={(title, content) => {
addBlogPost(title, content, () => navigation.navigate('Homepage'));
}}
/>
);
};
const styles = StyleSheet.create({});
export default CreateScreen;
I would like to create a second Context page that contains a different reducer, methods, and most importantly initialState object.
However, I will NOT be able to access the state of the second Context page because in the createDataContext only passes state as a value to a provider.
<Context.Provider value={{state, ...boundActions}}>
That only grants me access to the first Context(BlogContext). How can I access the state of the second Context page given how createDataContext file is at the moment? Would I need to create a second createDataContext or do I need to define individual states when I pass the state in the Context.Provider?