Update state in class from const - html

I'm new to React JS and I'm coding a really simple task manager. So, I have all tasks in state element of MyTodoList class (each task has: id, name, description, completed). Then I draw each task separately with Task constant.
I want to implement changing buttons below every task (if task is completed button should be "Done", if not - "Not done").
I do not understand how I can update "completed" attribute (which is in MyTodoList class in state) from const Task.
Would be grateful for any hint!
Code:
import logo from './logo.svg';
import './App.css';
import React from 'react';
function DoneButton({onClick}) {
return (
<button onClick={onClick}>
Done
</button>
);
}
function NotDoneButton({onClick}) {
return (
<button onClick={onClick}>
Not done
</button>
);
}
const Task = ({id, name, description, completed}) => {
const handleDoneClick = () => {
completed= false //something different should be here
}
const handleNotDoneClick = () => {
completed= true //something different should be here
}
let button;
if (completed) {
button = <DoneButton onClick={handleDoneClick} />
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />
}
return (
<div className='task'>
<h3>{name}</h3>
<div>{description}</div>
<div>{completed}</div>
{button}
</div>
)
}
class MyTodoList extends React.Component {
state = {
tasks: [
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
},
],
}
render () {
return(
<div>
<header><h1>TO-DO</h1></header>
<div>{this.state.tasks.map(task => <Task id={task.id} name={task.name}
description={task.description} completed={task.completed}/>)}
</div>
</div>
)
}
}
const App = () => {
return (
<MyTodoList />
)
}
export default App;

You should never re-assign parameters unless it is the only solution you have, but you should definitely never re-assign parameters which you plan to depend on in the render method.
The proper solution would be this:
import React, { useState } from 'react';
...
const Task = ({ id, name, description, completed }) => {
const [isCompleted, setIsCompleted] = useState(completed);
const handleDoneClick = () => {
setIsCompleted(true);
};
const handleNotDoneClick = () => {
setIsCompleted(false);
};
let button;
if (isCompleted) {
button = <DoneButton onClick={handleDoneClick} />;
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />;
}
return (
<div className="task">
<h3>{name}</h3>
<div>{description}</div>
<div>{isCompleted}</div>
{button}
</div>
);
};
You need to use local state, in which you will set the initial value (completed, or not completed) which you are receiving from props, and then change the state, and not the parameter. Furthermore, continue using the state value of your completed (isCompleted) so React will react to its change.
This is not the final solution though, as this will only keep the local change of the task, and not change the task status in tasks list.
Basically, if you component A holds the tasks and their complete status, you need to create a method in the component A which will modify the respective task by ID, to the correct status. Then you need to pass the respective method to component B which will call the method and pass along the id and complete status (true / false) The method which is assigned in component A will then look through the list of tasks, find the proper task by ID, and assign its new completed value you passed from component B. After that, react does its thing and automatically updates completed prop you passed to component B
Working snippet:
function DoneButton({ onClick }) {
return <button onClick={onClick}>Done</button>;
}
function NotDoneButton({ onClick }) {
return <button onClick={onClick}>Not done</button>;
}
const Task = ({ id, name, description, completed, onTaskClick }) => {
const handleDoneClick = () => {
onTaskClick(id, false);
};
const handleNotDoneClick = () => {
onTaskClick(id, true);
};
let button;
if (completed) {
button = <DoneButton onClick={handleDoneClick} />;
} else {
button = <NotDoneButton onClick={handleNotDoneClick} />;
}
return (
<div className="task">
<h3>{name}</h3>
<div>{description}</div>
<div>{completed}</div>
{button}
</div>
);
};
const MyTodoList = () => {
const [tasks, setTasks] = React.useState([
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
}
]);
const onTaskClick = React.useCallback(
(id, isCompleted) => {
const updatedTasks = [...tasks].map((task) => {
if (task.id === id) {
return {
...task,
completed: isCompleted,
};
}
return task;
});
setTasks(updatedTasks);
},
[tasks]
);
return (
<div>
<header>
<h1>TO-DO</h1>
</header>
<div>
{tasks.map((task) => (
<Task onTaskClick={onTaskClick} id={task.id} name={task.name} description={task.description} completed={task.completed} />
))}
</div>
</div>
);
};
const App = () => <MyTodoList />;
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

