Reactjs Child Component not updating - reactjs

I'm having trouble updating the state of the child component.
If i upload an image it doesn't appear in the ReactChromakeyedImage component. (which is a package i use)
import './App.css';
import ReactChromakeyedImage from 'react-chromakeyed-image';
import React, { useState, useEffect } from 'react';
function App() {
const [file, setFile] = useState();
function handleChange(e) {
console.log(e.target.files);
setFile(URL.createObjectURL(e.target.files[0]));
}
return (
<div className="App">
<h2>Add Image:</h2>
<input type="file" onChange={handleChange} />
<h3>Original</h3>
<img style={{width: "400px"}} src={file} />
<h3>Chromakeyed</h3>
<ReactChromakeyedImage src={file} style={{width: "400px"}} findColor="#3CC156" replaceColor="#FFFFFF" tolerance={70}/>
</div>
);
}
export default App;

I figured it out. It's a bug in the lib. Internally, it checks the img tag it uses inside to see if the complete property has been set on the image HTML element. This goes to true when the browser has painted the image. However, it only does this on every rerender, and it it also does not wait until it becomes true (it's asynchronous). That's why it's flaky.
Really the lib needs to be forked to fix, but there is a pretty horrible workaround you could do (this is really not nice at all, but it might be your only option unless you fork).
In this solution we attach a load listener using the DOM API (bad!) and then force a rerender, which triggers the library to check the completed property again.
Try this https://codesandbox.io/s/bold-voice-bhxw36?file=/src/App.js:0-1085.
import ReactChromakeyedImage from "react-chromakeyed-image";
import React, { useState, useRef, useLayoutEffect } from "react";
function App() {
const [file, setFile] = useState();
const containerRef = useRef();
const [, forceReload] = useState(0);
function handleChange(e) {
setFile(URL.createObjectURL(e.target.files[0]));
}
useLayoutEffect(() => {
if (!containerRef.current) return;
containerRef.current.querySelector("img").addEventListener("load", () => {
forceReload((x) => x + 1);
});
}, [file]);
return (
<div className="App">
<h2>Add Image:</h2>
<input type="file" onChange={handleChange} />
<h3>Original</h3>
<img style={{width: "400px"}} src={file} />
<h3>Chromakeyed</h3>
{file && (
<div ref={containerRef}>
<ReactChromakeyedImage
src={file}
findColor="#3CC156"
replaceColor="#FFFFFF"
tolerance={70}
width="400"
height="400"
/>
</div>
)}
</div>
);
}
export default App;

I do not know this library, and it could be a bug or simply that values are just stored and don't get updated if you change the value of the image. My advice would be just to make the component re-render to update it. The simplest way is to add a key to it, whenever this key changes the component will get re-rendered. I use file.name in this example but any changing value would be fine.
<ReactChromakeyedImage key={file.name} src={file} style={{width: "400px"}} findColor="#3CC156" replaceColor="#FFFFFF" tolerance={70}/>

Related

Why storing form data in useState() hook in React?

