I am building a React functional component that uses some state variables, and I am trying to modify some of these variables from an external function thats called on a button click event, but when I pass the reference to the state methods to this external function, all of them are undefined. What could be the cause? If I just put the exact same code within the functional component, it works perfectly as intended.
import React from "react";
import {CodeArea, Table, EmptyField, Button} from '../util/util.js'
import {Step, Load} from "./buttons.js" // The external function in question, Loadfunction
Core(props){
const registersInitial = new Array(32).fill(0);
let buttonNamesInitial = ['LOAD','play', 'step-forward', 'run-to', 'step-back','pause','halt', 'rerun', 'add'];
const [bigText, setText] = React.useState();
const [registers, setRegisters] = React.useState(registersInitial);
const [running, setRunning] = React.useState(false);
const [programCounter, setProgramCounter] = React.useState(0);
const [buttonNames, setButtonNames] = React.useState(buttonNamesInitial);
const [lines, setLines] = React.useState([]);
const getBigText = () => {
return bigText;
}
const getRunning = () =>{
return running;
}
const getButtonNames = () => {
return buttonNames;
}
//... some code here thats irrelevant
function getQuickbarContents(){
let functions = [ //Backend will come here
() => Load(setRunning, getRunning, setButtonNames, getButtonNames, setProgramCounter, setLines, getBigText), //Where Load gets called
() => console.log("code running..."),
() => console.log("stepping to next line..."),
() => console.log("running to location..."),
() => console.log("stepping..."),
() => console.log("pausing..."),
() => console.log("halting..."),
() => console.log("running again..."),
() => console.log("select widget to add...")
]
let contents = [];
let row = [];
for (let i = 0; i<9; i++){
row.push(<Button onClick ={functions[i]} className='quickbar' name={buttonNames[i]}/>);
contents.push(row);
row = [];
}
return contents
}
const divs = [];
let buttons = getQuickbarContents();
divs.push(<div key='left' className='left'><Table name='quickbar' rows='7' cols='1' fill={buttons}/> </div>);
//... some more code to push more components do divs
return divs;}
export default Core;`
Button looks like this:
function Button({onClick, className, name}){
return <button onClick={onClick} className={className} name={name}>{name}</button>
}
and Load like this:
export function Load({setRunning, getRunning, setButtonNames, getButtonNames, setProgramCounter, setLines, getBigText}){
let newButtonName;
if (!getRunning()){ // Functions errors out with getRunning() undefined
herenewButtonName = "Reload";
}
else{ //while running if user wants to reload
newButtonName = "LOAD";
}
let lines = getBigText().split(/\n/);
setLines(lines);
setProgramCounter(0);
setRunning(!getRunning());
const newButtonNames = getButtonNames().map((value, index) =>{
if (index === 0){
return (newButtonName);
}
return value;
})
setButtonNames(newButtonNames);
}
So essentially in my head the flow should be: state methods initialised -> button components created -> wait for click of a button -> update state variablesBut clearly, something goes wrong along the way.
I've tried using inspection mode debugging, which revealed that in fact all of the parameters to Load are undefined when they are evaluated.
Note, that everything works as intended if I change the code up like this, eg. just put the whole function within the React component;
//... everything same as before
function getQuickbarContents(){
let functions = [
() =>{
let newButtonName;
if (!getRunning()){ //User clicks to start running
newButtonName = "Reload";
}
else{
newButtonName = "LOAD";
}
let lines = getBigText().split(/\n/);
setLines(lines);
setProgramCounter(0);
setRunning(!getRunning());
const newButtonNames = getButtonNames().map((value, index) =>{
if (index === 0){
return (newButtonName);
}
return value;
})
setButtonNames(newButtonNames)},
() => console.log("code running..."),
() => console.log("stepping to next line..."),
() => console.log("running to location..."),
() => Step(setRegisters, registers, setProgramCounter, programCounter, lines[programCounter]),
() => console.log("pausing..."),
() => console.log("halting..."),
() => console.log("running again..."),
() => console.log("select widget to add...")
]
//...everything same as before
so consequently the error is somewhere in the way I pass parameters to Load, or maybe I'm doing something I shouldn't be doing in React. Either way I have no clue, any ideas?
Problem was in the way parameters were defined in Load, as #robin_zigmond pointed out. Fixed now.
Related
i know that the problem is that let todoList is an empty array, but i dont know how to solve it.
the id tags in my created html is so e can create a delete button later
heres my code:
const textArea = document.querySelector("textarea");
const button = document.querySelector("button");
const listContainer = document.querySelector(".list-container");
let id = 0;
let todoList = [];
button.onclick = function () {
const listItem = {
title: textArea.value,
};
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
};
function addToStorage(items) {
const stringify = JSON.stringify(items);
localStorage.setItem("list", stringify);
}
function getFromStorage() {
const data = localStorage.getItem("list");
const unstrigified = JSON.parse(data);
return unstrigified;
}
const createHtml = (data) => {
id++;
listContainer.innerHTML = "";
data.forEach((item) => {
listContainer.innerHTML += `<div class="list-item" data-id=${id}><p>${item.title} </p><button class="remove" data-id=${id}>Delete</button></div>`;
});
};
The problem here is you just forgot to load the data from localStorage when the page loaded like this
window.onLoad = () => {
const dataFromStorage = getFromStorage();
if(dataFromStorage){
createHtml(dataFromStorage);
} else {
createHtml([]);
}
}
The problem in the code is as follows
Initially the todolist will be an empty array. so when you do the below
todoList.push(listItem);
// adding to local storage which will override the existing todos when page is refreshed
addToStorage(todoList);
// So when the below line is executed only the latest todo will be returned
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
Fix:
Initialise the todos from localstorage instead of an empty array
let todoList = [];
// change it as below
let todoList = getFromStorage();
Now Modify the getFromStorage() as below
// If the data is present in the localStorage then return it, else return empty array
function getFromStorage() {
const data = localStorage.getItem("list");
if (!data) return [];
const unstrigified = JSON.parse(data);
return unstrigified;
}
Now when the page is loaded, we need to display the todos. Add the below lines of code
window.onload = function () {
createHtml(todoList);
};
That's it. This will fix the issue.
Few minor improvements can be made as well.
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage(); // this line is not necessary, remove it
createHtml(dataFromStorage); // change this to createHtml(todoList)
Codepen
Thanks.
I am building an animation where the letters of two words appear one by one, similar to a slide-in effect. I have the code made with jQuery, but I need to implement it in my React app (built with hooks). The code that I have takes the text, splits it creating individual letters, and adds spans between those letters. This is the following code that I need to convert to React:
const logoText = document.querySelector('.logo');
const stringText = logoText.textContent;
const splitText = stringText.split("");
for (let i=0; i < splitText.length; i++) {
text.innerHTML += "<span>" + splitText + "</span>"
}
let char = 0;
let timer = setInterval(onTick, 50)
I was wondering if you guys could help me figure it out. Thanks a lot!
You need to iterate over the text and create a timeout function for every letter with a different time of execution, that way will be visible the slide effect you are expecting:
Custom hook
const useSlideInText = text => {
const [slide, setSlide] = useState([]);
useEffect(() => {
Array.from(text).forEach((char, index) => {
const timeout = setTimeout(
() =>
setSlide(prev => (
<>
{prev}
<span>{char}</span>
</>
)),
index * 100
);
});
}, []);
return slide;
};
Usage
function App() {
const slide = useSlideInText("hello");
return (
<div>
{slide}
</div>
);
}
Working example
I am assuming the React components that you want to run this hook in possess the text you want to split. I am also assuming that on the interval, you want to reveal more of the text. In that case my example solution would look like this:
Hook
import {useState, useEffect} from "react";
const useSlideInText = (text) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
});
return text.split('').slice(0, revealed).map((char) => (<span>{char}</span>));
}
Example usage
const MyComponent = (props) => {
const displayText = useSlideInText(props.text);
return <div>{displayText}</div>;
};
going off of the other answer:
const generateDisplayTest = (text, numChars) => text.split('').slice(0, numChars).map((char) => (<span>{char}</span>));
const MyComponent = (props) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < props.text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
}, [revealed]);
const displayText = generateDisplayTest(props.text, revealed);
return <div>{displayText}</div>;
};
including [revealed] in the useEffect means that useEffect will run every time that revealed changes. Also I always feel that useState/useEffect should live on the component, it has been that way in the place I worked but I'm not sure if that is industry standard.
handleSearch = (ev, index) => {
const { dropdown_datas, dropdown_datas_search } = this.state;
const keys = Object.keys(dropdown_datas_search);
const current_key = keys[index];
let value = ev.target.value;
let current_dropdown_data = dropdown_datas_search[current_key];
const filtered_data = current_dropdown_data.filter(robots => {
return robots.label.toLowerCase().includes(value.toLowerCase());
})
dropdown_datas[current_key] = filtered_data
this.setState({
dropdown_datas: dropdown_datas
})
}
I am using this function in react.
"dropdown_datas_search" data is coming from state which data i don't wants change.
But, when i am doing the filter "dropdown_datas_search" is also changing from state.
I wants to only change and filter "current_dropdown_data" data .
Please take a look.
How can i prevent doing this.
Working on dialog component with angular js and now I find out that my function is subscribed and in if condition do not quit method, but continuously executing another function afterClosed() , here is example of code :
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
return;
}
}
});
dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
What is the best and optimized way to unsubscribe beforeClosed() function?
So from your description, I understand that you dont want a second subscription to happen if the condition in the first subscriber is true, right? But you subscription will happen anyway because you instantiated it in the method, the code in the subscribe() it's just a callback. So if you dont want a lot of rewriting I will suggest storing
subscriptions in variables, so you will have an access to them and can unsubscribe at any time.
openCreateNewContentDialog(): void {
const oldData = this.dataSource.data;
const dialogConfig = AppConstants.matDialogConfig();
const dialog = this.dialog.open(LicenseDialogComponent, dialogConfig);
const beforeClosed = dialog.beforeClosed().subscribe(licenceDate => {
for (const datesToCheck of oldData) {
const newDateFrom = new Date(licenceDate.expirationDateFrom);
const oldDateTo = new Date(datesToCheck.expirationDateTo.toString());
if (newDateFrom <= oldDateTo) {
// console.log('return?');
afterClosed.unsubscribe();
return;
}
}
});
const afterClosed = dialog.afterClosed().subscribe(licence => {
if (licence) {
this._value.push(licence);
this.dataSource.data = this.value;
this.change();
}
});
}
I hope it helps! Also you can try https://www.digitalocean.com/community/tutorials/angular-takeuntil-rxjs-unsubscribe if you have to handle multiple subscriptions.
I'm trying to render certain content onto my screen but currently it's not showing up. I know the function is entering the if conditional since I tested it with console logging.
Here is where I write the conditional to render a certain content.
renderContent = (): Content => {
this.props.contents.forEach((content:SomeType) => {
if (content.booleanCondition){
return <ImportantComponent contentData = {this.usefulFunction()}/>
}
}
}
And here is where I want to render everything.
renderEverything = () => {
return (
{this.renderContent()}
);
};
Alternatively, is there a way I can directly write the conditional in the renderEverything so I don't have to call the function renderContent?
If you want to render an ImportantComponent for each content element that satisfies the booleanCondition:
renderEverything = () => {
return this.props.contents.map(
content => content.booleanCondition && <ImportantComponent key={content} contentData = {this.usefulFunction()}/>
)
}
If you want to render only one ImportantComponent if any content element satisfies the booleanCondition:
renderEverything = () => {
const contentToBeShown = this.props.contents.find(content => content.booleanCondition)
if(contentToBeShown)
return <ImportantComponent contentData = {this.usefulFunction()}/>
}