Update state from another component - javascript

I am trying to update state of page index in index.js from component Pagination,
my index.js:
import useSWR from 'swr';
import { useState } from 'react';
const Index = ({ data }) => {
const initialStatePage = () => 1;
const [pageIndex, setPageIndex] = useState(initialStatePage);
const { data } = useSWR(`http://1.2.3.4/api/console?pagination[page]=${pageIndex}`, fetcher, {fallbackData: data});
return (
<>
<h1> {data} <h1/>
<Pagination pagenow={initialStatePage}/>
<>
);
};
export default Index;
my component:
import { useState } from 'react';
const Pagination = ({ pagenow }) => {
const [pageIndex, setPageIndex] = useState(pagenow);
return (
<>
<li>
<button onClick={() => setPageIndex(pageIndex - 1)}>
</button>
</li>
<button onClick={() => setPageIndex(pageIndex + 1)}>
</button>
</li>
</>
)
};
export default Pagination;
but after click, page index is not updating from my component

The state in your Pagination component will rerender the children element, not the whole page.
If you want it to rerender the whole Index page, pass your setPageIndex function to the component and use it to set the page index:
index.js
import useSWR from 'swr';
import { useState } from 'react';
const Index = ({ data }) => {
const initialStatePage = () => 1;
const [pageIndex, setPageIndex] = useState(initialStatePage);
const { data } = useSWR(`http://1.2.3.4/api/console?pagination[page]=${pageIndex}`, fetcher, {fallbackData: data});
return <>
<h1>{data}</h1>
<Pagination pagenow={initialStatePage} setPageIndex={setPageIndex} />
<>;
};
export default Index;
Pagination component file
import { useState } from 'react';
const Pagination = ({ pagenow: pageIndex, setPageIndex }) => {
return <>
<li>
<button onClick={() => setPageIndex(pageIndex - 1)}></button>
<button onClick={() => setPageIndex(pageIndex + 1)}></button>
</li>
</>;
};
export default Pagination;

Related

Trying to display one element from an Array -ReactJs

I am trying to make a flashcard web app for language learning and/or rote learning. I have managed to show the first element of the array which contains the data that I'm fetching from the backend but I can't switch from the first element to the subsequent elements.
Here is my code in React:
// Decklist component that displays the flashcard
import { React, useEffect, useState, useContext } from "react";
import Card from "./Card";
import cardContext from "../store/cardContext";
const axios = require("axios");
export default function Decklist() {
//State for data fetched from db
const [data, setData] = useState([]);
//State for array element to be displayed from the "data" state
const [position, setPosition] = useState(0);
//function to change the array element to be displayed after user reads card
const setVisibility = () => {
setPosition(position++);
};
//function to change the difficulty of a card
const difficultyHandler = (difficulty, id) => {
console.log(difficulty);
setData(
data.map((ele) => {
if (ele.ID === id) {
return { ...ele, type: difficulty };
}
return ele;
})
);
};
//useEffect for fetching data from db
useEffect(() => {
axios
.get("/api/cards")
.then((res) => {
if (res.data) {
console.log(res.data);
setData(res.data.sort(() => (Math.random() > 0.5 ? 1 : -1)));
}
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<cardContext.Provider
value={{ cardData: data, setDifficulty: difficultyHandler }}
>
{data.length && (
<Card
position={position}
// dataIndex={index}
visible={setVisibility}
id={data[position].ID}
front={data[position].Front}
back={data[position].Back}
/>
)}
</cardContext.Provider>
);
}
//Card component
import { React, useState, useEffect } from "react";
import Options from "./Options";
export default function Card(props) {
//State for showing or hiding the answer
const [reverse, setReverse] = useState(false);
const [display, setDisplay] = useState(true);
//function for showing the answer
const reversalHandler = () => {
setReverse(true);
};
return (
<div>
{reverse ? (
<div className="card">
{props.front} {props.back}
<button
onClick={() => {
props.visible();
}}
>
Next Card
</button>
</div>
) : (
<div className="card">{props.front}</div>
)}
<Options
visible={props.visible}
reverse={reversalHandler}
id={props.id}
/>
</div>
);
}
//Options Component
import { React, useContext, useState } from "react";
import cardContext from "../store/cardContext";
export default function Options(props) {
const ctx = useContext(cardContext);
const [display, setDisplay] = useState(true);
return (
<>
<div className={display ? "" : "inactive"}>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("easy", props.id);
}}
>
Easy
</button>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("medium", props.id);
}}
>
Medium
</button>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("hard", props.id);
}}
>
Hard
</button>
</div>
</>
);
}
The setVisibility function in the Decklist component is working fine and setting the position state properly. However, I don't know how to re-render the Card component so that it acts on the position state that has changed.
One way to force a re-render of a component is to set its state to itself
onClick={() => {
props.visible();
setReverse(reverse);
}}
However this probably isn't your issue as components will automatically re-render when their state changes or a parent re-renders. This means that for some reason the Card component isn't actually changing the parent component.

How to click programmatically a child component? react

