Redux how to access state - reactjs

I'm having trouble accessing the state in one of my components. I have a component where a user adds a name and a weight and then submits it. They are then redirected to the Home Page. What I want to happen is for the name that was inputed to be displayed on the Home Page. I can see the state updating, but I can't figure out how to access the state and have the name show on the Home Page. Any help would be appreciated.
Here is my Home Page component:
const HomePage = () => {
const classes = useStyles();
const name = useSelector(state => state.move.name);
const displayMovementButtons = () => {
if (name) {
return (
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
>
<div className={classes.movementName} >{name}</div>
</Button>
)
};
return <div className={classes.noMovementsMessage} >Click add button to begin</div>
};
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<div>{displayMovementButtons()}</div>
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);
export default compose(withConnect)(HomePage);
Here is my reducer, where I think the problem is:
const initialState = []
const addMovementReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_MOVEMENT:
return [ ...state, {name: action.name, weight: action.weight} ]
default:
return state;
}
};
export default addMovementReducer;
Here is a screenshot showing the state (note: I added multiple names and weights, I would eventually like each 'name' to appear on the Home Page):

Your move branch of state is an array. You can't access the name by state.move.name. Instead of this you can get an array of movements from redux store and render them with Array.map() method.
const MovementButtons = ({ movements }) => {
return (
<div>
{
movements.map(({ name, weight }) => {
if (name) {
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
key={name}
>
<div className={classes.movementName}>{name}</div>
</Button>
}
return (
<div className={classes.noMovementsMessage}>Click add button to begin</div>
)
})
}
</div>
);
}
const HomePage = () => {
const classes = useStyles();
const movements = useSelector(state => state.move);
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<MovementButtons movements={movements} />
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);

Related

How to use this to target a item in a mapped list in react

I am still pretty new and am doing a simple food ordering app in React. I have a file of dummy meals and am able to create a cart through user input, create a modal on the "Your Cart" button from the header, and map through the cart displaying as a list, but I am unable to figure out how to delete an item from the cart. I realize I need to target the id of the list item that the X is connected to, but for some reason I can't get the this command to work.
Is there something about the this command that I am not understanding when it comes to a mapped list item? So far I have both tried this through my CartModal component and the Home component, but both turn up undefined when using this and I throw an error when using e.target (enter different options like e.target.value, e.target.id, etc...), and have even tried working on an inline targeting (which is where the code below is at currently) but still comes up nothing.
I will add a couple dummy meals to this as well.
Thanks so much!
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "reset":
return initialState;
default:
return state;
}
};
export const CountContext = React.createContext();
const Home = (props) => {
const [cart, setCart] = useState([]);
const [count, dispatch] = useReducer(reducer, initialState);
const {isVisible, toggleModal} = useModal();
let totalPrice;
let cartCounter ;
const cartUpdaterHandler = (cartItem) =>{
setCart([...cart, cartItem]);
cartItem= cartItem;
}
useEffect(() => {
cartCounter = cart.length;
totalPrice = cart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2);
}, [cart])
const modalHandler = () => {
toggleModal(true)
}
const removeCI = () => {
console.log(cart)
}
return (
<CountContext.Provider
value={{ countState: count,
countDispatch: dispatch,
currentCart: cart
}}
className={classes.contextProvider}
>
{isVisible && (
<CartModal
overlayClassName="custom_overlay"
isVisible={isVisible}
hideModal={toggleModal}
currentCart={cart}
totalPrice={totalPrice}
remove={removeCI}
/>
)}
<Header cart={cart} cartCounter={cart} modal={modalHandler}/>
<Intro />
<MealForm onAdd={cartUpdaterHandler} />
{/* <Cart /> */}
{/* <CartModal isVisible={isVisible} hideModal={toggleModal} currentCart={cart} totalPrice={totalPrice}/> */}
</CountContext.Provider>
)
}
export default Home;
const CartModal = ({ isVisible, hideModal, currentCart, remove}) => {
// const checkKey = () => {
// console.log(cartItem.id)
// console.log(this.id)
// }
return isVisible
? createPortal(
<Card className={classes.modal}>
<div className={classes.modalBackground}>
<div className={classes.modalDiv}>
<div className={classes.modalHeader}>
<h5 className={classes.modalHeaderH5}>Your Cart</h5>
<button className={classes.modalHeaderX} onClick={hideModal}> X </button>
</div>
<div className={classes.modalBody}>
{currentCart.map((cartItem, i) => {
return<div key={Math.random()}>
<ul className={classes.cartModalItem} key={Math.random()}>
<div className={classes.cartModalItemInfo} >
<li className={classes.cartModalName}>{cartItem.name}</li>
<li className={classes.cartModalPrice}>{cartItem.price}</li>
<li className={classes.cartModalDesc}>{cartItem.description}</li>
</div>
//I am asking about the X below this.
<button className={classes.cartModalX} onClick={()=>{console.log(this)}}>X</button>
</ul>
</div>
})}
<h5 className={classes.modalTotal}>Total:{currentCart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2)} </h5>
</div>
<div className={classes.modalFooter}>
<button className={classes.closeModalButton} onClick={hideModal}> Close </button>
<button className={classes.orderModalButton}>Checkout</button>
</div>
</div>
</div>
</Card>,
document.body,
)
: null;
};
export default CartModal;
const AvalibleMeals = [
{
id: 'm1',
name: 'Sushi',
description: 'Finest fish and veggies',
price: 22.99,
},
{
id: 'm2',
name: 'Schnitzel',
description: 'A german specialty!',
price: 16.5,
},
{
id: 'm3',
name: 'Barbecue Burger',
description: 'American, raw, meaty',
price: 12.99,
},
{
id: 'm4',
name: 'Green Bowl',
description: 'Healthy...and green...',
price: 18.99,
},
];
export default AvalibleMeals;