Related

React : i have following code, i am using setInterval to change text value time to time ,when i switch pages and come back text is changing reallyfast

I assume when component is rendered multiple times something happens to setinterval,but how can i fix this.
bottom code is for Store that i am using and i don't understand.someone said that i must have useffect outside component but then it gives me error.
Anyways im new to react so i need help ,everyones appriciated.Thanks.
import SmallLogo from '../img/logo.svg';
import StarskyText from '../img/starskyproject.svg';
import './Statement.css'
import { BrowserRouter as Router,Routes,Route,Link } from "react-router-dom";
import { getElementError } from '#testing-library/react';
import react, { useRef , useState, useEffect } from 'react';
import { useDispatch, useSelector } from "react-redux";
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { store } from "./appReducer";
function TempText(props) {
return <span className="yellow changetext"> {props.body} </span>;
}
function doUpdate(callback) {
setInterval(callback, 1300);
}
export default function Statement(){
const dispatch = useDispatch();
const textOptions = ["NFT", "CRYPTO", "METAVERSE", "WEB3"];
const tempText = useSelector((state) => state.tempText);
function change() {
let state = store.getState();
const index = state.index;
console.log(index);
console.log(textOptions[index]);
dispatch({
type: "updatetext",
payload: textOptions[index]
});
let newIndex = index + 1 >= textOptions.length ? 0 : index + 1;
dispatch({
type: "updateindex",
payload: newIndex
});
}
useEffect(() => {
doUpdate(change);
}, []);
var [dropdownOpen , Setdrop] = useState(false);
return(
<div>
<Link to="/">
<img className='star-fixed' alt='starlogo' src={SmallLogo}></img>
</Link>
<img className='starsky-fixed' alt='starsky-project' src={StarskyText}></img>
<div className='text-content'>
<span className='statement-text'>WEB3 IS NOT ONLY THE FUTURE.
IT’S THE ONLY FUTURE!</span>
<span className='starsk-link'>starsk.pro</span>
</div>
<div className='text-content-bottom'>
<span className='statement-text-bottom'>CREATE YOUR NEXT
<TempText body={tempText} />
<span className='flex'> PROJECT WITH
<Dropdown className="hover-drop-out" onMouseOver={() => Setdrop(dropdownOpen=true) } onMouseLeave={() => Setdrop(dropdownOpen=false)} isOpen={dropdownOpen} toggle={() => Setdrop(dropdownOpen = !dropdownOpen) }>
<DropdownToggle className='hover-drop'> STRSK.PRO </DropdownToggle>
<DropdownMenu> </DropdownMenu>
</Dropdown> </span>
</span>
</div>
</div>
)
}
import { createStore } from "redux";
const initialState = {
tempText: "NFT",
index: 1
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "updatetext":
return {
...state,
tempText: action.payload
};
case "updateindex":
return {
...state,
index: action.payload
};
default:
return state;
}
};
export const store = createStore(reducer);
You can clear your timer by calling clearTimeout function with a reference to your timer when your component unmounting.
useEffect(() => {
const timer = setInterval(change, 1300);
// in order to clear your timeout
return () => clearTimeout(timer);
}, [])

Not able to fetch data from server in my ReactJs site

