How to pass data from mapStateToProps to parent component? - reactjs

This is my component:
export default const NotesComponent = () => {
return notesList.map((noteProps) => (
<NewsCard {...notesProps} key={notesProps.id} />
))
}
Here is the place where i use this component:
const Notes = (props: NotesProps) => {
return (
<Container>
<NotesComponent />
</Container>
)
}
const mapStateToProps = (state) => ({
notesData: state.notes.notesData,
})
// and other code so mapStateToProps is working correctly
I don't know how to pass notesData to NotesComponent, so the NewsCard can read the data.

You can use connect high-order-function from react-redux and export the returned component:
import { connect } from 'react-redux'
// Redux state data notesData will be available in props
const NotesComponent = ({notesData}) => {
return notesData.map((noteProps) => (
<NewsCard {...noteProps} key={noteProps.id} />
))
}
const mapStateToProps = (state) => ({
notesData: state.notes.notesData,
})
export default connect(mapStateToProps)(NotesComponent)
Or, as NotesComponent is a function component, you can use react-hook useSelector instead of using connect to read redux data:
// in a function component
import { useSelector } from 'react-redux'
...
const notesData = useSelector((state) => state.notes.notesData)
Edit:
You can also connect parent component i.e. Notes with Redux and pass data to NotesComponent in props (to make NotesComponent a dumb or reusable component):
interface NotesProps {
notesData: write_your_type[]
// ...
}
const Notes = (props: NotesProps) => {
const { notesData } = props
return (
<Container>
<NotesComponent data={notesData} />
</Container>
)
}
const mapStateToProps = (state) => ({
notesData: state.notes.notesData,
})
export default connect(mapStateToProps)(Notes)
// it now exports enhanced (with redux data in props) Notes component
And, NotesComponent:
export default const NotesComponent = ({data}) => {
return data.map((item) => (
<NewsCard {...item} key={item.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.

Different instances of a redux toolkit store

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.

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;

dispatch usereducer in gatsby

I am new to gatsby and hope you can all helt me.
I am getting a typeerror: dispatch is not a function message, and have stuggled with finding my typo for two days now. I am trying to make a cart and need to pass the product to a global state. It is only when I set in the dispatch in my handleClick function I seem to get the problem (function works with only a console.log and takes in the product fine)
This is my code. First the provider, then the layout and last the template that holds a single product:
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const reducer = (state, action) => {
switch (action.type) {
case "ADD":
return [...state, action.item];
default:
throw new Error(`unknown action ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [ state, dispatch ] = useReducer(reducer, [])
return (
<CartDispatchContext.Provider value={dispatch}>
<CartStateContext.Provider value={state}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
)
};
export const useCart = () => useContext(CartStateContext);
export const useDispatchCart = () => useContext(CartDispatchContext);
/* layout component */
import React, { useState } from 'react';
import Container from './Container';
import Footerone from './Footerone/Footerone';
import HeaderOne from './HeaderOne/HeaderOne';
import ShopHeaderOne from './ShopHeaderOne/ShopHeaderOne';
import { CartContext } from './CartContext';
import { CartProvider } from './Cart'
export default ({ children }) => {
const[cartVisible, setCartVisible] = useState(false);
const toggleCart = () => {
setCartVisible(!cartVisible)
};
return (
<Container>
<CartProvider>
<CartContext.Provider value={{ cartVisible, toggleCart}}>
<HeaderOne />
<ShopHeaderOne />
{ children }
<Footerone />
</CartContext.Provider>
</CartProvider>
</Container>
)
}
/* singleproduct */
import React from 'react'
import Layout from "../Components/Layout"
import './templates.scss'
import { useDispatchCart } from '../Components/Cart';
export default ( props ) => {
const product = props.pageContext
const dispatch = useDispatchCart();
const handleClick = (item) => {
dispatch({ type: "ADD", item })
}
console.log(product)
return (
<Layout>
<section className="single-grid">
<h1>{product.headline}</h1>
<img src={product.image.mediaItemUrl} alt={product.image.altText}/>
<p>{product.price}</p>
<button onClick={handleClick(product)}>Føj til kurv</button>
</section>
</Layout>
)
}
Have you tried binding directly the dispatch?
return (
<Layout>
<section className="single-grid">
<h1>{product.headline}</h1>
<img src={product.image.mediaItemUrl} alt={product.image.altText}/>
<p>{product.price}</p>
<button onClick={()=> dispatch({ type: "ADD", item })}>Føj til kurv</button>
</section>
</Layout>
)
If the issue persists, check the reducer in order to see if it's being set properly.

React Context state not shared between components

I have been using Redux for a long time, but now decided to try out the new ContextAPI.
I got it working with one component/page (using NextJs), however the state isn't shared between pages/components.
store.js
import React, { createContext, useReducer } from 'react';
import reducer from './reducer'
const initialState = {
players: [],
};
const Store = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Context.Provider value={[state, dispatch]}>
{children}
</Context.Provider>
)
};
export const Context = createContext(initialState);
export default Store;
reducer.js
const Reducer = (state, action) => {
switch (action.type) {
case 'ADD_PLAYER':
return {
...state,
players: [...state.players, action.payload],
};
case 'REMOVE_PLAYER_BY_INDEX':
const array = state.players;
if (array) {
array.splice(action.payload, 1);
}
return {
...state,
players: !array ? [] : array,
};
default:
return state;
}
};
export default Reducer;
add players page /players/add (addplayerspage.js)
import React, { useContext } from 'react';
import map from 'lodash/map';
import isEqual from 'lodash/isEqual';
import { Context } from '../../../context';
const PlayerCreatePage = () => {
const [_, dispatch] = useContext(Context);
const handleAddPlayer = () => {
dispatch({ type: 'ADD_PLAYER', payload: Math.random() });
};
const handleRemovePlayerByIndex = (index) => {
dispatch({ type: 'REMOVE_PLAYER_BY_INDEX', payload: index });
};
return (
<div className="layout">
<div>
<Context.Consumer>
{([state]) => {
const { players } = state;
return map(players, (p, i) => <div
key={i}
onClick={() => handleRemovePlayerByIndex(i)}
>
{p}
</div>
)
}}
</Context.Consumer>
</div>
<button onClick={() => handleAddPlayer()}>Add new</button>
</div>
);
};
export default React.memo(PlayerCreatePage, (prev, next) => isEqual(prev, next));
lobby players page /players/lobby (lobbyplayerspage.js)
import React, { useContext } from 'react';
import map from 'lodash/map';
import { Context } from '../../../context';
const PlayersLobbyPage = () => {
const [state, _] = useContext(Context);
return (
<div>
<div>
{map(state.players, (p, i) => <div
key={i}
>
{p}
</div>
)}
</div>
</div>
);
};
export default PlayersLobbyPage;
_app.js (NextJs)
import App, { Container } from 'next/app';
import '../styles/main.css';
import Store from '../context';
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Store>
<Component {...pageProps} />
</Store>
</Container>
);
}
}
export default MyApp;
THE PROBLEM:
Have two tabs open
Add players
Lobby
Add a new player
See that player is added on 'Add players' page
2.See that NOTHING happens on 'Lobby' page
Okay, so the issue is that I was trying to "share" context api state between different open tabs, it doesn't work like that by default, even for redux (tried adding it and faced same result), for redux there's a redux-state-sync library for that, nonetheless I will use sockets in future, so this won't be an issue.
Closed.

Resources