I am somewhat familiar with React.
In react tutorials, I have seen people creating useState hooks to store form data and also making them controlled and started doing the same but recently I saw a video of coder creating a form without using any useState for storing the data?
This has left me stumped because I am not understanding why I need to store form data in useState hook now. For what type of situations, is it useful to use state or just simply take the form value?
Please help.
I have tried searching google, reading some blogs on useState hooks, how it is used where we want to re-render a component.
Like, If I am displaying some data, and want to change it on some event, then I will use useState hook so that it will re-render the display component, with the updated state.
But I am still not understanding its use case with forms.
In a React form, you may want to do something on the screen as a user is typing using an onChange callback. You would need to store the user's input in state in order to properly deal with it in a useEffect. Just storing naked variables in components in React is terrible practice.
You generally only use destructured constants or derived conditional variables after hooks are called in a component in order to affect the display or to pass hook results as input to other hooks.
I would be interested to see the video or tutorial that showed you how to do a form "without state" in React because I'm not sure how that's possible - unless they were using a third-party library like formik which abstracts the useState away from you through it's own methods.
*ADDED EXAMPLE
Consider this simple comparison of two forms, one with state and one without:
https://codesandbox.io/p/sandbox/zen-sammet-imfzl5
app.tsx
import { useState } from "react";
import "./App.css";
import FormWithState from "./FormWithState";
import FormWithoutState from "./FormWithoutState";
function App() {
return (
<>
<div
style={{
border: "1px solid green",
marginBottom: "1rem",
padding: "1rem",
}}
>
<div>
This form with state notifies after 3 characters, and on submit.
</div>
<FormWithState />
</div>
<div style={{ border: "1px solid red", padding: "1rem" }}>
<div>This form without state can't alert or useEffect</div>
<FormWithoutState />
</div>
</>
);
}
export default App;
FormWithState.tsx
import { useEffect, useState } from "react";
import "./App.css";
export default function FormWithState() {
const [message, setMessage] = useState("");
const onChange = (e: any) => {
setMessage(e.target.value);
};
const onSubmit = (e: any) => {
e.preventDefault();
alert(`You sent the message: ${message}`);
};
useEffect(() => {
if (message.length >= 3) {
alert("This message is longer than 3 characters. Wow!");
}
}, [message]);
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" name="name" onChange={onChange} />
<button type="submit">Submit</button>
</form>
</div>
);
}
FormWithoutState.tsx
import { useEffect } from "react";
import "./App.css";
export default function FormWithState() {
let message = "";
const onChange = (e: any) => {
message = e.target.value;
};
const onSubmit = (e: any) => {
e.preventDefault();
alert(`You sent the message: ${message}`);
};
useEffect(() => {
if (message.length > 3) {
alert("This message is longer than 3 characters. Wow!");
}
}, [message]);
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" name="name" onChange={onChange} />
<button type="submit">Submit</button>
</form>
</div>
);
}
Notice how the green form can alert and useEffect as the user types / submits - while the red form cannot.
Both Forms:
This works (with state):
This does not work (without state):
I hope that helps a bit!

How do i use useEffect() / useState() hooks here

I am new to React Js and learning to implement certain functionalities by developing a website. Here i need to implement something like this -
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_typewriter but on page load (not on button click)
But as we know that React has Hooks, which needs to be used while implementing functionalities in the project. So i tried to implement Hook, but need some help in using it, I researched some examples on internet but didn't understood and doesn't fit for my current requirement.
Main.js
import React, { useEffect } from 'react';
import '../App.css';
const Main = () => {
useEffect( () => {
var i = 0;
var txt = 'Some Text here lorem ipsum lorem ipsum';
var speed = 50;
function typeWriter() {
if (i < txt.length) {
document.getElementById("typingText").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, speed);
}
}
});
return(
<main>
<div className='row'>
<img src='image.png' height='auto' width='100%' alt="banner"/>
<div className='position-absolute top-50 start-0 translate-middle-y p-5'>
<div class="card bg-black" style={{width: '30%'}}>
<div class="card-body text-white fw-bold">
<h1 id="typingText"></h1>
</div>
</div>
</div>
</div>
</main>
);
export default Main;
First of all, you are missing the array of dependencies of the hook. This prop is extemely important, otherwise the useEffect hook will keep on calling, reaching an overflow of calls.
If I understand what you want, every x milliseconds the typed text should change. useEffect will do the work. In this case, as you want only to run when the component mounts, the dependency array of the hook will be empty ([]). Inside the hook, you can set up an interval, that keeps running the function that you want. With this brief explanation, you could do:
import React, { useEffect, useState } from 'react';
import '../App.css';
const Main = () => {
// We will keep track of how many letters are being displayed
const [index, setIndex] = useState(0);
const type = () => {
setIndex((prev) => Math.min(prev + 1, 50));
};
useEffect(() => {
const id = setInterval(type, 500);
// It is important to clear the interval when the component unmounts!
return () => clearInterval(id);
}, []); // We are setting an empty dep array
return (
<main>
<div className='row'>
<img src='image.png' height='auto' width='100%' alt='banner' />
<div className='position-absolute top-50 start-0 translate-middle-y p-5'>
<div class='card bg-black' style={{ width: '30%' }}>
<div class='card-body text-white fw-bold'>
<h1 id='typingText'>
{'Your text'.substring(0, index)}
</h1>
</div>
</div>
</div>
</div>
</main>
);
};
export default Main;
To sum up, what we are doing is:
Setting an interval to repeat after 500ms. As commented in the code, it is crucial to clear the interval once the component unmounts. The setInterval returns the identifier of the interval, which is used to identify the interval to remove with clearInterval.
Instead of using the window to manipulate the DOM, we keep track of the state, increasing the letters to display from our string.
Finally, we "cut" the string displaying from 0 to the current index, which will be incremented every 500ms, creating the typewritter effect.
Hope to have been clear!
I recommend you take a look at the react hooks documentation.