Props is an empty object, so i cannot props.match.params.id

As said in title, my props is an empty object.
This is my component, in which i want to match a proper object in mapStateToProps.
The matching object exists, because when i pass and x.id === 1 , the object is being rendered.
const UserDetails = ({ episode, history }, props) => {
const { id } = useParams();
// const handleDelete = (id) => {
// if (window.confirm("Are you sure wanted to delete the episode ?")) {
// dispatch(deleteEpisode(id));
// }
// };
console.log("hej", props); // it prints empty object
console.log(episode);
return (
<div>
{episode ? (
<div>
<button onClick={() => history.push("/episodes")}>...back</button>
<h1> Tytuł: {episode.title}</h1>
<h3> Data wydania: {episode.release_date}</h3>
<h3> Series: {episode.series} </h3>
<img src={episode.img} alt="episode" />
{/* <div>
{episode.episode.characters.map((id) => {
const properActor = users.find((el) => el.id == id);
return <div>{`${properActor.name} `}</div>;
})}
</div> */}
<button onClick={() => history.push(`/episode/edit/${episode.id}`)}>
Edit
</button>
{/* <button onClick={() => handleDelete(episode.id)}>Delete</button> */}
<div>
<Link to={`/episodes/${episode.id}/addCharacter`}>
<button type="button">Dodaj postać do: {episode.title}</button>
</Link>
</div>
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};
const mapStateToProps = (state, props) => {
return {
episode: state.episodes.episodes
? state.episodes.episodes.find((x) => x.id === props.match.params.id)
: null,
};
};
export default withRouter(connect(mapStateToProps, null)(UserDetails));
for anyone, who woudl like to see the whole project:
https://stackblitz.com/edit/node-fxxjko?file=db.json
hope it works,
to run the database u have to install npm json-server and run
EDIT:
If i do something like this:
const mapStateToProps = (state, props) => {
console.group("mapStateToProps");
console.log(props); // Does props.match.params.id exist? What is it?
console.log(state.episodes.episodes); // Is there an object in this array whose id matches the above?
console.groupEnd();
return {
episode: state.episodes.episodes
? state.episodes.episodes.find(
(x) => x.episodeId === props.match.params.episodeId
)
: null,
};
};
i see the this result:
https://imgur.com/a/ssrJjHV
You are not properly destructuring the rest of your props. Implement ellipsis to get the rest of the props back
It should be:
const UserDetails = ({ episode, history, ...props}) => {
//rest of your code
}
not:
const UserDetails = ({ episode, history }, props) => {
//rest of your code
}

React JS Hooks with useState call same function but with params

I'm finding I'm having to duplicate the same function over again when I could write one function that handles different parameters. I can't get it to work so I wondered if someone could point me in the right direction? Below shows only 2 functions but in reality I've got many that all do the same thing.
import React, {useState} from "react"
const Section = ({ children }) => {
return (
<>
<Wrapper children = {children} />
</>
);
};
const HandlePages = () => {
const [showPageFooDialogue, setShowPageFooDialogue] = useState(false);
const [showPageBarDialogue, setShowPageBarDialogue] = useState(false);
const [currentDialogue, setCurrentDialogue] = useState(0);
{showPageFooDialogue && (
<Section>
<Headers heading = {"Foo Title"} currentDialogue = {currentDialogue} pages = {fooContents.length} />
{fooContents[currentDialogue]}
</Section>
)}
)
{showPageBarDialogue && (
<Section>
<Headers heading = {"Bar Title"} currentDialogue = {currentDialogue} pages = {barContents.length} />
{barContents[currentDialogue]}
</Section>
)}
)
}
const fooContents = [
//Lots of functionality specific to foo listed as the children of this function
];
const barContents = [
//Lots of functionality specific to bar listed as the children of this function
];
return (
<button onClick={() => setShowPageFooDialogue(true)}>Click for Page Foo</button>
<button onClick={() => setShowPageBarDialogue(true)}>Click for Page Bar</button>
)
}
export default HandlePages
Basically where I've got
const [showPageFooDialogue, setShowPageFooDialogue] = useState(false);
const [showPageBarDialogue, setShowPageBarDialogue] = useState(false);
I need just one function such as this but somehow pass 2 parameters to it:
const [showPageGenericDialogue, setShowPageGenericDialogue] = useState(false);
and where I've got:
{showPageFooDialogue && (
<Section>
<Headers heading = {"Foo Title"} currentDialogue = {currentDialogue} pages = {fooContents.length} />
{fooContents[currentDialogue]}
</Section>
)}
)
{showPageBarDialogue && (
<Section>
<Headers heading = {"Bar Title"} currentDialogue = {currentDialogue} pages = {fooContents.length} />
{barContents[currentDialogue]}
</Section>
)}
)
}
I need just one function with 2 parameters for "Foo or Bar Title" (param1) and fooContents or barContents (param2):
{showPageGenericDialogue && (
<Section>
<Headers heading = {param1} currentDialogue = {currentDialogue} pages = {param2.length} />
{param2[currentDialogue]}
</Section>
)}
)
}
And then finally the buttons:
<button onClick={() => setShowPageFooDialogue(true)}>Click for Page Foo</button>
<button onClick={() => setShowPageBarDialogue(true)}>Click for Page Bar</button>
should just pass the parameters something like:
<button onClick={() => setShowPageGenericDialogue(true, fooParam1, fooParam2)}>Click for Page Foo</button>
<button onClick={() => setShowPageGenericDialogue(true, barParam1, barParam2)}>Click for Page Bar</button>
I've looked at various solutions but due to my limitations, I cannot apply them to this.
Any ideas?
Thanks.
You can use reducer:
https://reactjs.org/docs/hooks-reference.html#usereducer
It is a simple example, you can modify as per your needs.
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
Or Take a look at react-redux.
EDIT
If you need functionality like you asked one function with multiple parameter
you can try to give a default state to your useState hook:
const [myFooBar, setFoo] = useState(
{
'foo': false,
'bar': false
}
);
Changing the values:
setFoo(
prevState => ({
...prevState,
'foo': true
})
);
setFoo(
prevState => ({
...prevState,
'bar': true
})
);
Access it like this:
myFooBar.foo
myFooBar.bar

