Different instances of a redux toolkit store - reactjs

I'm building a custom dropdown component and i'm using redux toolkit to manage the state. It works just fine
But when I reuse the dropdown component in another place, in the same page, the "states conflicts", so when I open one dropdown, the another opens to. (This is my dropdown reducer)
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
interface Dropdown {
isOpen: boolean;
selectedValue: string;
}
export const toggleOpen = createAction("dropdown/toggleOpen");
export const changeSelectedValue = createAction<string>(
"dropdown/changeSelectedValue"
);
const initialState = {
isOpen: false,
selectedValue: "-- Selecione --",
} as Dropdown;
const dropdownReducer = createReducer(initialState, (builder) => {
builder
.addCase(toggleOpen, (state) => {
state.isOpen = !state.isOpen;
})
.addCase(changeSelectedValue, (state, action) => {
state.selectedValue = action.payload;
});
});
const dropdownStore = configureStore({
reducer: dropdownReducer,
});
type RootState = ReturnType<typeof dropdownStore.getState>;
type AppDispatch = typeof dropdownStore.dispatch;
export const useDropdownDispatch = () => useDispatch<AppDispatch>();
export const useDropdownSelector: TypedUseSelectorHook<RootState> = useSelector;
export default dropdownStore;
Is there any way that I can create different "instances" of the same store, so each dropdown has it's own?
PS: I'm populating the Provider in the Dropdown component, so there is one provider to each dropdown, as follow:
import React from "react";
import { Provider } from "react-redux";
import ArrowDown from "../assets/icons/arrow-down";
import ArrowUp from "../assets/icons/arrow-up";
import store, {
useDropdownSelector,
useDropdownDispatch,
toggleOpen,
changeSelectedValue,
} from "../store/reducers/dropdown";
import styles from "./SingleDropdown.module.scss";
interface ItemProps {
value: string;
onClick?: (value: string) => void;
}
const ArrowIcon = () => {
const isOpen = useDropdownSelector((state) => state.isOpen);
return isOpen ? <ArrowUp /> : <ArrowDown />;
};
export const SelectItem: React.FC<ItemProps> = ({
children,
value,
onClick,
}) => {
const dispatch = useDropdownDispatch();
const changeSelectedValueClickHandler = () => {
dispatch(changeSelectedValue(value));
if (onClick) onClick(value);
};
return (
<div
className={styles.dropdown__menu__items}
onClick={changeSelectedValueClickHandler}
id={value}
>
{children}
</div>
);
};
const SelectMenu: React.FC = ({ children }) => {
const isOpen = useDropdownSelector((state) => state.isOpen);
return isOpen ? (
<div className={styles.dropdown__menu}>{children}</div>
) : null;
};
const InitialSelectItem = () => {
const selectedValue = useDropdownSelector((state) => state.selectedValue);
const dispatch = useDropdownDispatch();
return (
<div
onClick={() => dispatch(toggleOpen())}
className={styles.dropdown__field}
>
{selectedValue}
<ArrowIcon />
</div>
);
};
export const SingleSelect: React.FC = ({ children }) => {
return (
<Provider store={store}>
<div className={styles.dropdown}>
<InitialSelectItem />
<SelectMenu>{children}</SelectMenu>
</div>
</Provider>
);
};

Generally, we would suggest not keeping state like this in Redux, for exactly the kind of reason you just saw. It isn't "global" state - only one specific component cares about it:
https://redux.js.org/tutorials/essentials/part-2-app-structure#component-state-and-forms
By now you might be wondering, "Do I always have to put all my app's state into the Redux store?"
The answer is NO. Global state that is needed across the app should go in the Redux store. State that's only needed in one place should be kept in component state.
If you truly need to have this data in Redux, and control multiple "instances" of a component with their own separate bits of state, you could use some kind of a normalized state structure and track the data for each component based on its ID.

Related

Update react context provider value using child component

