Dispatching components via react-redux - reactjs

I want to pass a component and display it inside another one through redux.
I am doing something like this:
ComponentToDispatch.js
const ComponentToDispatch = (props) => {
return (<div>Propagate me {props.anthem}</div> {/* props.anthem = undefined, unlike what I expect.*/}
);
};
export {ComponentToDispatch};
In the following component, I have a button that dispatches the formerly defined one.
BruitEvent.js
// code above
`import {ComponentToDispatch} from "./ComponentToDispatch";
import {showComponentAction} from "./actions";
const BruitEvent =()=>{
const dispatch = useDispatch();
return (<button onClick={(evt)=>dispatch(showComponentAction(ComponentToDispatch)}>
Click to dispatch</button>);
};`
The action that triggers this event is:
actions.js
`
export function ShowComponentAction(Component) {
return {
type: SHOW_ACTION,
payload: {
component: <Component />,
},
};
}`
Finally, I can display the propagated component:
const DispayComponent = () =>{
const { component} = useSelector((state) => {
if (state.testDisplay) {
return {
component: state.testDisplay.component,
};
}
return { component: null };
});
useInjectReducer({ key: "testDisplay", reducer });
return (<div>{component}</div>);
}
export {DisplayComponent};
So far so good, thanks to David Hellsing for his insight, I can display every static thing resides in `ComponentToDispatch', but it fails to handle props.
Question: How can I transmit props while dispatching component itself?

You need to either instantiate and enclose the props before the component is dispatched, or pass the uninstantiated component and props object in the dispatched action and pass the props to the component on the receiving end. I suggest the latter, send both component and props in action.
const BruitEvent =()=>{
const dispatch = useDispatch();
return (
<button
onClick={(evt) => dispatch(
showComponentAction(ComponentToDispatch, /* some possible props object */)
)}
>
Click to dispatch
</button>
);
};
...
export function ShowComponentAction(Component, props = {}) {
return {
type: SHOW_ACTION,
payload: { Component, props }, // <-- assumes reducer places in state.testDisplay
},
};
...
const DispayComponent = () =>{
const { Component, prop } = useSelector((state) => state.testDisplay);
useInjectReducer({ key: "testDisplay", reducer });
return Component ? <div><Component {...props} /></div> : null;
}

Related

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.

sharing states between two components with useReducer

trying to share the states from one component to another: The state can be accessed from main component but it comes undefined when accessing from a new component
This is my reducer:
export const tableReducer=(state = [], action)=> {
switch (action.type) {
case 'SELECTED_LIST':
state = JSON.parse(JSON.stringify(action.payload));
return state;
default:
return state
}
}
access it from a different file:
const [userList, usersDispatch] = useReducer(tableReducer, []);
useEffect(() => {
const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
d => d.original.email
)
: '';
usersDispatch({ type: 'SELECTED_LIST', payload: list, });
}, [selectedRowIds, selectedFlatRows]);
and in a new component:
const [userList] = useReducer(tableReducer);
const deleteUsers = () => {
console.log(userList)
}
but here console.log(userList) it results to undefined
For Sharing of state between components, you can use Context API with useReducer.
Context API provides a neat way of providing state to child components without ending up with a prop drilling situation. It requires that a Provider is setup, which provides its values to any of its Consumers. Any component that is a child of the Provider can consume the context.
First a piece of context is created.
CustomContext.js
import React from 'react';
const CustomContext = React.createContext();
export function useCustomContext() {
return React.useContext(CustomContext);
}
export default CustomContext;
We can define your reducer in a seperate file.
TableReducer.js
export const tableReducer=(state = [], action)=> {
switch (action.type) {
case 'SELECTED_LIST':
state = JSON.parse(JSON.stringify(action.payload));
return state;
default:
return state
}
}
next is to implement the provider, and give it a value within a "Parent" component (A higher up component)
Parent.js
import CustomContext from './CustomContext'
import { tableReducer } from './TableReducer'
const ParentComponent = () => {
const [userState, usersDispatch ] = React.useReducer(tableReducer, []);
const providerState = {
userState,
usersDispatch
}
return (
<CustomContext.Provider value={providerState} >
<ChildComponent /> //Any component within here can access value by using useCustomContext();
</CustomContext.Provider>
)
}
now any component nested within <CustomContext.Provider></CustomContext.Provider> can access whatever is passed into "value" prop of the Provider which is your context state and the dispatch method.
The child component will look like this (I have ommited your state values and such..)
Child.js
import { useCustomContext }from './CustomContext'
const ChildComponent = (props) => {
//your custom state variables and other methods
const { userState, usersDispatch } = useCustomContext();
useEffect(() => {
const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
d => d.original.email
)
: '';
usersDispatch({ type: 'SELECTED_LIST', payload: list, });
}, [selectedRowIds, selectedFlatRows]);
return(
<div>your components dependent on selectedRowIds, selectedFlatRows<div>
)
}
You can't share the state with useReducer hook like you are trying to. Each call to useReducer returns a new state that is managed using the reducer function passed to useReducer hook.
Just as each call to useState returns a different state, each call to useReducer returns a different state. Two useReducer calls can't share the same state.
To share the state, you can use one of the following options:
Context API
React-Redux
Pass the state from parent component to child component using props
#Gandzal is correct but I found it was lacking a typscript version and also today createContext requieres a default parameter. This came up as one of the top answers on google so I thought I would share.
I setup my solution like this:
Custom context:
import React, {Dispatch} from 'react';
import {StateType, Action} from './reducer'
interface IContextProps {
state: StateType;
dispatch:Dispatch<Action>
}
const CustomContext = React.createContext({} as IContextProps);
export function useCustomContext() {
return React.useContext(CustomContext);
}
export default CustomContext;
Note StateType and Action:
export type StateType = {
items: Array<DataItems>;
date: Date;
};
export type Action = {
type: ActionKind;
payload: DataItems;
};
reducer:
export const reducer = (state: StateType, action: Action) => {
const { type, payload } = action;
let newArray: Array<DataItems> = [];
switch (type) {
case ActionKind.Checked:
newArray = state.items.map((item) => ({
...item,
checked: item.id === payload.id ? true : item.checked,
}));
return {
...state,
items: newArray,
}
default:
return state;
}
};
App.tsx:
import { reducer, initalState } from 'Shared/Reducer/reducer';
import CustomContext from 'Shared/Reducer/CustomContext';
const App: React.FC = () => {
const [state, dispatch] = React.useReducer(reducer, initalState);
const providerState = {
state,
dispatch,
};
return (
<CustomContext.Provider value={providerState}>
<main role="main">
// your components
</main>
</CustomContext.Provider>
);
};
export default App;
And one of your components:
import { useCustomContext } from 'Shared/Reducer/CustomContext';
export const MyComp: React.FC<MyType> = (props) => {
const { data} = props;
const { state, dispatch } = useCustomContext(); --- Your state and dispatch here
return (
<div>
// your component
</div >
);
}

useEffect not setting data to state in functional component

I have functional component wrapped with HOC. Its returns some props after api call. How do I set the state in my child component(functional).
const withEditHoc = (WrappedComponent, actioneffects) => {
class HOC extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
};
}
executeAllActions = async (data, id) => {
await Promise.all(data.map(act => this.props.dispatch(act(id)))).then(() =>
this.setState({ loading: false }),
);
};
componentDidMount = () => {
const editpageId = this.props.match.params.id;
this.executeAllActions(actioneffects, editpageId);
};
render() {
console.log(this.state.loading);
return (
<React.Fragment>
<Loading loading={this.state.loading}>
<WrappedComponent {...this.props} />
</Loading>
</React.Fragment>
);
}
}
return HOC;
This is my HOC Structure. After the api call the data will be in redux.
I am getting a prop for my functional component using mapToStateProp.(react version 16.3)
Please any suggestion for this.
Functional component
function ProjectDetails(props) {
const [projectValue, setValue] = useState({});
const [proData, setProData] = useState({ ...props.project });
useEffect(() => {
setProData({ props.project });//Here I need to set my data, Iam not able to set data here.
}, []);
return <div>{JSON.stringify(props.project)}</div>;
}
function mapStateToProps(state) {
return {
project: state.projects.project,
};
}
const projectDetailsWithHocLoading = withEditHoc(ProjectDetails, [actions.apiCall()]);
export default connect(mapStateToProps, null)(projectDetailsWithHocLoading);
I am a beginner to react. Please suggest a good way
mapStateToProps created for class components.
because you are using hooks, you should use useSelector hook
import { useSelector } from 'react-redux';
function ProjectDetails(props) {
const [projectValue, setValue] = useState({});
const proData = useSelector(state => state.projects.project)
return <div>{JSON.stringify(proData)}</div>;
}
const projectDetailsWithHocLoading = withEditHoc(ProjectDetails,actions.apiCall()]);
export default projectDetailsWithHocLoading;

React-Redux: Using action creators in React components

I am new to React/Redux, and appreciate your help. I am taking a Udemy course on this topic. The course instructor creates a component like this.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
export default connect(mapStateToProps, { fetchUser })(User)
my question: why inside the componentDidMount() he is prefixing fetchUsers() with this.props?
it is not the case that he is passing fetchUsers() as props from the parent component. This is how the parent is using this component <User userId={post.userId}/>
Note: this code works
It is because of this line :
export default connect(mapStateToProps, { fetchUser })(User)
the second parameter to connect is called mapDispatchToProps, It adds the actions to props
From the docs :
connect can accept an argument called mapDispatchToProps, which lets
you create functions that dispatch when called, and pass those
functions as props to your component.
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' })
}
}
Your code is using the “object shorthand” form.
The way the mapDispatchToProps in the example is shorthanded. It might be easier to tell what is going if it was written like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
const mapDispatchToProps = () => ({
fetchUser
});
export default connect(mapStateToProps, mapDispatchToProps)(User)
Maybe this shows it more clearly, but the dispatch function (fetchUser) is being mapped to the components properties. Just like the state value (user) is being mapped to the properties of the component. I think you just got confused because of the shorthand that was used.

React Redux Store updating, but component not re-rendering

Using the terminal to test my dispatched actions, Redux-logger shows that my state is being correctly updated. However, my component is not re-rendering as a result of the state change. I've looked at the SO answers regarding component not re-rendering, a majority of the responses claim that the state is being mutated; as a result, Redux won't re-render. However, I'm using Lodash's merge to deep-dup an object, I'm pretty sure I'm not returning a modified object. (Please see attached snippet below)
Would love to hear some advice from you guys, pulling my hair out on this one!
const usersReducer = (state = {}, action) => {
Object.freeze(state); // avoid mutating state
console.log(state);
// returns an empty object
let newState = merge({}, state);
console.log(newState);
// returns my state with my dispatched action object inside already???
// newState for some reason already has new dispatched action
switch (action.type) {
case RECEIVE_USER:
let newUser = {[action.user.id] = action.user};
return merge(newUser, newUser);
case RECEIVE_USERS:
newState = {};
action.users.forEach(user => {
newState[user.id] = user;
});
return merge({}, newState);
default:
return state;
}
};
React Container Component
import { connect } from 'react-redux';
import { receiveUsers, receiveUser, refreshAll, requestUsers, requestUser } from '../../actions/user_actions';
import allUsers from '../../reducers/selectors';
import UserList from './user_list';
const mapStateToProps = (state) => ({
users: allUsers(state), // allUsers (selector that takes the state specfically the user Object and returns an array of user Objects)
state
});
const mapDispatchToProps = (dispatch) => ({
requestUser: () => dispatch(requestUser()),
requestUsers: () => dispatch(requestUsers()),
receiveUsers: (users) => dispatch(receiveUsers(users)),
receiveUser: (user) => dispatch(receiveUser(user)),
refreshAll: (users) => dispatch(refreshAll(users))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList);
React Presentational component
import React from 'react';
class UserList extends React.Component {
render() {
const { users, state } = this.props;
const userItems = users.map((user, idx) => {
return(<li key={idx}>{user.username}</li>);
});
return (
<div>
<ul>
{ userItems }
</ul>
</div>
);
}
}
export default UserList;
React Store
import { createStore, applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import RootReducer from '../reducers/root_reducer';
const logger = createLogger();
const configureStore = (preloadedState = {}) => {
return createStore(
RootReducer,
preloadedState,
applyMiddleware(logger));
};
// const configureStore = createStore(rootReducer, applyMiddleware(logger));
// oddly enough, when I have the store as a constant and not a function that returns the store constant, dispatching actions through the terminal will correctly update the state and rerender the component
export default configureStore;
React Selector
const allUsers = ({ users }) => {
return Object.keys(users).map(id => (
users[id]
));
};
export default allUsers;
I had a similar problem, just in case someone stumbles upon this, I needed to clone the array in order to re-render the view:
export const addFieldRow = () => (
(dispatch: any, getState: any) => {
const state = getState();
const myArrayOfObjects = myArrayOfObjectsProp(state);
const newObject = {
key: "",
value: "",
};
myArrayOfObjects.push(newObject);
dispatch(addFieldRowAction({ myArrayOfObjects: [...myArrayOfObjects] })); <== here
}
);
Common problem in this case is using not reactive operations for changing state. For example use concat() for array, but not push() and so on.
I use this solution to do it.
I put the users on my state and update it on any change, with componentWillReceiveProps. Hope it helps :-)
class UserList extends React.Component {
constructor(props) {
super(props);
console.log(this.props);
this.state = {
users: props.users
};
}
componentWillReceiveProps(nextProps) {
if (this.props.users !== nextProps.users) {
this.setState({
users: nextProps.users,
});
}
}
render() {
const { users } = this.state;
const userItems = users.map((user, idx) => {
return(<li key={idx}>{user.username}</li>);
});
return (
<div>
<ul>
{ userItems }
</ul>
</div>
);
}
}
export default UserList;
What do your React components look like? Are you using internal state in them or props to push the data down. Usually I see the issue is with people setting the internal state of props with Redux state. You should be pushing props down to the components and they will re-render on update.
Also, check out
https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
to see if the props are really changing.
Create new copy of array from the prop state to re-render the component
render() {
const {allPost} = this.props;
//Use the spread operator to create a new copy of the array
const posts = [...allPost];
const plansList = () => {
return posts.length < 1 ? null : posts && <PlansList allPost={posts}
/>;
};
return (
<>
<Container className="mt-lg-5 pt-lg-5">
{plansList()}
</Container>
</>
);
}
i spent lot of time to find out that when using more than 1 reducer (with combineReducers), then your mapStateToProps should point the correct reducer names, for example
const mapStateToProps = state => ({
someVar: state.yourReducerName.someVar,
loading: state.yourReducerName.loading,
error: state.yourReducerName.error
});

Resources