So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example
Related
I'm learning react context and while developing a todo application using useContext, I'm facing an issue where on submitting one task, the same task gets added two times to an array. The output component would loop through this array and display the results. While debugging I observed that, although the submit of task add only one entry into the array, not sure why and how, the consumer component gets the array with duplicate entry. Please let me know, what I'm missing.
Here is my code of index file that maintains context
import { createContext, useReducer } from "react";
import ContextReducer, { initialState } from "./ContextReducer";
const taskContext = createContext();
const ContextProvider = (props) => {
const [state, dispatch] = useReducer(ContextReducer, initialState);
const setTaskInput = (taskInput) => {
dispatch({
type: "SET_TASKINPUT",
payload: taskInput,
});
};
const addTask = (task) => {
dispatch({
type: "ADD_TASK",
payload: task,
});
};
const deleteTask = (id) => {
dispatch({
type: "DELETE_TASK",
payload: id,
});
};
const todoContext = {
todo: state.todo,
taskInput: state.taskInput,
setTaskInput,
addTask,
deleteTask,
};
return (
<taskContext.Provider value={todoContext}>
{props.children}
</taskContext.Provider>
);
};
export { taskContext };
export default ContextProvider;
This is the code for reducer
const initialState = {
todo: [],
taskInput: "",
};
const ContextReducer = (state = initialState, action) => {
if (action.type === "SET_TASKINPUT") {
state.taskInput = action.payload;
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "ADD_TASK") {
state.todo = [...state.todo, action.payload];
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "DELETE_TASK") {
state.todo = state.todo.filter((todo) => todo.id !== action.payload);
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
return state;
};
export { initialState };
export default ContextReducer;
This is the code of output component or say, consumer component
import React, { Fragment, useContext } from "react";
import { taskContext } from "../../Context";
import styles from "./Content.module.css";
const Output = () => {
const { todo, deleteTask } = useContext(taskContext);
const deleteHandler = (e) => {
deleteTask(+e.target.parentElement.parentElement.id);
};
return (
<Fragment>
{todo.length > 0 && (
<div className={styles.outputDiv}>
<ul>
{todo.map((task) => {
return (
<li key={task.id} id={task.id}>
<div className={styles.row1}>{task.task}</div>
<div className={styles.row2}>
<button className={styles.edit}>Edit</button>
<button className={styles.delete} onClick={deleteHandler}>
Delete
</button>
</div>
</li>
);
})}
</ul>
</div>
)}
</Fragment>
);
};
export default Output;
I find myself in a bit of a pickle and can't seem to find an answer on Google.
I'm trying to use the React query library with TSX and display the returning data in a simple list. However it seems that the fetchng and displaying is done only by leaving the tab and coming back to it.
Here's the component
import React, { ChangeEvent, useState, ReactElement } from "react";
import { useQuery, UseQueryResult } from "react-query";
import axios from "axios";
import { API_URL } from "../../settings";
import SearchBar from "../../components/search-bar";
const Employees = (): ReactElement => {
type Employee = Record<string, any>;
const [name, setName] = useState("");
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
console.log(name, "I'm the direct input");
const e = eventData.target.value;
setName(e);
getEmployeesList(name);
}
async function getEmployeesList(name: string) {
const { data } = await axios.get(
API_URL + "employees?q[firstname_or_lastname_cont]=" + name
);
console.log(data);
return data;
}
const {
data,
error,
isError,
isLoading,
}: UseQueryResult<Employee[], Error> = useQuery("employees", () =>
getEmployeesList(name)
);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return (
<div>
`Error!
{error?.message}`
</div>
);
}
if (data) {
console.log(data, "I'm the query data");
}
return (
<div className="findEmployees">
<SearchBar
placeholder=""
value={name}
onValueChange={(event: ChangeEvent<HTMLInputElement>) =>
getValue(event)
}
/>
<div className="listContainer">
<h1>Employees</h1>
{data?.map((employee, index: number) => (
<li key={employee.id}>
{employee.firstname} {employee.lastname}
<p>{employee.role}</p>
</li>
))}
</div>
</div>
);
};
export default Employees;
Here's the child component
import React, {
ChangeEventHandler,
MouseEvent,
ReactElement,
ReactEventHandler,
} from "react";
import { SearchBarContainer, SearchBarInput } from "./styled-components";
import Icon from "../icon";
interface Props {
placeholder: string;
value: string;
onValueChange: ChangeEventHandler<HTMLInputElement>;
}
const SearchBar = ({
onValueChange,
placeholder,
value,
}: Props): ReactElement => (
<SearchBarContainer>
<SearchBarInput
onChange={onValueChange}
placeholder={placeholder}
value={value}
/>
<Icon color="grey700" name="search" />
</SearchBarContainer>
);
export default SearchBar;
So far I haven't found the problem. I tried a custom hook to get and set the data but that obviously didn't change the problem. If anyone has an idea I'll be thankful.
Have a great day
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
const e = eventData.target.value;
setName(e);
console.log(name); // still you should receive previous value
getEmployeesList(e);
}
setState isn't synchronous. It only updates the state value at end of the function call.
thanks for your inputs. I actually solved the problem with a custom hooks
import React, { ChangeEvent, useState, ReactElement } from "react";
import { useQuery, UseQueryResult } from "react-query";
import axios from "axios";
import { API_URL } from "../../settings";
import SearchBar from "../../components/search-bar";
const Employees = (): ReactElement => {
type Employee = Record<string, any>;
const [name, setName] = useState<string>("");
const [eData, setEData] = useState<Employee[]>([]);
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
setName(eventData.target.value);
getEmployeesList(eventData.target.value);
}
async function getEmployeesList(name: string) {
const { data } = await axios.get(
API_URL + "employees?q[firstname_or_lastname_cont]=" + name
);
setEData(data);
return data;
}
const {
data,
error,
isError,
isLoading,
}: UseQueryResult<Employee[], Error> = useQuery("employees", () =>
getEmployeesList(name)
);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return (
<div>
`Error!
{error?.message}`
</div>
);
}
return (
<div className="findEmployees">
<SearchBar
placeholder=""
value={name}
onValueChange={(event: ChangeEvent<HTMLInputElement>) =>
getValue(event)
}
/>
<div className="listContainer">
<h1>Employees</h1>
{eData?.map((employee, index: number) => (
<li key={employee.id}>
{employee.firstname} {employee.lastname}
<p>{employee.role}</p>
</li>
))}
</div>
</div>
);
};
export default Employees;
As you can see I changed "data" by the hook value "eData". I noticed that my axios query was updated in real time so I took this entry and stocked it in a custom hook which I then mapped on my JSX. Got a real time fetch that way. Furthermore I updated the
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
setName(eventData.target.value);
getEmployeesList(eventData.target.value);
}
part which was requesting with one letter fewer in the first version.
I am creating a basic shopping cart app with Reactjs. I created a useContext file to make the states globally available.
Unfortunately, the objects in the useReducer state are not responding to action, except the array of products called 'cart'. The 'amount' and 'total' are not rendering.
Though, the actions can be seen to be updated when I checked the console log. That means I am not returning the right variables.
The action I want to achieve is that when <MdKeyboardArrowUp> is clicked, the 'amount' variable should increase by 1. It increases on console log but not rendered on the page.
The product list:
export default [
{
id: 1,
title: 'Samsung Galaxy S7',
price: 599.99,
img:
'https://res.cloudinary.com/diqqf3eq2/image/upload/v1583368215/phone-2_ohtt5s.png',
amount: 1,
},
useContext file
import React, {useState, useContext, useReducer, useEffect} from 'react';
import cartData from '../component/data'; //this is the source file for the product list//
import customReducer from './reducer'; //the file that handles the useReducer
const Appcontext = React.createContext();
const initialState = {
loading: false,
cart: cartData,
total: 0,
amount: 0,
}
const AppProvider = ({children}) =>{
const [state, dispatch] = useReducer(customReducer, initialState);
const increaseProduct = (id) =>{
dispatch({type: "INCREASE_PRODUCT", payload: id})
}
const decreaseProduct = (id) =>{
dispatch({type: "DECREASE_PRODUCT", payload: id})
}
return(
<Appcontext.Provider value={{...
state,
clearShopCart,
clearShopCart,
removeProduct,
decreaseProduct,
increaseProduct,
}}>
{children}
</Appcontext.Provider>
)
}
export const useGlobalContext = () =>{
return useContext(Appcontext)
}
export {Appcontext, AppProvider}
useReducer file
const customReducer = (state, action) => {
if(action.type === 'CLEAR_SHOPP_CART'){
return{...state, cart: []}
}
if(action.type === 'REMOVE_ITEM'){
const newProducts = state.cart.filter((singleProduct) => singleProduct.id !==
action.payload)
return{...state, cart: newProducts}
}
**if(action.type === "INCREASE_PRODUCT"){
let newValue = state.cart.map((singleProduct) => {
if(singleProduct.id === action.payload){
return {... singleProduct, amount: singleProduct.amount + 1}
}
return singleProduct
});
console.log(newValue)
return {...state, cart: newValue}** //these codes on bold format are the codes that
increases by 1 each time the button is clicked//
}
return state
}
export default customReducer;
The home file where the codes are rendered
import React, { useState, useEffect } from 'react';
import {HiShoppingCart} from 'react-icons/hi';
import {MdKeyboardArrowUp} from 'react-icons/md';
import {RiArrowDownSLine} from 'react-icons/ri';
import { useGlobalContext } from '../component/context';
export default function Home() {
const {cart, amount, total, clearShopCart, removeProduct, decreaseProduct, increaseProduct} =
useGlobalContext();
{cart.map((singleData) => {
const {id, title, price, img} = singleData;
return(
<>
<div key={id} className='product-container'>
<div className='img-container'>
<img src={img} alt={title} />
<div className='product-text-container'>
<h4>{title}</h4>
< h4>${price}</h4>
<h5 className='btn1' onClick={() => removeProduct(id)}>Remove</h5>
</div>
</div>
<div className='item-control'>
<MdKeyboardArrowUp className='iconUp' onClick={() =>
increaseProduct(id)}/> **//when clicked, should inrease 'amount' by
1**//
<p>{amount}</p>
{console.log(amount)}
<RiArrowDownSLine className='iconDown' onClick={() =>
decreaseProduct(id)}/>
</div>
</div>
</>
)
})}
I eventually found the solution. I needed to destructure the 'amount' object coming from the product array. That way, I was able to increase the individual products' amount. It should be like this while destructuring the array:
{cart.map((singleData) => {
const {id, title, price, img, amount} = singleData;
I' m new to React and I'm building a simple React app that displays all the nations of the world on the screen and a small search bar that shows the data of the searched nation.
Here an image of the site
But I don't know how to show the country you want to click in the scrollbar.
Here the app.js code:
import React, { Component } from 'react';
import './App.css';
import NavBar from '../Components/NavBar';
import SideBar from './SideBar';
import CountryList from '../Components/SideBarComponents/CountryList';
import Scroll from '../Components/SideBarComponents/Scroll';
import Main from './Main';
import SearchCountry from '../Components/MainComponents/SearchCountry';
import SearchedCountry from '../Components/MainComponents/SearchedCountry';
import Datas from '../Components/MainComponents/Datas';
class App extends Component {
constructor() {
super();
this.state = {
nations: [],
searchField: '',
button: false
}
}
onSearchChange = (event) => {
this.setState({searchField: event.target.value});
console.log(this.state.searchField)
}
onClickChange = () => {
this.setState(prevsState => ({
button: true
}))
}
render() {
const {nations, searchField, button, searchMemory} = this.state;
const searchedNation = nations.filter(nation => {
if(button) {
return nation.name.toLowerCase().includes(searchField.toLowerCase())
}
});
return (
<div>
<div>
<NavBar/>
</div>
<Main>
<div className='backgr-img'>
<SearchCountry searchChange={this.onSearchChange} clickChange={this.onClickChange}/>
<SearchedCountry nations={searchedNation}/>
</div>
<Datas nations={searchedNation}/>
</Main>
<SideBar>
<Scroll className='scroll'>
<CountryList nations={nations} clickFunc/>
</Scroll>
</SideBar>
</div>
);
}
componentDidMount() {
fetch('https://restcountries.eu/rest/v2/all')
.then(response => response.json())
.then(x => this.setState({nations: x}));
}
componentDidUpdate() {
this.state.button = false;
}
}
export default App;
The countryList:
import React from 'react';
import Images from './Images';
const CountryList = ({nations, clickFunc}) => {
return (
<div className='container' style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(115px, 3fr))'}}>
{
nations.map((country, i) => {
return (
<Images
key={country.numericCode}
name={country.name}
flag={country.flag}
clickChange={clickFunc}
/>
);
})
}
</div>
)
}
export default CountryList;
And the images.js:
import React from 'react';
import './images.css'
const Images = ({name, capital, region, population, flag, numericCode, clickChange}) => {
return (
<div className='hover bg-navy pa2 ma1 tc w10' onClick={clickChange = () => name}>
<img alt='flag' src={flag} />
<div>
<h6 className='ma0 white'>{name}</h6>
{capital}
{region}
{population}
{numericCode}
</div>
</div>
);
}
export default Images;
I had thought of using the onClick event on the single nation that was going to return the name of the clicked nation. After that I would have entered the name in the searchField and set the button to true in order to run the searchedNation function.
I thank anyone who gives me an answer in advance.
To keep the actual structure, you can try using onClickChange in Images:
onClickChange = (newName = null) => {
if(newName) {
this.setState(prevsState => ({
searchField: newName
}))
}
// old code continues
this.setState(prevsState => ({
button: true
}))
}
then in onClick of Images you call:
onClick={() => {clickChange(name)}}
Or you can try as well use react hooks (but this will require some refactoring) cause you'll need to change a property from a parent component.
With that you can use useState hook to change the value from parent component (from Images to App):
const [searchField, setSearchField] = useState('');
Then you pass setSearchField to images as props and changes the searchField value when Images is clicked:
onClick={() => {
clickChange()
setSearchField(name)
}}
I'm digging into my first react/redux application and I've been having quite a bit of trouble mapping my dispatch actions to onClick events in my components.
I've tried a couple of variations of trying to bind the onClick Event to the dispatch, but I always end up with either :
ReferenceError: onMovieClick is not defined
or alternatively when I do end up binding a function correctly I'll get an error related to dispatch is not defined.
My Goal
I'm trying to implement a filter(delete) from store function
actions/movieActions.js
import * as actionTypes from './actionTypes'
export const createMovie = (movie) => {
return {
type: actionTypes.CREATE_MOVIE,
movie
}
};
export const deleteMovie = (id) => {
console.log('action triggered. movie index:' + id)
return {
type: actionTypes.DELETE_MOVIE,
id
}
}
reducers/movieReducers.js
export default (state = [], action) => {
switch (action.type){
case 'CREATE_MOVIE':
return [
...state,
Object.assign({}, action.movie)
];
case 'DELETE_MOVIE':
return [
state.filter(({ id }) => id !== action.id)
]
default:
return state;
}
};
components/MovieList.js
import React from 'react'
import Slider from 'react-slick'
import { dispatch, connect } from 'react-redux'
import {Icon} from 'react-fa'
import { deleteMovie } from '../../actions/movieActions'
import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'
import './MovieList.scss'
class MovieList extends React.Component{
constructor(props){
super (props)
}
handleClick(id) {
dispatch(deleteMovie(id))
}
onMovieClick(id){
dispatch.deleteMovie(id)
}
render () {
// Settings for slick-carousel
let settings = {
infinite: true,
speed: 500
}
return (
<div className='col-lg-12'>
{this.props.movies.map((b, i) =>
<div key={i} className="col-lg-2">
<Slider {...settings}>
{b.images.map((b, z) =>
<div className="img-wrapper">
<Icon name="trash" className="trash-icon" onClick={() =>
console.log(this.props.movies[i].id),
onMovieClick(this.props.movies[i].id)
}/>
<img className="img-responsive" key={z} src={b.base64}></img>
</div>
)}
</Slider>
<div className="text-left info">
<h2>{b.title}</h2>
<p>{b.genre}</p>
</div>
</div>
)}
</div>
)
}
}
// map state from store to props
const mapStateToProps = (state) => {
return {
movies: state.movies
}
};
// Map actions to props
const mapDispatchToProps = (dispatch) => {
return {
onMovieClick: (id) => {
dispatch(deleteMovie(id))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MovieList)
Would love some advice if anyone has a moment.
Since you are passing onMovieClick through connect, you can actually invoke it from the MovieList component props. First, I would remove the onMovieClick method definition in your MovieList component and then use this.props.onMovieClick in the onclick handler of Icon like so:
<Icon name="trash" className="trash-icon" onClick={() =>
console.log(this.props.movies[i].id),
this.props.onMovieClick(this.props.movies[i].id)
}/>