I have created a provider that is doing API call and setting data inside provider using value
//context
export const ProductContext = createContext({
loading: false,
data: null,
});
export function useProductContext() {
const context = useContext(ProductContext);
return context;
}
//provider
export const ProductProvider = ({ children, id }) => {
const { data, error, loading } = fetch({url},'id');
const value = useMemo(
() => ({
data
}),
[data],
);
return (
<ProductContext.Provider value={value}>
{children}
</ProductContext.Provider>
);
};
// component
const Card = (): JSX.Element => {
const { data } = useProductContext();
return (
<StyledSection>
<button onClick={}>fetch data with new params</button>
</StyledSection>
);
};
export default Card;
Here in component i want to fetch data when user click on button with different params.
So you want to fetch Data from the value you are given. Here is the way that I'll do this.
//Context
export const ProductContext = createContext();
export const ProductProvider = ({children}) => {
const fetchData = (val) => fetch(`fetch/${val}`);
return (
<ProductContext.Provider value={{fetchData}}>
{children}
</ProductContext.Provider>
);
};
As you can see in my context above I have my fetchData method there. Which accepts an argument.
Note: Make sure you have wrapped your root component with the above Provider.
In my component, I'll do it like this,
//Component
import {useContext} from 'react';
import {ProductContext} from '{your path here}/ProductContext';
const Card = () => {
const {fetchData} = useContext(ProductContext);
return (
<StyledSection>
<button onClick={() => fetchData('pass your value here')}>fetch data with new params</button>
</StyledSection>
);
}
export default Card
I believe this is the most simplest way you can achieve your task.

How to use React redux hooks for updating state for just 1 value on click

I'm fairly new to React Redux and am using it to display different transactions on a page. I have set up my store, then created my reducer & selector.
I have a filter state that is controlled by 3 buttons that when clicked, should change the type ('incoming', 'outgoing' or 'trades') and this renders different components based on this change.
Where I seem to be going wrong, is with updating the Redux value on click and ensuring the state also updates, I'm sure its an easy fix but haven't quite figured it out.
This is my reducer:
import {createSlice} from "#reduxjs/toolkit";
export const initialState = {
filter_type: "outgoing",
}
const slice = createSlice({
initialState,
name: "transactionFilter",
reducers: {
setFilterType: (state, {payload}) => {
state.filter_type = payload
}
}
})
const { reducer, actions } = slice;
export const { setFilterType } = actions;
export default reducer;
And my selector:
import {createSelector} from "#reduxjs/toolkit";
const baseState = (state) => state.transaction_filter;
export const transactionFilterSelector = createSelector(
[baseState],
(state) => state.filter_type
)
I'm importing the selector along with useDispatch/useSelector and can see the Redux state in my console.log, how can I use dispatch correctly to update everything?
This is my transaction file:
import React, {useMemo, useEffect, useState} from "react"
import {useDispatch, useSelector} from "react-redux";
const TransactionsPage = (props) => {
const filterState = useSelector(transactionFilterSelector);
console.log('Redux filter state: ', filterState);
const dispatch = useDispatch();
const [filter, setFilter] = useState({
type: filterState,
sources: {
type: "all",
items: []
},
assets: [],
dates: {
type: "all",
from: "",
to: "",
},
category: "all",
})
return (
<div>
<Heading level={'4'}>Choose a type</Heading>
<div className={'tx-type-filter'}>
{Object.values(TxTypes).map((i) => (
<div
className={`tx-type-item ${type === i ? 'selected' : ''}`}
key={i}
onClick={() => {
dispatch({type: setFilterType, payload: i});
}}>
<img src={image(i)} alt={'tx type'} />
</div>
))}
</div>
</div>
)
export default TransactionsPage;
Thank you in advance for any guidance!
You'd do dispatch(setFilterType(i));. You don't write out action objects in modern Redux.

React redux not fetching data from API

Hi im new to redux and im trying to create a movie app using the API from www.themoviedb.org. I am trying to display the popular movies and im sure the API link works since ive tested it in postman but i cant seem to figure out why redux doesnt pick up the data.
//action
import { FETCH_POPULAR } from "./types";
import axios from "axios";
export const fetchPopularMovies = () => (dispatch) => {
axios
.get(
`https://api.themoviedb.org/3/movie/popular?api_key=${API}&language=en-US`
)
.then((response) =>
dispatch({
type: FETCH_POPULAR,
payload: response.data
})
)
.catch((err) => console.log(err));
};
//reducer
import { FETCH_POPULAR } from "../actions/types";
const initialState = {
popular: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POPULAR:
return {
...state,
popular: action.payload,
};
default:
return state;
}
}
import React from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
export default connect(mapStateToProps)(FetchedPopular);
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.poster_path}`}
/>
</div>
);
};
export default Popular;
I cant really tell what I'm missing can someone help?
Next to mapStateToProps you need to create mapDispatchToProps. After that, you will be able to call your Redux action from your React component.
I suggest you the mapDispatchToProps as an Object form. Then you need to use this mapDispatchToProps as the second parameter of your connect method.
When you will have your action mapped to your component, you need to call it somewhere. It is recommended to do it for example on a component mount. As your React components are Functional components, you need to do it in React useEffect hook.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
import { fetchPopularMovies } from 'path_to_your_actions_file'
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
useEffect(()=> {
// call your mapped action (here it is called once on component mount due the empty dependency array of useEffect hook)
props.fetchPopularMovies();
}, [])
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
// create mapDispatchToProps
const mapDispatchToProps = {
fetchPopularMovies
}
// use mapDispatchToProps as the second parameter of your `connect` method.
export default connect(mapStateToProps, mapDispatchToProps)(FetchedPopular);
Moreover, as I wrote above in my comment, your Popular does not have the prop poster_path but it has the prop popular which probably has the property poster_path.
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.popular.poster_path}`}
/>
</div>
);
};
export default Popular;

