React Hooks useState Array empty with states rendered in the component - javascript

I have a situation where i can successfully dispatch my states with reducers and i can render it in my component
Here the relevant code
in my action/index.js
export const receivedLeaguesList = json => ({
type: RECEIVE_LEAGUES_LIST,
json: json
});
export function fetchLeaguesList() {
return function(dispatch) {
dispatch(requestLeaguesList());
return axios
.get("https://www.api-football.com/demo/v2/leagues/")
.then(res => {
let leagues = res.data.api.leagues;
dispatch(receivedLeaguesList(leagues));
})
.catch(e => {
console.log(e);
});
}
}
my reducers/index.js
import { REQUEST_LEAGUES_LIST, RECEIVE_LEAGUES_LIST } from "../actions";
const initialState = {
leaguesList: [],
isLeagueListLoading: false
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case REQUEST_LEAGUES_LIST:
return { ...state, isLeagueListLoading: true };
case RECEIVE_LEAGUES_LIST:
return { ...state, leaguesList: action.json, isLeagueListLoading: false };
default:
return state;
}
};
in my component component/Leagues.js
let Leagues = ({ leaguesList, loading, getList }) => {
useEffect(() => {
getList();
}, [getList]);
const [itemsLeagues] = useState([leaguesList]);
console.log("league list", itemsLeagues);
const mapDispatchToProps = {
getList: fetchLeaguesList
};
I have reproduced the demo here => https://codesandbox.io/s/select-demo-71u7h?
I can render my leaguesList states in my component doing the map, but why when
const [itemsLeagues] = useState([leaguesList]);
console.log("league list", itemsLeagues);
returns an empty array ?
See the image

You're setting useState's init value wrong:
const [itemsLeagues] = useState(leaguesList);
instead of
const [itemsLeagues] = useState([leaguesList]);
The return value of useState isn't the value itself, but the array of value and mutator:
const [value, setValue] = useState([42, 43])
// here's value equals [42, 43]
So if you were trying to destructure the wrapping array you passed to useState(), you should use it like this (though you don't need it):
const [[itemsLeagues]] = useState([leaguesList]);

Related

Firebase - return the value from the onSnapshot event in function

I am trying to return the value from function that has the onSnapshot() event but keep getting this weird error. Basically, I call this action and return the data from it like I would in any other function. But I keep getting this error and I do not know how to fix it.
This is the error
Uncaught TypeError: Cannot add property 0, object is not extensible
at Array.push (<anonymous>)
This the function
export const getQuestions = () => {
var questions = [];
onSnapshot(collection(firebaseDatabase, "questions"), (querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
if (doc.data() !== null) {
questions.push(doc.data());
}
});
});
return questions;
};
Also this function is used with Redux Thunk and Redux Toolkit.
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { getQuestions } from "../../utils/firebase-functions/firebase-functions";
export const getAllQuestions = createAsyncThunk(
"allQuestions/getAllQuestions",
async () => {
const response = getQuestions();
return response;
}
);
export const allQuestionsSlice = createSlice({
name: "allQuestions",
initialState: {
allQuestions: [],
loading: false,
error: null,
},
extraReducers: {
[getAllQuestions.pending]: (state) => {
state.loading = true;
state.error = null;
},
[getAllQuestions.fulfilled]: (state, action) => {
state.allQuestions = action.payload;
state.loading = false;
state.error = null;
},
[getAllQuestions.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload;
},
},
});
export default allQuestionsSlice.reducer;
Where it is dispatched
const dispatch = useDispatch();
const tabContentData = useSelector(
(state) => state.allQuestions.allQuestions
);
useEffect(() => {
dispatch(getAllQuestions());
}, [dispatch]);
console.log(tabContentData);
You can try returning a promise when the data is being fetch for first time as shown below:
let dataFetched = false;
export const getQuestions = () => {
return new Promise((resolve, reject) => {
onSnapshot(collection(firebaseDatabase, "questions"), (querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
if (doc.data() !== null) {
questions.push(doc.data());
}
});
if (!dataFetched) {
// data was fetched first time, return all questions
const questions = querySnapshot.docs.map(q => ({ id: q.id, ...q.data()}))
resolve(questions)
dataFetched = true;
} else {
// Questions already fetched,
// TODO: Update state with updates received
}
});
})
};
getQuestions() now returns a Promise so add an await here:
const response = await getQuestions();
For updates received later, you'll have to update them directly in your state.

How do you get an element out of a redux reducer?

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.

Update useContext every second