Is there any way to call the api and save it using useState in reactjs?

I am facing some issue while calling api in useEffect and saving it using useState each time my page is reloaded or visited.
I am call the api in useEffect and than setting its data using useState in datas variable.and using that data to pass some value in element <Casebox / >.
When I implemented it for first time, it works absolutely fine, I was getting the data on my screen what i wanted to show, But when I reloaded the page it throws some error on the screen saying my datas variable is undefined.
I am sharing my code and snippet of error i got. Please help me to solve this error.
Code :-
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Casebox from './casebox';
import StatecaseContainer from './statecaseContainer';
function Home(){
const [datas, setDatas] = React.useState({});
React.useEffect(function effectFunction() {
function fetchapi() {
axios.get('https://api.covid19india.org/data.json')
.then((response) => {
setDatas(response.data);
});
}
fetchapi();
}, []);
return (
<div className="container-fluid caseinfocontainer">
<div className="row row-cols-1 row-cols-md-4 justify-content-center">
<Casebox
caseId="totalcases"
caseHeading="Infected"
caseCount={datas.statewise[0].confirmed}
/>
<Casebox
caseId="recovered"
caseHeading="Recovered"
caseCount="2892223"
/>
<Casebox
caseId="deaths"
caseHeading="Deaths"
caseCount="2892223"
/>
<Casebox
caseId="vaccinated"
caseHeading="Vaccinated"
caseCount="2892223"
/>
</div>
<StatecaseContainer />
</div>
)
}
export default Home;
My Error :-
enter image description here
Note :- I want to call api when the page is visited or reloaded.
Your fetch method takes some time to complete, so there is a time where your datas state is undefined on initial state. You can simply combat this by adding conditional rendering to check if the datas state exist yet.
By adding datas && in front of your div, it will check the state of datas. If datas is undefined, it won't show the div, else, it will show.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Casebox from './casebox';
import StatecaseContainer from './statecaseContainer';
function Home(){
const [datas, setDatas] = React.useState({});
React.useEffect(function effectFunction() {
function fetchapi() {
axios.get('https://api.covid19india.org/data.json')
.then((response) => {
setDatas(response.data);
});
}
fetchapi();
}, []);
return (
<div className="container-fluid caseinfocontainer">
{{datas && <div className="row row-cols-1 row-cols-md-4 justify-content-center">
<Casebox
caseId="totalcases"
caseHeading="Infected"
caseCount={datas.statewise[0].confirmed}
/>
<Casebox
caseId="recovered"
caseHeading="Recovered"
caseCount="2892223"
/>
<Casebox
caseId="deaths"
caseHeading="Deaths"
caseCount="2892223"
/>
<Casebox
caseId="vaccinated"
caseHeading="Vaccinated"
caseCount="2892223"
/>
</div>}}
<StatecaseContainer />
</div>
)
}
export default Home;

React Hooks Calling GET Request On Modal Popup