Redux onClick action not firing from Component, no errors or console errors

I have an eCommerce React app I'm putting together that has a view of items. Each item has an Add to cart button with an onClick function that dispatches an ADD_ITEM action to update the cart in state.
The problem I'm seeing is that the Action is never firing and the state is never updating, but there aren't any console errors or anything to point me in the direction of what's broken.
I've looked at everything over and over, there's no typos and everything is connected to the store so I'm really at a loss as to why it's not working.
Cart Reducer
import { AnyAction } from "redux";
import CartActionTypes from "./cart.types";
const INITIAL_STATE = {
hidden: true,
cartItems: [],
};
const cartReducer = (state = INITIAL_STATE, action: AnyAction) => {
switch (action.type) {
case CartActionTypes.TOGGLE_CART_HIDDEN:
return {
...state,
hidden: !state.hidden,
};
case CartActionTypes.ADD_ITEM:
return {
...state,
cartItems: [...state.cartItems, action.payload],
};
default:
return state;
}
};
export default cartReducer;
Cart Actions
import CartActionTypes from "./cart.types";
export const toggleCartHidden = () => ({
type: CartActionTypes.TOGGLE_CART_HIDDEN,
});
export const addItem = (item) => ({
type: CartActionTypes.ADD_ITEM,
payload: item,
});
Cart Types
const CartActionTypes = {
TOGGLE_CART_HIDDEN: "TOGGLE_CART_HIDDEN",
ADD_ITEM: "ADD_ITEM",
};
export default CartActionTypes;
Root Reducer
import { combineReducers } from "redux";
import userReducer from "./user/user.reducer";
import cartReducer from "./cart/cart.reducer";
export default combineReducers({
user: userReducer,
cart: cartReducer,
});
Item Component with onClick/mapDispatchToProps function
import React from "react";
import styled from "styled-components";
import { connect } from "react-redux";
import { addItem } from "../../redux/cart/cart.actions";
import { Item } from "../../pages/shop/shop.component";
const CollectionItem = ({ item }: { item: Item }) => {
const { name, price, imageUrl } = item;
return (
<CollectionItemContainer>
<Image
style={{ backgroundImage: `url(${imageUrl})` }}
className="image"
/>
<CollectionFooter>
<Name>{name}</Name>
<Price>{price}</Price>
</CollectionFooter>
<CollectionItemButton
onClick={() => addItem(item)}
className="custom-button inverted"
>
Add to cart
</CollectionItemButton>
</CollectionItemContainer>
);
};
const mapDispatchToProps = (dispatch) => ({
addItem: (item) => dispatch(addItem(item)),
});
export default connect(null, mapDispatchToProps)(CollectionItem);
Item Collection component (Parent of Item Component)
import React from "react";
import styled from "styled-components";
import { Item } from "../../pages/shop/shop.component";
import CollectionItem from "../collection-item/collection-item.component";
const CollectionPreview = ({
title,
items,
}: {
title: string;
items: Array<Item>;
}) => {
return (
<CollectionPreviewContainer>
<Title>{title.toUpperCase()}</Title>
<Preview>
{items
.filter((item, idx) => idx < 4)
.map((item) => (
<CollectionItem key={item.id} item={item} />
))}
</Preview>
</CollectionPreviewContainer>
);
};
export default CollectionPreview;
There is only a very small issue, but very relevant issue in your code: In your CollectionItem component your not using the addItem function from your props, which was injected by connect with your mapDispatchToProps function. You probably meant to destructure it in the function definition of your CollectionItem component but just forgot it.
changing
const CollectionItem = ({ item }: { item: Item }) =>
to
const CollectionItem = ({ item, addItem }: { item: Item, addItem: () => void }) =>
should fix the issue.
Note that you didn't see any error because your action creator is called addItem too. Therefore when you call addItem in the onClick function, the function is still defined even though you didn't destructure it from the props. However calling the action creator instead of the function from mapDispatchToProps will just create the action (a plain js object) and return it, without dispatching it...
To avoid such hard to spot mistakes I would recommend to name the function injected through mapDispatchToProps differently than the action creator.
Example:
const CollectionItem = ({ item /* missing fn here */ }: { item: Item }) => {
const { name, price, imageUrl } = item;
return (
<CollectionItemContainer>
<Image
style={{ backgroundImage: `url(${imageUrl})` }}
className="image"
/>
<CollectionFooter>
<Name>{name}</Name>
<Price>{price}</Price>
</CollectionFooter>
<CollectionItemButton
onClick={() => handleAddItem(item)}
className="custom-button inverted"
>
Add to cart
</CollectionItemButton>
</CollectionItemContainer>
);
};
const mapDispatchToProps = (dispatch) => ({
handleAddItem: (item) => dispatch(addItem(item)),
});
Not the the error would become really obvious, because a handleAddItem function not defined error would be thrown and you'd immediately know that you are missing the handleAddItem function in the first line of this example.
import React from 'react'
import "./login.css"
import {Button} from "#material-ui/core";
import { auth, provider} from "./firebase";
function login() {
const signin= () =>
{
auth.signInWithPopup(provider).catch(error => alert(error.message));
};
return (
<div id="Login">
<div id="login_logo">
<img src="https://cdn.worldvectorlogo.com/logos/discord-logo-color-wordmark-1.svg"/>
</div>
<Button onClick={() => signin} >Sign in</Button>
</div>
)
}
export default login;