Load state/data before render?

I have implemented the authentication part of my app (built using the MERN stack). The login action validates login data, then loads user data, then pushes the route to /dashboard. On the dashboard page, I have a simple Welcome to the dashboard, {email}! however I am getting an error telling me that it can not return data of null. I also have the users First & Last name as well as their email in the navbar, that also spawns an error of returning null data. I have a useEffect that loads the user data in my App.js but i'm still receiving the null errors.
Is there a way to load the data prior to render?
Index.js
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</PersistGate>
</Provider>,
document.getElementById('root')
);
App.js
const App = () => {
const [loading, setLoading] = useState(true);
const dispatch = useDispatch();
useEffect(() => {
// check for token in LS
if (localStorage.token) {
setAuthToken(localStorage.token);
}
dispatch(attemptGetUser())
.then(() => setLoading(false))
.catch(() => setLoading(false));
// Logout user from all tabs if they logout in another tab
window.addEventListener('storage', () => {
if (!localStorage.token) dispatch({ type: LOGOUT });
});
// eslint-disable-next-line
}, []);
return loading ? (
<Loading cover="page" />
) : (
<div className="App">
<Switch>
<Route path="/" component={Views} />
</Switch>
</div>
);
};
redux/thunks/Auth.js
export const attemptLogin = (formData) => async (dispatch) => {
await postLogin(formData)
.then((res) => {
dispatch(login(res.data));
dispatch(push('/dashboard'));
})
.then(() => {
dispatch(attemptGetUser());
})
.catch((error) => {
const errors = error.response.data.message;
dispatch(setAlert('Uh-oh!', errors, 'error'));
});
};
redux/thunks/User.js
export const attemptGetUser = () => async (dispatch) => {
await getUser()
.then((res) => {
dispatch(setUser(res.data));
})
.catch((error) => {
const errors = error.response.data.message;
console.log(errors);
dispatch(setAlert('Uh-oh!', errors, 'danger'));
});
};
views/app-views/dashboard
const Dashboard = () => {
const { email } = useSelector((state) => state.user.user);
return (
<div>
Welcome to the dashboard,
<strong>{email}</strong>!
</div>
);
};
export default Dashboard;
components/layout-components/NavProfile.js
export const NavProfile = () => {
const { firstName, lastName, email } = useSelector(
(state) => state.user.user
);
const dispatch = useDispatch();
const onLogout = () => {
dispatch(attemptLogout());
};
const profileImg = '/img/avatars/thumb-1.jpg';
const profileMenu = (
<div className="nav-profile nav-dropdown">
<div className="nav-profile-header">
<div className="d-flex">
<Avatar size={45} src={profileImg} />
<div className="pl-3">
<h4 className="mb-0">{firstName} {lastName}</h4>
<span className="text-muted">{email}</span>
</div>
</div>
</div>
<div className="nav-profile-body">
<Menu>
{menuItem.map((el, i) => {
return (
<Menu.Item key={i}>
<a href={el.path}>
<Icon className="mr-3" type={el.icon} />
<span className="font-weight-normal">{el.title}</span>
</a>
</Menu.Item>
);
})}
<Menu.Item key={menuItem.legth + 1} onClick={onLogout}>
<span>
<LogoutOutlined className="mr-3" />
<span className="font-weight-normal">Logout</span>
</span>
</Menu.Item>
</Menu>
</div>
</div>
);
return (
<Dropdown placement="bottomRight" overlay={profileMenu} trigger={['click']}>
<Menu className="d-flex align-item-center" mode="horizontal">
<Menu.Item>
<Avatar src={profileImg} />
</Menu.Item>
</Menu>
</Dropdown>
);
};
export default NavProfile;
So the error is telling you that in your redux state that state.user.user is undefined, this is why you can't destructure firstName, lastName, email values.
If in your store state.user.user is at least a defined, empty object ({}) then the access of null errors should resolve.
const userReducer = (state = { user: {} }, action) => {
...
}
This can still potentially leave your UI rendering "undefined", so in the component code you'll want to provide default values, i.e.
const { firstName = '', lastName = '', email = '' } = useSelector(
(state) => state.user.user
);
The alternative is to have fully qualified initial state in your user reducer slice.
const initialState = {
user: {
firstName: '',
lastName: '',
email: '',
},
};
const userReducer = (state = initialState, action) => {
...
}
Seems like you could fix this by changing your Redux store initial state.
Taking your Dashboard component as an example:
const Dashboard = () => {
const { email } = useSelector((state) => state.user.user);
return (
<div>
Welcome to the dashboard,
<strong>{email}</strong>!
</div>
);
};
It expects that there is a user object with an email string in the user slice of state in your Redux store. As noted in their documentation
You could update your createStore call to include a initial value for the redux store like createStore({'user': {'user': {'email': ''}}}); for example