Getting undefined data type error while fetching data from JSON
I have searched at many places but didn't get the suitable answer
import SavedData from "./SavedData";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true,
datas: [],
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
const todoData = response.json().then((res) => {
this.setState({ datas: res });
});
}
render() {
console.log(this.state.datas[0].description); //not able to get data
return (
<div>
{/* {this.state.datas.map((items) => (
<SavedData
key={items.countTodo}
title={items.title}
desc={items.desc}
/>
))} */}
</div>
);
}
}
Someone help me so that I can proceed
Just like Dave Newton has pointed out in the comments, the render is triggered before the request completes. This is normal and you just need to handle it properly.
If you see the console logs of this codesandbox, you can see that initially this.state.datas is just an empty array [] - so any attempt to access this.state.datas[0].description will be undefined. Only after the state is updated when the request completes, the logs show the data retrieved - this is because according to the mount lifecycle of a React Component, the render() is called before the componentDidMount() and also the request being async.
This is very common and it is even recommended by the official React docs to make HTTP calls in componentDidMount(). The docs also has provided an example to handle this issue.
import SavedData from "./SavedData";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true, // we initially set this to true
datas: [],
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
const todoData = response.json().then((res) => {
this.setState({
datas: res,
loading: false // when the request is complete, we set this to false
});
});
}
render() {
if (this.state.loading) {
// during the first render, loading will be true and we
// can return a loading message or a spinner
return (
<div>Loading...</div>
);
}
// when render is called after the state update, loading will be false
// and this.state.datas will have the fetched data
console.log(this.state.datas[0].description);
return (
<div>
{this.state.datas.map((items) => (
<SavedData
key={items.countTodo}
title={items.title}
desc={items.desc}
/>
))}
</div>
);
}
}
Your datas state is initially an empty array until your componentDidMount fires and sets the state. As a result, your console log will then be undefined until the state is set. In order to combat this you must wait for this.state.datas[0] to be true before accessing the first objects description within the array. The following code seems to work as expected
import React from "react";
export default class Saved extends React.Component {
constructor() {
super();
this.state = {
loading: true,
datas: []
};
}
async componentDidMount() {
const url = "https://todo-list-site.herokuapp.com/todo-data";
const response = await fetch(url);
response.json().then((res) => {
this.setState({ datas: res });
});
}
render() {
console.log(this.state.datas[0] && this.state.datas[0].description);
return (
<div>
{this.state.datas.map((items, i) => (
<div key={i}>
<div> title={items.title}</div>
<div> desc={items.description}</div>
</div>
))}
</div>
);
}
}

Trouble getting into json data object

