Create redux reducer to change state nested object value - reactjs

I am new to React-Redux and i am struggling with a proof-of-concept project (list of movies) in order to learn the basics of redux. I am quite good at React, but Redux is another deal...
So, in my repo and specifically in Smart Component MoviesList.jsx (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/MoviesList.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) i am wondering how to dispatch the correct action to change the rating (increment, decrement) in each SingleMovieItem (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/SingleMovieItem.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) and what would be the correct reducer to achieve that. Could anyone help me with this?
In other words, to show a real example of what i mean...
Lets say that there is a main App and we make use of the
<Provider store={store}>
<App />
</Provider>
And then the App contains the following:
import React, { Component } from 'react';
import MoviesList from './MoviesList'
class App extends Component {
render() {
return (
<div>
<MoviesList />
</div>
)
}
}
export default App;
And then, the MovieList.jsd contains the following:
import React, { Component } from 'react';
import { addRating } from './actions';
import SingleMovieItem from './SingleMovieItem';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
allMovies: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(addRating(id))
};
};
class MovieList extends Component {
constructor(props) {
super(props);
}
handleIncrement = id => {
}
render() {
return (
<div>
<ul className="moviesList">
{this.props.allMovies.movies.map(movie => {
return (
<SingleMovieItem
key={movie.id}
movie={movie}
onIncrement={this.handleIncrement(movie.id)} />
);
})}
</ul>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MovieList);
What would be the reducer to increment rating of each singlemovie
if initialState is
const initialState = {
movies: [
{
id: "1",
title: "Harry Potter 1",
url: "harry-1.jpg",
rating: "2"
},
{
id: "2",
title: "Harry Potter 2",
url: "harry-2.jpg",
rating: "3"
}
]
};
Thanks in advance.

Related

Can't access state using 'useStoreState in react easy-peasy

I recently decided to learn about state management with easy-peasy, and followed along the basic tutorials, but i can't seem to access the state.
Here is the App component :-
import model from './model';
import Todo from './components/Todo.tsx';
import { StoreProvider, createStore } from 'easy-peasy';
const store = createStore(model);
function App() {
return (
<StoreProvider store={store}>
<div className="App">
<Todo />
</div>
</StoreProvider>
);
}
export default App;
Here is the model file 'model.js'
export default {
todos: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
};
And this is the Todo file:-
import React from 'react';
import {useStoreState } from 'easy-peasy';
const Todo = () => {
//The line below does not work for me, when i do 'state.todos' i get an error that todos does not exist on type
const todos = useStoreState(state=>state.todos);
return (
<div>
</div>
);
}
export default Todo;
Try removing the .todos so that
const todos = useStoreState(state=>state.todos);
turns into:
const todos = useStoreState(state=>state);
import React from 'react'
import { useStoreState } from 'easy-peasy';
import Feed from './Feed'
const Home = ({isLoading,fetchError}) => {
const { searchResults} = useStoreState((state)=>state.searchResults)
return (
{isLoading && Loading Posts ...};
{fetchError && <p className='statusMsg' style={{ color: "red" }}>{ fetchError }};
{!isLoading && !fetchError && (searchResults.length ? : No posts to display)}
)
}
export default Home;

Transferring data from one state object to another while using React context in a Next.js app

I'm building a pretty simple restaurant website using React and Next.js. I have a home page and an 'order' page, which renders a menu from a state object ('menuitems'). I want the user to be able to add items to their 'cart', which is another state object. So ultimately I'm transferring data from the static 'menuitems' state to the 'cart.'
What I can't figure out is how I can update the 'cart' state from my 'order' page.
I've set up a context for the app going off of this guide. I've been able to successfully access the menu using a call to the custom hook 'useAppContext()' but I no longer have access to the updater functions provided through useState() or useContext() calls I previously used when I built something similar with everything in a single file (you can see in my code below where I invoke the now-undefined setCartItem() function).
How can I update the 'cartitems' state from inside of my 'order' component?
File where I have my context object:
import { createContext, useContext } from "react";
const AppContext = createContext();
export function AppWrapper({ children }) {
const state = {
menuitems: [
{
title: "Spagett",
description: "Our finest spagett made with homemade meatballs and sauce.",
},
{
title: "Sandwich",
description: "You gotta try this sandwich",
},
{
title: "BFB",
description: "Watch out for your toilet after this one bro",
},
],
cartitems: []
}
return (
<AppContext.Provider
value={state}
>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
My _app.js file:
import "../styles/globals.css";
import { AppWrapper } from "./context/state";
function MyApp({ Component, pageProps }) {
return (
<AppWrapper>
<Component {...pageProps} />
</AppWrapper>
);
}
export default MyApp;
Finally, my 'order' component, where I am trying to update the 'cartitems' state:
import Link from "next/link";
import { useAppContext } from "./context/state";
//IMPORT CONTEXT
const OrderPage = () => {
const { menuitems } = useAppContext();
const { cartitems } = useAppContext();
const renderedMenu = menuitems.map((item) => (
<div key={item.name} className="order-item">
<h4>{item.title}</h4>
<p>{item.description}</p>
<button onClick={() => setCartItem([...cartitems, item.title])}>
Add Item
</button>
</div>
));
return (
<div>
<Link href="/">
<a>Go home</a>
</Link>
<div>{renderedMenu}</div>
</div>
);
};
export default OrderPage;
Create a state in your provider, then pass setCartItems into your context.
export function AppWrapper({ children }) {
const [ cartItems, setCardItems ] = useState([])
const state = {
menuitems: [
{
title: "Spagett",
description: "Our finest spagett made with homemade meatballs and sauce.",
},
{
title: "Sandwich",
description: "You gotta try this sandwich",
},
{
title: "BFB",
description: "Watch out for your toilet after this one bro",
},
],
cartitems: []
}
return (
<AppContext.Provider
value={{ state, cartItems, setCardItems }}
>
{children}
</AppContext.Provider>
);
}
You can then use it this way.
const { state, cartItems, setCartitems } = useAppContext();

Could not find "store" in the context of "Connect(Component)" GatsbyJs Redux

I ran into some issues with my Gatsby web application when trying to implement a store for global states with Redux. I am new to both. Before I worked with MobX and "plain" React.
The problem is, that I cannot access the data of my store from my components. I use the Redux Provider class as I read in several tutorials, but as I make use of other providers as well, my case seems to be special... This is what I came up with so far:
gatsby-ssr.js and gatsby-browser.js
import prepPages from "./prepPages"
export const wrapRootElement = prepPages
prepPages.js
import React from "react"
import { createGlobalStyle, ThemeProvider } from "styled-components"
import { Provider } from "react-redux"
import createStore from "./src/state/store"
import { MDXProvider } from "#mdx-js/react"
import { Table } from "./src/components"
import Theme from "./src/themes/theme"
//Provider for my global styling
const GlobalStyles = createGlobalStyle`...`
//Overriding the table component
const components = {
table: Table
}
export default ({ element }) => {
const store = createStore()
return(
<Provider store={store}>
<MDXProvider components={components}>
<ThemeProvider theme={Theme}>
<GlobalStyles/>
{element}
</ThemeProvider>
</MDXProvider>
</Provider>
)
}
store.js
import {createStore as reduxCreateStore} from "redux"
const initialState = {
loggedIn: false,
menuToggleOn: false,
//other initial states
}
const reducer = (state, action, dispatch) => {
//Toggles
if(action.type === 'TOGGLE_MENU'){
return {
...state,
toggleMenuOn: !state.toggleMenuOn
}
}
//other actions
}
const createStore = () => reduxCreateStore(reducer, initialState);
export default createStore;
components/Nav.js
import React from 'react';
import {useStaticQuery, Link, graphql} from "gatsby";
import {NavWrapper} from "../styles";
import { Button } from "./Button";
import {FontAwesomeIcon} from "#fortawesome/react-fontawesome";
import {faBars} from "#fortawesome/free-solid-svg-icons";
import connect from "react-redux/lib/connect/connect";
import PropTypes from "prop-types"
const NavHolder = ({loggedIn, toggleMenu, toggleMenuOn}) => {
...
//defining the nav items depending on the login state
var items;
var cta = {};
if(loggedIn){
items = [
{name: "home", ref: "/", key: 0},
{name: "wiki", ref: "/wiki", key: 1},
{name: "workspace", ref: "/workspace", key: 2}
]
}else {
items = [
{name: "about", ref: "#about", key: 0},
{name: "features", ref: "#features", key: 1},
{name: "download", ref: "#download", key: 2},
{name: "contact", ref: "#contact", key: 3}
];
cta = {exists: true, name: "login"}
}
//mapping the nav items and adding the visible class if the menu is toggled on
let navItems = items.map((item) =>
<li key={item.key} className={toggleMenuOn ? "nav-item visible" : "nav-item"}>
<a href={item.ref} className={isActive ? "active":""}>
{item.name}
</a>
</li>
)
return (
<NavWrapper>
<ul>
<li>
<Link to="/" id="logo">...</Link>
</li>
{navItems}
<li className="nav-toggle">
<a href="/" onClick={toggleMenu}>
<FontAwesomeIcon icon={faBars}/>
</a>
</li>
{cta.exists && (
<li className="nav-cta">
<Button color="petrol" href="login">{cta.name}</Button>
</li>
)}
</ul>
</NavWrapper>
)
}
NavHolder.propTypes = {
loggedIn: PropTypes.bool.isRequired,
menuToggleOn: PropTypes.bool.isRequired,
toggleMenu: PropTypes.func.isRequired
}
const mapStateToProps = ({loggedIn, toggleMenuOn}) => {
return { loggedIn, toggleMenuOn }
}
const mapDispatchToProps = dispatch => {
return { toggleMenu: () => dispatch({ type: `TOGGLE_MENU` }) }
}
const ConnectedNav = connect(mapStateToProps, mapDispatchToProps)(NavHolder)
export const Nav = () => {
return <ConnectedNav/>
}
I thought this might work, but I get this error:
Error: Could not find "store" in the context of "Connect(NavHolder)". Either wrap the root component in a Provider, or pass a custom React context provider to Provider and the corresponding React context consumer to Connect(NavHolder) in connect options.
could not find store in component error
Has anyone an idea where I went wrong? I am really grateful for any help.
Thanks :)
With Gatsby you need to use wrapRootElement API.
Wrap the root element in your Gatsby markup once using wrapRootElement, an API supporting both Gatsby’s server rendering and browser JavaScript processes.
Refer to Adding a Redux Store in Gatsby docs, there is an example repo for that.

creating a function that will move an object from one array (of objects) to another each time a button of the selected product is pressed

I want to create a function that will move an object from one array (of objects) to another each time a button of the selected product is pressed
The shopping list will display in the HomePage component (1 array of objects)
The cart list of the selected products will display in the Cart component (additional array)
Did I defined something in a wrong way in the state? or in the return?
its really important for me to keep it the simple way like I did, because I want to understand before I move on... also with no routers or hooks!
thanks! (:
App.js
import React, { Component } from 'react'
import Cart from './components/Cart.js';
import HomePage from './components/HomePage.js';
import './App.css';
export default class App extends Component {
state = {
productsList: [
{ product: 'T-shirt', cost: '12$' },
{ product: 'Shoes', cost: '45$' },
{ product: 'Haircut Machine', cost: '60$' }
],
cartList: [{ product: '', cost: '' }]
}
add = () => {
this.props.add(this.state.index)
}
addToCart = (i) => {
cartList = this.state.productsList.filter((element, index) => (index != i));
this.setState({ productsList: [...cartList] })
};
render() {
return (
<div className="App">
{this.state.productsList.map((element) => {
return <HomePage product={element.product} cost={element.cost} index={i} add={this.addToCart} />
})}
<Cart />
</div>
)
}
}
HomePage.js
import React, { Component } from 'react'
export default class HomePage extends Component {
constructor(props) {
super(props)
this.state = {
product: '',
cost: '',
index: props.index
}
}
render(props) {
return (
<div>
<div>Price: {this.props.product}</div>
<div>Product: {this.props.cost}</div>
<button onClick={this.add}>Add To Cart</button>
</div>
)
}
}
Cart.js (This component will hold and show the new products objects array)
import React, { Component } from 'react'
export default class Cart extends Component {
render() {
return (
<div>
</div>
)
}
}

Implementing React Redux

I am slowly learning React and also learning to implement it with Redux. But I seem to have hit a road block. So this is what I have so far.
/index.jsx
import './main.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App.jsx'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import ShoppingList from './reducers/reducer'
let store = createStore(ShoppingList)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
/actions/items.js
import uuid from 'node-uuid'
export const CREATE_ITEM = 'CREATE_ITEM'
export function createItem(item) {
return {
type: CREATE_ITEM,
item: {
id: uuid.v4(),
item,
checked: false
}
}
}
/reducers/reducer.js
import * as types from '../actions/items'
import uuid from 'node-uuid'
const initialState = []
const items = (state = initialState, action) => {
switch (action.type) {
case types.CREATE_ITEM:
return {
id: uuid.v4(),
...item
}
default:
return state;
}
}
export default items
/reducers/index.js
UPDATE:
import { combineReducers } from 'redux'
import items from './reducer'
const ShoppingList = combineReducers({
items
})
export default ShoppingList
/components/Item.jsx
import React from 'react';
import uuid from 'node-uuid'
export default class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
isEditing: false
}
}
render() {
if(this.state.isEditing) {
return this.renderEdit();
}
return this.renderItem();
}
renderEdit = () => {
return (
<input type="text"
ref={(event) =>
(event ? event.selectionStart = this.props.text.length : null)
}
autoFocus={true}
defaultValue={this.props.text}
onBlur={this.finishEdit}
onKeyPress={this.checkEnter}
/>
)
};
renderDelete = () => {
return <button onClick={this.props.onDelete}>x</button>;
};
renderItem = () => {
const onDelete = this.props.onDelete;
return (
<div onClick={this.edit}>
<span>{this.props.text}</span>
{onDelete ? this.renderDelete() : null }
</div>
);
};
edit = () => {
this.setState({
isEditing: true
});
};
checkEnter = (e) => {
if(e.key === 'Enter') {
this.finishEdit(e);
}
};
finishEdit = (e) => {
const value = e.target.value;
if(this.props.onEdit) {
this.props.onEdit(value);
this.setState({
isEditing: false
});
}
};
}
/components/Items.jsx
import React from 'react';
import Item from './Item.jsx';
export default ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
)}</ul>
);
}
// UPDATE: http://redux.js.org/docs/basics/UsageWithReact.html
// Is this necessary?
const mapStateToProps = (state) => {
return {
state
}
}
Items = connect(
mapStateToPros
)(Items) // `SyntaxError app/components/Items.jsx: "Items" is read-only`
//////////////////////////////////////
// Also tried it this way.
//////////////////////////////////////
Items = connect()(Items)
export default Items // same error as above.
Tried this as well
export default connect(
state => ({
items: store.items
})
)(Items) // `Uncaught TypeError: Cannot read property 'items' of undefined`
UPDATE:
After many attempts #hedgerh in Gitter pointed out that it should be state.items instead. so the solution was
export default connect(
state => ({
items: state.items
})
)(Items)
credits to #azium as well.
/components/App.jsx
export default class App extends React.Component {
render() {
return (
<div>
<button onClick={this.addItem}>+</button>
<Items />
</div>
);
}
}
What am I missing here in order to implement it correctly? Right now it breaks saying that Uncaught TypeError: Cannot read property 'map' of undefined in Items.jsx. I guess it makes sense since it doesn't seem to be hooked up correctly. This is the first part of the app, where the second will allow an user to create a many lists, and these lists having many items. I will probably have to extract the methods from Item.jsx since the List.jsx will do pretty much the same thing. Thanks
You're missing connect. That's how stuff gets from your store to your components. Read the containers section from the docs http://redux.js.org/docs/basics/UsageWithReact.html
import React from 'react'
import Item from './Item.jsx'
import { connect } from 'react-redux'
let Items = ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
})
</ul>
)
}
export default connect(
state => ({
items: state.items
})
)(Items)
Also you seem to be expecting onEdit and onDelete functions passed from a parent but you're not doing that so those functions will be undefined.

Resources