Thanks in advance for any help you can provide. I am trying to create a Modal is react and call a get request to load details of a task.
I have most of it working (I think), but essentially what I have done is createa custom Modal Hook that toggles two modals.
The second of the two modals is meant to open a task and render the task details in a form for editing but I am unable to get it working.
Here is the useModal hook:
import { useState } from "react";
const useModal = () => {
const [isShowing, setIsShowing] = useState(false);
const [secondModalIsShowing, secondModalSetIsShowing] = useState(false);
function toggle() {
setIsShowing(!isShowing);
}
function secondToggle() {
secondModalSetIsShowing(!secondModalIsShowing);
console.log("clicked");
}
return {
isShowing,
toggle,
secondModalIsShowing,
secondToggle,
};
};
export default useModal;
I then call the function for the secondToggle which fires the code below to render the modal. Now as you may see I have to comment out the section where it calls getTask() with match.params.id, as well as the component that is then meant to be rendered in the modal.
If I don't do that I get an error message with the following " Line 23:5: Expected an assignment or function call and instead saw an expression no-unused-expressions"
import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import TaskItem from "../tasks/task-item/TaskItem";
import { getTask } from "../../actions/task";
import ReactDOM from "react-dom";
import "./Modal.styles.scss";
import "../dashboard/Dashboard.styles.scss";
import Task from "../task/Task";
import TaskEdit from "../task/TaskEdit";
const TaskModal = ({
getTask,
task: { task, loading },
match,
secondModalIsShowing,
hide,
}) => {
const [displayEdit, toggleDisplayEdit] = useState(false);
useEffect(() => {
getTask();
// match.params.id;
}, [getTask]);
return secondModalIsShowing
? ReactDOM.createPortal(
<React.Fragment>
<button
type="submit"
value="toggle"
onClick={() => toggleDisplayEdit(!displayEdit)}
>
Show/Edit
</button>
{(displayEdit && <TaskItem task={task} />) || (
<div>{/* <TaskEdit /> */}</div>
)}
<div className="modal-overlay" />
<div
className="modal-wrapper"
aria-modal
aria-hidden
tabIndex={-1}
role="dialog"
>
<div className="modal">
<div className="modal-header">
Add New Task
<button
type="button"
className="modal-header__button"
data-dismiss="modal"
aria-label="Close"
onClick={hide}
>
<span aria-hidden="true">×</span>
</button>
</div>
</div>
</div>
</React.Fragment>,
document.body
)
: null;
};
Now if I render this EditTask component outside the modal as a normal component it works correctly. I can also get the modal to render when it's not trying to display the EditTask component.
As a result, I think it's related to the Route path and passing the response to the TaskModal component? When I click the modal to open I cant get it to render the URL with the task ID and therefore I cant render the details of the task in the modal.
<Route path="/taskedit/:id" component={TaskModal} />
For context, I think this guide comes close to solving my issue (https://blog.logrocket.com/building-a-modal-module-for-react-with-react-router/) but I am not familiar with working with class-based components and when I try and convert to functional-based components I'm running into even more issues.
Thanks in advance for any insight you can provide as I keep trying to work through this.
Paul
The first issue I am seeing is you have to pass the task id to TaskModal component
<Route path="/taskedit/:id"
render={(props) => <TaskModal {...props} />}>
</Route>
This will make the task id available as property in TaskModal.
Then in the TaskModal, fetch like below
let taskid = prop.match.params.id;

How to refactor react hooks nested function properly

I have a function component which uses two states and both are changed based on event triggered.
I've read on react docs that is a bad idea to change states in nested function or condition. I also seen some examples using useEffects, but I have no clear idea how to properly refactor this.
here is my entire component:
import React, { useState, useEffect } from 'react'
import './App.css'
import AppHeader from '../app-header'
import AppFooter from '../app-footer'
import SearchInput from '../search-input'
import Stats from '../stats'
import Chart from '../chart'
import { getBundleInfoAPI } from '../../services/hostApi'
import 'react-loader-spinner/dist/loader/css/react-spinner-loader.css'
import Loader from 'react-loader-spinner'
function App() {
const [isSpinnerVisible, setSpinnerVisible] = useState(false)
const [bundleData, setBundleData] = useState({})
const _handleOnItemSelected = (item) => {
if (item && item.package && item.package.name && item.package.version) {
setSpinnerVisible(true)
getBundleInfoAPI(item.package.name, item.package.version)
.then(resposeData => setBundleData(resposeData))
.finally(() => setSpinnerVisible(false))
} else {
// TODO - implement an error handler component?
console.warn('WARNING: The selected bundle does not have name or version!')
}
}
return (
<div className="app">
<AppHeader />
<main>
<SearchInput onItemSelected={_handleOnItemSelected} />
<div className="app-main-inner-container">
<Loader type="Triangle" color="#00BFFF" height={200} width={200} visible={isSpinnerVisible} />
{!isSpinnerVisible &&
<div className="app-stats-chart-container">
<section className="app-stats-container"><Stats size={bundleData.stats} /></section>
<section className="app-chart-container"><Chart bundleChartData={bundleData.chart} /></section>
</div>
}
</div>
</main>
<AppFooter />
</div>
)
}
export default App
Docs section you are referring to means you must not put line with useState inside of nested functions, conditions, loops.
Calling setter returned by hook is definitely fine and correct.
This is fine, you are showing the loading screen when starting fetch and then hiding it when the fetch is done... no refactoring needed

Resources