Unable to trigger function from context api with react hooks - javascript

I'm trying to trigger a function from my CartContext Api upon a click, but it isn't happening. I have checked the method and it works, but when I add the context function it doesn't do anything... see below code:
Context file
import React, { useState } from 'react';
export const CartContext = React.createContext({
cart: [],
setCart: () => {},
});
const CartContextProvider = (props) => {
const [updateCart, setUdatedCart] = useState();
const updateCartHandler = () => {
console.log('click');
};
return (
<CartContext.Provider
value={{ cart: updateCart, setCart: updateCartHandler }}
>
{props.children}
</CartContext.Provider>
);
};
export default CartContextProvider;
Component where Im using the context:
import React, { useContext } from 'react';
import classes from './SingleProduct.css';
import AddToCartBtn from './AddToCartBtn/AddtoCartBtn';
import { CartContext } from '../context/cart-context';
const singleProduct = (props) => {
const cartContext = useContext(CartContext);
const addToCart = (id, productName, price, qty) => {
const productInCart = {
productId: id,
productName: productName,
productPrice: price,
productQty: qty,
};
cartContext.setCart();
};
return (
<article className={classes.SingleProduct}>
<div className={classes.ProductImgContainer}>
<img src={props.productImg} alt="" />
</div>
<div className={classes.ProductTitle}>
<h2>{props.productName}</h2>
</div>
<AddToCartBtn
clicked={() => {
addToCart(
props.productId,
props.productName,
props.productPrice,
props.productQty
);
}}
/>
</article>
);
};
export default singleProduct;
I'm just adding a console.log('click') to check if the method triggers at the moment. By the way, when I console.log the context variable it contains the properties and works. Any ideas why this isn't happening

Forgot to wrap the component with provider thanks!

Related

LocalStorage doesn't set items into itself