I have some code that allows the user to click a image to then update the page and display the clicked on champions name. the json data looks like this -http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/Alistar.json
I console.log response.data and see a object of objects and am wondering how to get passed the section that has the response.data.(whatever champion the user picked). I have tried adding a variable like response.data.champion but I assume no variables can be passed like that seeing how it doesnt work.
Not sure if its even worth posting the code but just in case! My code is below, the fetch im trying to go through is in NewChamp function.
To make my request simpler, All i want to know for example is how i would get response.data.(whatever the user clicked).key from any possible champion clicked like http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/Alistar.json or http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/Anivia.json
or whatever other champion the user clicks.
import React, { Component } from 'react';
import './Champions.css';
class AllChamps extends Component {
render() {
let champion = this.props.champion;
return(
<div className='champions'>
<h1> all champions</h1>
{Object.keys(this.props.champions).map((s) => (
<div className='champs' onClick={() => this.props.NewChamp({s, champion})}>
<img
alt='Champion Images'
src={`http://ddragon.leagueoflegends.com/cdn/10.16.1/img/champion/${s}.png`}
onClick={this.props.onClick}
></img>
{s}
</div>
))}
</div>
)}}
class SpecificChamp extends Component {
render() {
let champion = this.props.champion
let Spec = champion[champion.length - 1];
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{Spec}
</div>
</div>
)}
}
class Champions extends Component {
constructor(props) {
super(props);
this.handleAllChamps = this.handleAllChamps.bind(this);
this.handleSpecificChamp = this.handleSpecificChamp.bind(this);
this.NewChamp = this.NewChamp.bind(this);
this.state = {
champions: [],
champion: [],
clickedChamp: false,
thisChamp: 'ahri'
}}
NewChamp = (props) =>
{
let s = props.s;
props.champion.push(s);
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
Object.keys(response.data).map((a) => (s = a
))})
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
console.log(s)
console.log(response.data)
console.log(props.champion)
})
console.log(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`);
}
handleAllChamps = (props) => {
this.setState({ clickedChamp: true,
})};
handleSpecificChamp = () => {
this.setState({ clickedChamp: false,
})};
componentDidMount(props) {
const apiUrl = `http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion.json`;
fetch(apiUrl)
.then(response => { return response.json() })
.then((response) => {
this.setState({
champions: response.data
}, () => (this.state.champions))
return
})
}
render() {
const clickedChamp = this.state.clickedChamp;
let display;
if (clickedChamp ) {
display = <SpecificChamp champion={this.state.champion} onClick={this.handleSpecificChamp} s={this.state.thisChamp}/>;
} else {
display = <AllChamps champions={this.state.champions} onClick={this.handleAllChamps} NewChamp={this.NewChamp} thisChamp={this.state.thisChamp} champion={this.state.champion} />;
}
return (
<div>
<div className='champions'></div>
{display}
</div>
);
}
}
export default Champions;
Your response is in the form of Object of Objects. You've to use JSON.stringify(response.data) in order to view the entire data as a string in the debug console.
You will have to destructure the Object of objects.
Object.keys(response.data).map((key)=> console.log(response.data[key]))
In this case if it is just one key
response.data[s]

In React, is it possible to store a ref in a context?

I need global app-wide access to a VideoElement to play it on user events on browsers like Safari and was wondering if storing the VideoElement in a context would be the best way to do that. I programmatically play my video through a redux action and in Safari that is not possible unless it has been played once through a user triggered event (like a click)
Is it possible to store an element (ref) within a context? The VideoElement will be then rendered within the component which I want to have my video, and then other components will also have access to the context and be able to call functions such as usePlayVideo that based on the context's state, will either call videoElement.play() if this is the first time the video is being played, or dispatch the redux action to play the video programmatically otherwise
It is possible to store a ref into context! You need to create a context at first. Then you need to pass value to the context provider and create a ref object using useRef hook. After that, you pass the ref into the value.
Now, You have a ref object sharing between components under the context provider and if you want to retrieve or pass a new ref, you could use useContext hook to deal with it.
Here is the demo (codesandbox).
Here is the sample code.
import { createContext, useContext, useEffect, useRef, useState } from "react";
import "./styles.css";
const MyContext = createContext();
export const ContextStore = (props) => {
const ref = useRef();
return <MyContext.Provider value={ref}>{props.children}</MyContext.Provider>;
};
export default function App() {
return (
<>
<ContextStore>
<MyComponent />
<MyComponent2 />
</ContextStore>
</>
);
}
const MyComponent = () => {
const myContext = useContext(MyContext);
return (
<div className="App" ref={myContext}>
<h1>Hello MyComponent1</h1>
</div>
);
};
const MyComponent2 = () => {
const myContext = useContext(MyContext);
const [divRef, setDivRef] = useState();
useEffect(() => {
setDivRef(myContext);
}, [myContext]);
return (
<div className="App">
<h1>{divRef?.current && divRef.current.innerText}</h1>
</div>
);
};
You can use this approach:
VideoContext.js
import { createContext, createRef, useContext } from "react";
const VideoContext = createContext();
const videoRef = createRef();
export const VideoContextProvider = (props) => {
return (
<VideoContext.Provider value={videoRef}>
{props.children}
</VideoContext.Provider>
);
};
export const useVideoContext = () => useContext(VideoContext);
and App.js for example:
import { useState, useEffect } from "react";
import { useVideoContext, VideoContextProvider } from "./VideoContext";
const SomeComponent = () => {
const videoRef = useVideoContext();
return (
<div ref={videoRef}>
<h1>Hey</h1>
</div>
);
};
const SomeOtherComponent = () => {
const [ref, setRef] = useState();
const videoRef = useVideoContext();
useEffect(() => {
setRef(videoRef);
}, [videoRef]);
return (
<div>
<h1>{ref?.current?.innerText}</h1>
</div>
);
};
export default function App() {
return (
<>
<VideoContextProvider>
<SomeComponent />
</VideoContextProvider>
{/* ... */}
{/* Some other component in another part of the tree */}
<VideoContextProvider>
<SomeOtherComponent />
</VideoContextProvider>
</>
);
}
code sandbox
Why not? I'll say. Let's see if we can setup an example.
const fns = {}
const addDispatch = (name, fn) => { fns[name] = fn }
const dispatch = (name) => { fns[name] && fns[name]() }
const RefContext = createContext({ addDispatch, dispatch })
export default RefContext
const Child1 = () => {
const [video, dispatchVideo] = useState(...)
const { addDispatch } = useContext(RefContext)
useEffect(() => {
addDispatch('video', dispatchVideo)
}, [])
}
const Child2 = () => {
const { dispatch } = useContext(RefContext)
const onClick = () => { dispatch('video') }
...
}
The above two childs do not have to share the same ancestor.
I didn't use ref the way you wanted, but i think you can pass your ref to one of the function. This is a very basic idea. I haven't tested it yet. But seems it could work. A bit
I used this approach:
first I creacted the context and ContextProvider;
import React, { useRef } from "react";
export const ScrollContext = React.createContext();
const ScrollContextProvider = (props) => {
return (
<ScrollContext.Provider
value={{
productsRef: useRef(),
}}
>
{props.children}
</ScrollContext.Provider>
);
};
export default ScrollContextProvider;
then Added my provider in my index.js:
root.render(
<React.StrictMode>
<ScrollContextProvider>
<App />
</ScrollContextProvider>
</React.StrictMode>
);
after that I used my context where I needed it:
import React, { useContext } from "react";
import { ScrollContext } from "../../store/scroll-context";
const Products = () => {
const scrollCtx = useContext(ScrollContext);
return (
<section ref={scrollCtx.productsRef}>
// your code...
</section>
);
};
In my case I wanted to to scroll to the above component clicking a button from a different component:
import React, { useContext } from "react";
import { ScrollContext } from "../../store/scroll-context";
function Header() {
const scrollCtx = useContext(ScrollContext);
const scrollTo = () => {
setTimeout(() => {
scrollCtx.productsRef.current.scrollIntoView({ behavior: "smooth" });
}, 0);
};
return (
<header>
//your code ...
<button alt="A table with chair" onClick={scrollTo}>Order Now<button />
</header>
);
}
No. It's not possible to use Ref on context api. React ref is considered to be used on rendering element.
What you're looking for is to forward the ref, so that you can consume them wherever you want.

React function is not defined

I am trying to create a react component with imported data from Google API. I can see the code is working in the console.log but when I try to use that code in React render method, I am not getting anything. When I move my function inside the class it comes up as the function not defined. I cannot understand why?
function handleTouchTap() {
console.log('CHIP selected');
authorize();
}
function handleAccounts(response) {
console.log(response.result.username);
var username = response.result.username
console.log(username);
}
function authorize(event) {
var useImmidiate = event ? false : true;
var authData = {
client_id: CLIENT_ID,
scope: SCOPES,
immidiate: useImmidiate
};
gapi.auth.authorize(authData, function (response) {
gapi.client.load('analytics', 'v3').then(function () {
console.log(response);
gapi.client.analytics.management.accounts.list().then(handleAccounts);
});
});
}
class Chips extends React.Component {
render() {
return (
<div style={styles.wrapper}>
<Chip
onTouchTap={handleTouchTap}
style={styles.chip} >
<Avatar icon={<FontIcon className="material-icons">perm_identity</FontIcon>} />
Login
</Chip>
<Chip
style={styles.chip} >
<Avatar icon={<FontIcon className="material-icons">account_circle</FontIcon>} />
{this.username}
</Chip>
</div>
);
}
}
In most cases, when you want to render something that might change, you want to add it to the state. That way when you call setState the component knows it needs to rerender and show the changes.
Here I added the functions as component methods, so that you can call this.setState on the result. Ideally you would probably do this with redux and use actions but this will work as a self contained component.
class Chips extends React.Component {
handleTouchTap = () => {
console.log('CHIP selected');
this.authorize();
}
handleAccounts = (response) => {
var username = response.result.username;
this.setState({
username
});
}
authorize = (event) => {
var useImmidiate = event ? false : true;
var authData = {
client_id: CLIENT_ID,
scope: SCOPES,
immidiate: useImmidiate
};
gapi.auth.authorize(authData, (response) => {
gapi.client.load('analytics', 'v3').then(() => {
console.log(response);
gapi.client.analytics.management.accounts.list()
.then(this.handleAccounts);
});
});
}
render() {
return (
<div style={styles.wrapper}>
<Chip
onTouchTap={this.handleTouchTap}
style={styles.chip}>
<Avatar icon={<FontIcon className="material-icons">perm_identity</FontIcon>} />
Login
</Chip>
<Chip
style={styles.chip} >
<Avatar icon={<FontIcon className="material-icons">account_circle</FontIcon>} />
{this.state.username}
</Chip>
</div>
);
}
}