How to derive loading state when using lazy and suspense in React?

I am trying to build a lazy loading tabs component that fetches the code/bundle for the tab only when it is clicked. I am trying to use lazy+suspense for it. I would like to animate/color the tab the user clicked on when it is loading to indicate that a tab is being loaded. How can I do that best?
Here is some example code I have whipped up. The bug here is that the tab-header can sometimes get rendered twice when the code is being loaded. How can I avoid the issue and display a loading state on the new tab.
import React, {lazy, Suspense, useState, useReducer} from "react";
import "./App.css";
import classNames from "classnames";
import Spinner from "./Spinner";
const Tokyo = lazy(() => {
return import("./Tokyo");
});
const Mexico = lazy(() => {
return import("./Mexico");
});
const London = lazy(() => {
return import("./London");
});
const App = () => {
const [_, setTab] = useState("Tokyo");
return (
<div className="App">
<header className="App-header">
<Tabs initialTab="Tokyo" onTabChange={setTab}>
<Tab id="Tokyo" name="Tokyo">
<Tokyo />
</Tab>
<Tab id="Mexico" name="Mexico">
<Mexico />
</Tab>
<Tab id="London" name="London">
<London />
</Tab>
</Tabs>
</header>
</div>
);
};
const Tab = () => {
return null;
};
function genClickLog(log, current) {
const set = new Set([current]);
const newLog = [current];
log.forEach(l => {
if (!set.has(l)) {
log.push(l);
newLog.push(l);
}
});
return newLog;
}
function createSuspenseTree(targetTab, log, child, tabs, handleTabChange) {
const head = log.shift();
if (head !== targetTab) {
console.warn(`expect ${head} to be ${targetTab}`);
}
let current = child;
log.forEach(l => {
current = (
<Suspense
fallback={
<Fallback
tabs={tabs}
prevTab={l}
activeTab={targetTab}
onTabChange={handleTabChange}
/>
}
>
{current}
</Suspense>
);
});
return <Suspense fallback={<Spinner />}>{current}</Suspense>;
}
function reducer(state, action) {
switch (action.type) {
case "change":
if (state.current === action.id) {
return state;
}
return {
current: action.id,
prev: state.current,
clickLog: genClickLog(state.clickLog, action.id),
};
case "initial":
return {
current: action.id,
prev: null,
clickLog: [action.id],
};
default:
throw new Error("bad reducer action");
}
}
const Tabs = props => {
const {children, onTabChange, initialTab} = props;
const [state, dispatch] = useReducer(
reducer,
{
clickLog: [],
prev: null,
current: null,
},
{type: "initial", id: initialTab}
);
const handleTabChange = tab => {
dispatch({type: "change", id: tab});
onTabChange(tab);
};
const tabs = React.Children.map(children, x => ({
id: x.props.id,
name: x.props.name,
render: x.props.children,
}));
const child = (
<>
<TabHeader
tabs={tabs}
activeTab={state.current}
onTabChange={handleTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={state.current}
render={x.render}
/>
</div>
))}
</>
);
return (
<div className="TabContainer">
{createSuspenseTree(
state.current,
[...state.clickLog],
child,
tabs,
handleTabChange
)}
</div>
);
};
const Fallback = props => {
const {prevTab, activeTab, onTabChange, tabs} = props;
if (prevTab && prevTab !== activeTab) {
return (
<>
<TabHeader
tabs={tabs}
activeTab={prevTab}
loadingTab={activeTab}
onTabChange={onTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={prevTab}
render={x.render}
/>
</div>
))}
</>
);
}
return <Spinner />;
};
const TabFrag = props => {
if (props.id === props.activeTab) {
return props.render;
}
return null;
};
const TabHeader = props => {
const {tabs, activeTab, loadingTab, onTabChange} = props;
return (
<div className="TabHeader">
{tabs.map(x => (
<TabItem
id={x.id}
key={x.id}
name={x.name}
active={x.id === activeTab}
loading={x.id === loadingTab}
onTabChange={onTabChange}
/>
))}
</div>
);
};
const TabItem = props => {
const {id, name, loading, active, onTabChange} = props;
const handleTabChange = () => {
onTabChange(id);
};
return (
<div
className={classNames("TabItem", {
ActiveTab: active,
LoadingTab: loading,
})}
onClick={handleTabChange}
>
{name}
</div>
);
};

Resources