I've got a bug with LocalStorage on react.js. I try to set a todo into it, but it doesn't load. This is the code:
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if (storedTodos) setTodos(storedTodos)
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
function HandleAddTodo(e){
const name = TodoNameRef.current.value
if (name==='') return
setTodos(prevTodos => {
return[...prevTodos, { id:uuidv4(), name:name, complete:false}]
})
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
This is TodoList.js
import React from 'react'
import Todo from './Todo';
export default function TodoList({ todos }) {
return (
todos.map(todo =>{
return <Todo key ={todo.id} todo={todo} />
})
)
}
And as last Todo.js:
import React from 'react'
export default function Todo({ todo }) {
return (
<div>
<label>
<input type="checkbox" checked={todo.complete}/>
{todo.name}
</label>
</div>
)
}
What the code has to do is load a todo into the local storage, and after refreshing the page reload it into the document. The code I implemented
I just started with react but I hope anyone can pass me the right code to make it work. If anyone need extra explenation, say it to me.
Kind regards, anonymous
Try to decouple your local storage logic into it's own react hook. That way you can handle getting and setting the state and updating the local storage along the way, and more importantly, reuse it over multiple components.
The example below is way to implement this with a custom hook.
const useLocalStorage = (storageKey, defaultValue = null) => {
const [storage, setStorage] = useState(() => {
const storedData = localStorage.getItem(storageKey);
if (storedData === null) {
return defaultValue;
}
try {
const parsedStoredData = JSON.parse(storedData);
return parsedStoredData;
} catch(error) {
console.error(error);
return defaultValue;
}
});
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(storage));
}, [storage]);
return [storage, setStorage];
};
export default useLocalStorage;
And you'll use it just like how you would use a useState hook. (Under the surface it is not really more than a state with some side effects.)
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useLocalStorage(LOCAL_STORAGE_KEY, []);
const handleAddTodo = event => {
setTodos(prevTodos => {
return[...prevTodos, {
id: uuidv4(),
name,
complete: false
}]
})
};
return (
<button onClick={HandleAddTodo}>Add todo</button>
);
}
You added the getItem and setItem methods of localStorage in two useEffect hooks.
The following code intializes the todo value in localStorage when reloading the page.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
So you need to set the todo value in HandleAddTodo event.
I edited your code and look forward it will help you.
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storageItem = localStorage.getItem(LOCAL_STORAGE_KEY);
const storedTodos = storageItem ? JSON.parse(storageItem) : [];
if (storedTodos) setTodos(storedTodos)
}, []);
function HandleAddTodo(e){
const name = TodoNameRef.current.value;
if (name==='') return;
const nextTodos = [...todos, { id:uuidv4(), name:name, complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));//replace todos to nextTodos
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
There is no need of adding the second useEffect.
You can set your local Storage while submitting in the handleTodo function.
Things you need to add or remove :
Remove the Second useEffect.
Modify your handleTodo function :
const nextTodos = [...todos, { id:uuidv4(), name:name,complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));
Note: Make sure you won't pass todos instead of nextTodos as we know setTodos is an async function There might be a chance we are setting a previous copy of todos

How to get localStorage, createContext, withrouter and history, component from react to work on client side while using NextJs?

I am trying to get the localStorage to work but I do not know how to load the code on just the client side. Also to just have certain files run with the information from the component.
import React, { useContext } from 'react';
import { isInCart } from '../components/Helper';
import { CartContext } from '../context/cartContext';
import { withRouter } from 'next/router';
import featureP from '../styles/featureProduct.module.css';
const FeaturedProduct = (props) => {
const {title, imageUrl, price, history, id, description, priceOptions, alt,
productType} = props;
const product = { title, imageUrl, price, id, description, priceOptions,
alt, productType}
const { addProduct, cartItems, increase } = useContext(CartContext);
const itemInCart = isInCart(product, cartItems);
return (
<div className={featureP.featuredProduct}>
<div className={featureP.featuredImage} onClick={() =>
history.push(`/product/${id}`)}>
<img src={imageUrl} alt={alt} />
</div>
<div className={featureP.namePrice} />
<h3>{title} {productType}</h3>
<p>${price}</p>
{
!itemInCart && <button className={featureP.isBlack}
onClick={()=> addProduct(product)}>
Add to Cart
</button>
}
{
itemInCart && <button className={featureP.isWhite} id="btn-
white-columns" onClick={()=> increase(product)}>
Add More
</button>
}
</div>
</div>
)
}
export default withRouter(FeaturedProduct);
This is the component file I am tryin to use but the localStorage error appears. Is there a way to load this and other pages that use this content as a component but only once it is rendered on the client side. As I am using this information to get the client information for a cart.
import React, { createContext, useReducer } from 'react';
import cartReducer, { sumItems } from './cartReducer';
export const CartContext = createContext();
const cartFromStorage = localStorage.getItem('cart') ?
JSON.parse(localStorage.getItem('cart')) : [];
const initialState = { cartItems: cartFromStorage,
...sumItems(cartFromStorage)};
const CartContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(cartReducer, initialState);
const addProduct = (product) => dispatch({type: 'ADD_ITEM', payload:
product});
const increase = (product) => dispatch({type: 'INCREASE', payload:
product});
const decrease = (product) => dispatch({type: 'DECREASE', payload:
product});
const removeProduct = (product) => dispatch({ type:'REMOVE_ITEM', payload:
product});
const clearCart = () => dispatch({ type:'CLEAR'});
const contextValues = {
...state,
addProduct,
increase,
decrease,
removeProduct,
clearCart,
}
return (
<CartContext.Provider value={ contextValues }>
{
children
}
</CartContext.Provider>
);
}

Display the data from 'this.state.data'?