I have a variable "second", which needs to be updated every second. and it was used by multiple components so i placed it in context.
Only for the first time i see correct value and from the next iteration it is coming as undefined.
any advice please
Output:
state is {second: 43, error: null}
state is {second: undefined, error: null}
and i'm using setInterval inside useEffect to update the variable every second
Code
import React, { useReducer, useEffect } from "react";
export const SecContext = React.createContext();
const initialState = {
second: new Date().getSeconds(),
error: null,
};
const reducer = (state, action) => {
switch (action.type) {
case "UPDATE_SECOND":
return {
...state,
second: action.second,
};
default:
throw new Error();
}
};
export const SecContextProvider = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
const updateSecond = () => {
let second = new Date().getSeconds();
console.log("state is ", state);
dispatch({
type: "UPDATE_SECOND",
payload: second,
});
};
useEffect(() => {
const timeoutId = setInterval(() => {
updateSecond();
}, 1000);
return function cleanup() {
clearInterval(timeoutId);
};
}, [updateSecond]);
return (
<SecContext.Provider value={[state, dispatch]}>
{props.children}
</SecContext.Provider>
);
};
export default SecContextProvider;
Sorry for writing this answer, but I am not able to add a new comment.
A minor issue I saw
return {
...state,
second: action.second, // you refer to action.second, which is undefined, you need action.payload here
};

REDUX: Error: Actions may not have an undefined "type" property. Have you misspelled a constant?

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,
})
);
};

how to use state in actions when we use useReducer

I am using hooks and context api.I have multiple actions that write them into seperate file.my problem this:in another file how can I access state?
I use this file for create my contexts:
createContext.js
import React, { useReducer } from "react";
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
and when I want to create context I pass actions ,reducer and default values to createContext file and get Context and Provider from that.like this:
productContext.js
import createDataContext from "./createDataContext";
import {storeProducts, detailProduct} from "../data";
const productReducer = (state, action) => {
switch (action.type) {
case "GET_ITEM":
return {...state, productDetail: action.productDetail};
case "ADD_TOTALS":
return {
...state,
cartSubTotal: action.cartSubTotal,
cartTotal: action.cartTotal,
cartTax: action.cartTax
};
case "ADD_TO_CART":
return {
...state,
products: action.tempProducts,
cart: [...state.cart, action.product]
};
default:
return state;
}
};
const getItem = (id) => {
const product = **products**.find(item => item.id === id);
return product;
}
const handleDetail = dispatch => (id) => {
const productDetail = getItem(id);
dispatch({type: "GET_ITEM", productDetail})
};
const addToCart = dispatch => (id) => {
let tempProducts = [...storeProducts];
const index = tempProducts.indexOf(getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
dispatch({
type: "ADD_TO_CART",
tempProducts,
product
});
const data = addTotals();
dispatch({
type: "ADD_TOTALS",
cartSubTotal: data.cartSubTotal,
cartTotal: data.cartTotal,
cartTax: data.cartTax
});
};
const addTotals = () => {
let subTotal = 0;
**cart**.map(item =>{ (subTotal += item.total)});
const tempTax = subTotal * 0.1;
const tax = parseFloat(tempTax.toFixed(2));
const total = subTotal + tax;
return {cartSubTotal: subTotal, cartTax: tax, cartTotal: total};
};
export const {Provider, Context} = createDataContext(
productReducer,
{
handleDetail,
},
{
products: storeProducts,
productDetail: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct,
cartSubTotal: 0,
cartTax: 0,
cartTotal: 0
);
I can not access cart and products that are bold.how can I use them?
It looks like you're doing a lot of work in the action creator function that would make more sense as part of the reducer. For example, instead of this:
const productReducer = (state, action) => {
switch (action.type) {
case 'GET_ITEM':
return { ...state, productDetail: action.productDetail };
default:
return state;
}
};
const getItem = (id) => {
// no access to state!
const product = products.find((item) => item.id === id);
return product;
};
const handleDetail = (dispatch) => (id) => {
const productDetail = getItem(id);
dispatch({ type: 'GET_ITEM', productDetail });
};
You can do this:
// action value
{ type: 'GET_ITEM', id: 1234 }
// reducer
const productReducer = (state, action) => {
switch (action.type) {
case 'GET_ITEM':
const productDetail = state.products.find(
(item) => item.id === action.id
);
return { ...state, productDetail };
default:
return state;
}
};
Inside the reducer is where you have access to both the action and the state. Try to design your actions so that they contain the smallest amount of information possible to achieve your intention.

Categories