React-Redux State won't update onClick

New to React. I am trying out react redux for the first time (on my own). I have a state for a gameboard called force_hidden that I want to set in App.js and then use in a child component ( a few levels down). I used redux to create forceGameBoardHidden that should set force_hidden to whatever value is inside the (). so, forceGameBoardHidden(true) should set the state of force_hidden to true. However, that doesn't happen. I can click on the item and it logs "before change" and then the state. In between it should have set the state to true, but the state is still false. I don't know what's going wrong here. I tried console.logging the gameBoardReducer. It fires when I start the page, but doesn't fire when I click the button.
gameboard.types.js
const GameBoardActionTypes = {
FORCE_GAMEBOARD_HIDDEN: 'FORCE_GAMEBOARD_HIDDEN'
}
export default GameBoardActionTypes;
gameboard.action.js
import GameBoardActionTypes from './game-board.types';
export const forceGameBoardHidden = value => ({
type: GameBoardActionTypes.FORCE_GAMEBOARD_HIDDEN,
payload: value
});
gameboard.reducer.js
import GameBoardActionTypes from './game-board.types'
const INITIAL_STATE = {
force_hidden: false
}
const gameBoardReducer = ( state = INITIAL_STATE, action) => {
switch (action.type) {
case GameBoardActionTypes.FORCE_GAMEBOARD_HIDDEN:
return {
...state,
force_hidden: action.payload
}
default:
return state;
}
}
export default gameBoardReducer;
root-reducer
import { combineReducers } from 'redux';
import gameBoardReducer from './game-board/game-board.reducer'
export default combineReducers ({
gameboard: gameBoardReducer
})
store.js
const middlewares = [];
const store = createStore(rootReducer, applyMiddleware(...middlewares))
export default store;
index.js
<Provider store={store}>
App.js -- this is where the magic should happen in forceGameBoardHidden
const App = () => {
const handleKeyChange = event => {
setKey(event.target.value);
console.log("before change")
forceGameBoardHidden(true)
console.log(store.getState().gameboard)
}
return (
<SearchBox
onChange={handleKeyChange}
placeholder="Enter your game Key"/>
</div>
);
}
const mapDispatchToProps = dispatch => ({
forceGameBoardHidden: item => dispatch(forceGameBoardHidden(item))
})
export default connect(null,mapDispatchToProps)(App);
I think you need to dispatch the action, there are 2 methods , one is to connect the component to the actions and bind them to dispatch. The other one is much easier since you use functional components, is by using the useDispatch hook
Example here:
import { useDispatch } from 'react-redux' // <-- add this
const App = () => {
const dispatch = useDispatch() // <-- add this
const handleKeyChange = event => {
setKey(event.target.value);
console.log("before change")
dispatch(forceGameBoardHidden(true)) // <-- change this
console.log(store.getState().gameboard)
}
return (
<SearchBox
onChange={handleKeyChange}
placeholder="Enter your game Key"/>
</div>
);
}

Resources