Goal:
*Get the data of of variable Cars to the 'this.state.data' when you have retrieved the data from API.
*Display data from 'this.state.data' and not using the variable Cars.
Problem:
I do not know how to do it and is is it possible to do it when you have applied refactoring SOLID?
Info:
I'm newbie in React JS.
Stackblitz:
https://stackblitz.com/edit/react-v39jre?
App.js
import React from 'react';
import './style.css';
import CarsList from './components/CarsList';
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
data: null
};
}
render() {
return (
<div className="App">
<CarsList />
</div>
);
}
}
export default App;
CarsList.jsx
import React, { useState, useEffect } from 'react';
this.state = {
name: 'React',
data: null
};
const CarsList = () => {
const [cars, setCars] = useState([]);
useEffect(() => {
const fetchCars = async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
setCars(await response.json());
};
fetchCars();
}, []);
return (
<div>
{cars.map((car, index) => (
<li key={index}>
[{++index}]{car.id} - {car.name}$
</li>
))}
</div>
);
};
export default CarsList;
After getting the response in the child component you should do a callback function which can be passed as prop from parent to child. Using the function you can pass the data from child to parent and update the parent state.
App.js
import { useState } from "react";
import CarsList from "./CarsList";
import "./styles.css";
export default function App() {
const [state, setState] = useState([]);
const handleUpdateParentState = (data) => {
setState(data);
};
console.log("state in parent", state);
return (
<div>
<CarsList updateParentState={handleUpdateParentState} />
</div>
);
}
CarsList.js
import React, { useState, useEffect } from "react";
const CarsList = (props) => {
const [cars, setCars] = useState([]);
useEffect(() => {
const fetchCars = async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
const data = await response.json();
setCars(data);
props?.updateParentState(data);
} catch (error) {
console.log(error);
}
};
fetchCars();
}, []);
return (
<ul>
{cars?.map((car, index) => (
<li key={index}>
[{++index}]{car.id} - {car.name}$
</li>
))}
</ul>
);
};
export default CarsList;
Codesandbox
Data can be shared using props but from parent component to child component only. We cannot pass child component state to parent component through props.
Though we can create a function at parent level and pass it to child component as props so we can execute there.
In your case, you have to create a function in App component and pass it on carList component as props. In carList component you do not have to create the cars state. After fetching the cars from API just call the function you passed from App component
App.js
import React from 'react';
import './style.css';
import CarsList from './components/CarsList';
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
data: null
};
}
function setCarList(cars) {
this.setState({
date: cars
});
}
render() {
return (
<div className="App">
<CarsList setCars={setCarList}/>
</div>
);
}
}
export default App;
CarList.js
import React, {useEffect } from 'react';
this.state = {
name: 'React',
data: null
};
const CarsList = (props) => {
useEffect(() => {
const fetchCars = async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
this.props.setCars(await response.json());
};
fetchCars();
}, []);
return (
<div>
{cars.map((car, index) => (
<li key={index}>
[{++index}]{car.id} - {car.name}$
</li>
))}
</div>
);
};
export default CarsList;
It doesn't make much sense for each CarList component to load data if you're going to have loads of them and they're going to share information with each other. You should load all your data in your App component using an array of API fetch calls and then use Promise.all to extract and parse the data, and then add it to the state. That state can be then shared with all your Carlist components.
Here's a React component:
const {Component} = React;
const json = '["BMW", "Clio", "Merc", "Fiat"]';
// Simulates an API call
function mockFetch() {
return new Promise((res, rej) => {
setTimeout(() => res(json), 1000);
});
}
class App extends Component {
constructor() {
super();
this.state = { cars: [] };
}
componentDidMount() {
// Have an array fetches (you would supply each one a
// different API endpoint in your code)
const arr = [mockFetch(), mockFetch(), mockFetch()];
// Grab the json, `map` over it and parse it
Promise.all(arr).then(data => {
const cars = data.map(arr => JSON.parse(arr));
// Then set the new state
this.setState(prev => ({ ...prev, cars }));
});
}
// You can now send the data to your small functional
// carlist components
render() {
const { cars } = this.state;
if (!cars.length) return <div />;
return (
<div>
<Carlist cars={cars[0]} />
<Carlist cars={cars[1]} />
<Carlist cars={cars[2]} />
</div>
)
}
};
function Carlist({ cars }) {
return (
<ul>{cars.map(car => <div>{car}</div>)}</ul>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
And here's equivalent written as a functional component with hooks:
const {useState, useEffect} = React;
const json = '["BMW", "Clio", "Merc", "Fiat"]';
function mockFetch() {
return new Promise((res, rej) => {
setTimeout(() => res(json), 1000);
});
}
function App() {
const [cars, setCars] = useState([]);
// This works in the same way as the previous example
// except we're not setting `this.state` we're setting the
// state called `cars` that we set up with `useState`.
useEffect(() => {
function getData() {
const arr = [mockFetch(), mockFetch(), mockFetch()];
Promise.all(arr).then(data => {
const cars = data.map(arr => JSON.parse(arr));
setCars(cars);
});
}
getData();
}, []);
if (!cars.length) return <div />;
return (
<div>
<Carlist cars={cars[0]} />
<Carlist cars={cars[1]} />
<Carlist cars={cars[2]} />
</div>
);
};
function Carlist({ cars }) {
return (
<ul>{cars.map(car => <div>{car}</div>)}</ul>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to test for props initial values?

I have a header component which listens for loggedInUser data from Redux store. I want to unit test for redux prop values. Like i have mocked a redux store for initial values and want to test for those values in props of the connected component.
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faUser, faShoppingCart } from '#fortawesome/free-solid-svg-icons';
import { NavLink, useHistory } from 'react-router-dom';
import Cart from './../Cart/Cart.component';
import { signOutStart } from './../../redux/user/user.actions';
import './Header.styles.scss';
export const Header = ({ noOfItemsInCart, loggedInUser, signOut }) => {
const [isUserDropDownVisible, setUserDropDownVisibility] = useState(false);
const [isCartDropDownVisible, setCartDropDownVisibility] = useState(false);
const history = useHistory();
console.log(loggedInUser);
return (
<header className = 'header' id = 'header'>
<NavLink to = '/'><p className = 'title'>Kart</p></NavLink>
{loggedInUser ? (
<div className = 'header__options' id = 'header__options'>
<div className = 'cart__options'>
<FontAwesomeIcon
icon={faShoppingCart}
onClick = {() => {
setUserDropDownVisibility(false);
setCartDropDownVisibility(prevState => {return !prevState})}
} />
<span><sup>{noOfItemsInCart}</sup></span>
{isCartDropDownVisible ? (
<div className="dropdown">
<Cart />
</div>
) : null}
</div>
<div className = 'user__options'>
<FontAwesomeIcon
icon={faUser}
onClick = {() => {
setCartDropDownVisibility(false);
setUserDropDownVisibility(prevState => {return !prevState})}
} />
{isUserDropDownVisible ? (
<div className="dropdown" onClick = {() => setUserDropDownVisibility(false)}>
<NavLink to = '/orders'>My Orders</NavLink>
<span onClick = { async () => {
await signOut();
history.push('/auth');
} }>Logout</span>
</div>
) : null}
</div>
</div>
) : null}
</header>
)
}
const mapStateToProps = state => {
return {
loggedInUser: state.user.loggedInUser,
noOfItemsInCart: state.cart.noOfItemsInCart
}
}
const mapDispatchToProps = dispatch => {
return {
signOut: () => dispatch(signOutStart())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
I had implemented an unit test as follows, by using a shallow render of component and tried accessing the props using .props()
import React from 'react';
import { shallow } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import Header from './Header.component';
const mockStore = configureMockStore();
describe('<Header />', () => {
let wrapper, store;
beforeEach(() => {
const initialState = {
user: {
loggedInUser: 'user1',
error: null
},
cart: {
noOfCartItemsInCart: 0
}
}
store = mockStore(initialState);
wrapper = shallow(
<Header store = {store} />
)
});
it('should have valid props', () => {
expect(wrapper.props().loggedInUser).toBe('user1');
})
})
I am getting prop values a undefined or null values. How to test for prop values to an redux connected component?
Have you tried this from the docs?
wrapper.instance().props

React.js Redux reducer does not insert the item

I'm very new to React.js and Redux.
I'm trying to build a very simple shopping cart application.
What I want is if you hit on an item (eg :- banana) It should appear in the cart.
(It should change the state of the cartReducer.js)
But instead of pushing the item to the reducer state it pushes something else.
What is the reason for this error?
This is my code.
cartReducer
import {ADD_TO_CART} from '../actions/index'
const initialState =[
]
export default (state = initialState,action)=>{
console.log("ACTION PAYLOAD",action.payload)
switch(action.type){
case ADD_TO_CART:
return[...state,action.payload]
default:
return state
}
}
Item component
import React, { Component } from "react";
import { connect } from "react-redux";
import {addToCart} from '../../actions/index'
export class Etem extends Component {
showItems = () => {
const { items, addToCartAction } = this.props;
console.log("ITEMS", items);
return items.map(items => <div key={items.id} onClick={addToCartAction}>{items.name}</div>);
};
render() {
return (
<div>
<h1>Items</h1>
<div>{this.showItems()}</div>
</div>
);
}
}
// export default items;
const mapStateToProps = reduxState => ({
items: reduxState.items
});
const mapDispatchToProps = dispatch => ({
addToCartAction: item => dispatch(addToCart(item))
});
export default connect(mapStateToProps, mapDispatchToProps)(Etem);
Action
export const ADD_TO_CART = 'ADD_TO_CART';
export const addToCart=(item) =>{
console.log("ITEMMMMMMMMMM",item)
return(
{
type:ADD_TO_CART,
payload:item,
}
)
}
<div key={items.id} onClick={addToCartAction}> this will pass the click event to addToCartAction instead of item.
Try this:
return items.map(item => (
<div key={item.id} onClick={() => addToCartAction(item)}>
{item.name}
</div>
));
Can you change the mapStateToProps
const mapStateToProps = reduxState => ({
items: reduxState.cartReducer.items
})

Categories

Resources