I have two components, the parent and child. Currently I have these codes below. But unfortunately it returns an error:
TypeError: Cannot read property 'click' of null
For some reasons I want when button is click the Item component also will be click. But these codes below produces an error above. Anyone does know how to achieve it?
import React, { useRef } from 'react';
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
{dynamicBoolean ? (
<button onClick={() => itemRef.current.click()}>
click item
</button>
) : (
//more codes here
<Item ref={itemRef} />
)}
</div>
);
};
export default App;
Child component would look like below (demonstration purposes, the code is very lengthly)
import React from 'react';
const Item = (props) => {
return (
<div>
//some design here
</div>
);
};
export default Item;
You need useRef and you have to forward this ref to the Item component.
import React, { forwardRef, useRef } from 'react';
const Item = forwardRef((props, ref) => {
return <li {...props}
onClick={() => alert('clicked on Item')}
ref={ref} >MyItem</li>
})
const App = (props) => {
const itemRef = useRef(null);
return (
<div>
<button onClick={() => itemRef.current.click()}>
click item
</button>
<Item ref={itemRef} />
</div>
);
};
export default App;
import React, { createRef } from "react";
const Hello = (props) => {
const itemRef = createRef();
const hello = () => {
itemRef.current.click();
};
return (
<div>
<button onClick={() => hello()}>click item</button>
<Item ref={itemRef} />
</div>
);
};
const Item = React.forwardRef((props, ref) => {
const myClick = () => {
console.log("this is clicked");
};
return (
<button ref={ref} className="FancyButton" onClick={myClick}>
{props.children}
</button>
);
});
export default Hello;

Reducer/Context Api

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

Custom pagination using ReactJS

I have this project for pagination of json data received through an API. The problem is that my code somehow gives me a 'slice' error (it is not the case when using other API's, e.g. https://corona.lmao.ninja/v2/countries) <--- Works fine
Items.js:
import React from 'react';
import { ITEMS_PER_PAGE } from '../utils/constants';
import Data from './Data';
const Items = ({ items, page }) => {
const startIndex = (page - 1) * ITEMS_PER_PAGE;
const selectedItems = items.slice(startIndex, startIndex + ITEMS_PER_PAGE);
return (
<React.Fragment>
{selectedItems.map(item => (
<Data key={item.country} {...item} />
))}
</React.Fragment>
);
};
export default Items;
Data.js:
import React from 'react';
const Data = ({ Data }) => {
const { high, low } = Data;
return (
<div class="data">
<p>
<strong>Test:</strong> {high} {low}
</p>
<hr />
</div>
);
};
export default Data;
Pagination.js:
import React from 'react';
const Pagination = ({ totalPages, handleClick, page }) => {
const pages = [...Array(totalPages).keys()].map(number => number + 1);
return (
<div className="numbers">
{pages.map(number => (
<a
key={number}
href="/#"
onClick={() => handleClick(number)}
className={`${page === number && 'active'}`}
>
{number}
</a>
))}
</div>
);
};
export default Pagination;
App.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Pagination from './components/Pagination';
import Items from './components/Items';
import { ITEMS_PER_PAGE } from './utils/constants';
const App = () => {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
axios
.get('https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=10')
.then(response => {
const result = response.data;
setItems(result);
setTotalPages(Math.ceil(result.length / ITEMS_PER_PAGE));
setIsLoading(false);
});
}, []);
const handleClick = number => {
setPage(number);
};
return (
<div>
<h1>Pagination Demo</h1>
{isLoading ? (
<div className="loading">Loading...</div>
) : (
<React.Fragment>
<Items items={items} page={page} />
<Pagination
totalPages={totalPages}
handleClick={handleClick}
page={page}
/>
</React.Fragment>
)}
</div>
);
};
export default App;
My problem seems to be something that am I missing with this other API: https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=10
error: TypeError: items.slice is not a function in Items.js
Any help would be appreciated!
The response from the API has 2 nested Data keys, so it has to be like this:
const result = response.data;
setItems(result.Data.Data);
Data.js
import React from 'react';
const Data = ({ high, low }) => {
return (
<div class="data">
<p>
<strong>Test:</strong> {high} {low}
</p>
<hr />
</div>
);
};
export default Data;
demo: https://stackblitz.com/edit/react-arqaxj

Update a react state (array of objects) from a child component

I would like to update the parent state from child component, which renders each object of the array of objects. The main goal of the child component is to update the original value from the array of objects.
I've the following code
Parent:
import { useState } from 'react';
import ExpenseItem from './expenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} {...expense} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
child:
import { useState, useRef } from 'react';
function ExpenseItem({ description, date, credit, debit }) {
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Once way, is to pass the parent state property (expenses) and the function that updates it (setExpenses) to the child Component via the props:
Parent:
import React from 'react';
import ReactDOM from 'react-dom';
import { useState } from 'react';
import ExpenseItem from './ExpenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
Checking: { expenses[0].description } | { expenses[1].description }
<hr/>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
Child:
import React from 'react';
import { useState, useRef } from 'react';
function ExpenseItem( props ) {
let { description, date, credit, debit } = props.expenses[props.index];
const setExpenses = props.setExpenses;
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
const expenses = [ ...props.expenses ]; // Get a copy of the expenses array
// Replace the current expense item
expenses.splice( props.index, 1, {
description: value, date, credit, debit
});
// Update the parent state
setExpenses( expenses );
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Working demo
This can get really complicated as you move along, so the best option is to look for some sort of State Management solution, like using the Context API.
Also, take a look at this interesting post that talks about using the map index value as a key value: Index as a key is an anti-pattern

Categories