useState and useRef don't work together properly - reactjs

I'm in a case where I need to combine useState and useRef in a way, but I'm having a problem when it comes to deleting, so for example when I call the delete the update function firstly I update the state and then update the user jeff, but the deleted item firstly is deleted from the iterated and showing list, then again pop-ups, but kinda messed, the content is not the same.
Here is how I manage delete:
const Delete = (props: any, index: number, editorsRef: any): void => {
const newEmployment = cloneDeep(props.resume.employmentHistory.positions)
newEmployment.splice(index, 1)
props.setResume(
{
...props.resume, employmentHistory:
{
...props.resume.employmentHistory,
positions: newEmployment
}
}
)
const refs = {} as any;
newEmployment.map(
(position: any, index: number) =>
refs[index] = position.description
)
editorsRef.current = refs;
console.log(newEmployment, editorsRef.current)
}
And here is the iteration of the editor that has the value of useReff, and the interaction and everything related to how I change the state, something Is missing but I don't know, I really don't.
//#ts-nocheck
import { Item } from '../..';
import { WorkItemValidation } from '../../../../../utils/Validation';
import { Date, Input } from '../../../Shared';
import { Editor } from '../../..';
import { Items } from '../Components';
import { Container, Liner, Linerier } from './Components'
import { useCallback, useEffect, useRef, useState } from 'react';
import { Employment as Work } from '../../../../../utils/operations/Resume';
import { debounce } from 'lodash';
const Employment: any = (props: any) => {
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
const [loading, setLoading] = useState(true);
const editorsRef = useRef({})
const currentIndexRef = useRef('')
console.log(props, 'props', editorsRef, 'editorsRef')
useEffect(() => {
if (loading === true) {
const refs = {}
props.resume.employmentHistory.positions.map(
(position: any, index: number) =>
refs[index] = position.description
)
editorsRef.current = refs;
setLoading(false);
}
}, [])
const isFocusedRef = useRef(false);
const autoUpdateCallbackRef = useRef(null);
const callWorkDescription = useCallback(
debounce((props, currentIndex, editor) => {
Work.Description(props, currentIndex, editor);
currentIndexRef.current = ''
}, 1000),
[]
);
const handleDescription = (e: any, index: number) => {
currentIndexRef.current = index
editorsRef.current = {
...editorsRef.current,
[index]: e,
}
callWorkDescription(props, index, e)
};
useEffect(() => {
const intervalId = setInterval(() => {
if (isFocusedRef.current) autoUpdateCallbackRef.current?.();
}, 1000);
return () => clearInterval(intervalId);
}, []);
const handleFocus = () => isFocusedRef.current = true;
const handleBlur = () => isFocusedRef.current = false;
return (
<Container>
{
loading === false &&
<Items>
{
props.resume.employmentHistory.positions.map(
(position: any, index: number) => {
return (
<Item delete={() => Work.Delete(props, index, editorsRef)}
>
<Liner style={{ width: '100%' }}>
<Editor
id={`editor-${index}`}
label="Description"
value={editorsRef.current[index] && editorsRef.current[index] || ''}
placeholder="Brief description of the role..."
onChange={(e) => handleDescription(e, index)}
onFocus={handleFocus}
onBlur={handleBlur}
/>
</Liner>
</Item>
)
}
)
}
</Items>
}
</Container >
)
}
export default Employment;

Related

TypeScript Reset countdown timer when all questions have been answered or timer hits zero

I followed a tutorial online for a react quiz using typescript. I have the countdown/timer working (with help from a friend) but now I am looking to reset it back to the original countdown value when either the last question has been answered or when the countdown timer has reached zero seconds.
App.tsx
import React, { useEffect, useState } from 'react';
// Components
import QuestionCard from './components/QuestionCard';
// Types
import { QuestionState } from './API';
import Timer, { useTimer } from './components/Timer';
import StartButton from './components/StartButton';
export type AnswerObject = {
question: string;
answer: string;
correct: boolean;
correctAnswer: string;
};
const TOTAL_QUESTIONS = 10;
const TIME_ALLOWED = 20;
const App = () => {
const [loading, setLoading] = useState(false);
const [questions, setQuestions] = useState<QuestionState[]>([]);
const [number, setNumber] = useState(0);
const [userAnswers, setUserAnswers] = useState<AnswerObject[]>([]);
const [score, setScore] = useState(0);
const [gameOver, setGameOver] = useState(true);
const { timeRemaining, toggle, reset, isActive } = useTimer(TIME_ALLOWED);
const checkAnswer = (e: React.MouseEvent<HTMLButtonElement>) => {
if (!gameOver) {
//Users answer
const answer = e.currentTarget.value;
// Check answer against correct answer
const correct = questions[number].correct_answer === answer;
// Add score if answer is correct
if (correct) setScore((prev) => prev + 1);
// Save answer in the array for user answers
const answerObject = {
question: questions[number].question,
answer,
correct,
correctAnswer: questions[number].correct_answer,
};
setUserAnswers((prev) => [...prev, answerObject]);
console.log({ answerObject });
}
};
const nextQuestion = () => {
// Move on to the next question if not the last question
const nextQuestion = number + 1;
if (nextQuestion === TOTAL_QUESTIONS) {
setGameOver(true);
} else {
setNumber(nextQuestion);
}
};
useEffect(() => {
// If the timer reaches 0, the game is over
if (timeRemaining <= 0) {
setGameOver(true);
}
}, [timeRemaining]);
return (
<div className="App">
<h1>REACT QUIZ</h1>
{(gameOver || userAnswers.length === TOTAL_QUESTIONS) && (
<StartButton
setLoading={setLoading}
setQuestions={setQuestions}
setUserAnswers={setUserAnswers}
setNumber={setNumber}
setScore={setScore}
setGameOver={setGameOver}
toggleTimer={toggle}
/>
)}
{!gameOver ? <p className="score">Score: {score}</p> : null}
{loading && <p>Loading Questions...</p>}
{!loading && !gameOver && (
<QuestionCard
questionNr={number + 1}
totalQuestions={TOTAL_QUESTIONS}
question={questions[number].question}
answers={questions[number].answers}
userAnswer={userAnswers ? userAnswers[number] : undefined}
callback={checkAnswer}
/>
)}
{!gameOver &&
!loading &&
userAnswers.length === number + 1 &&
number !== TOTAL_QUESTIONS - 1 ? (
<button className="next" onClick={nextQuestion}>
Next Question
</button>
) : null}
<Timer
timeRemaining={timeRemaining}
isActive={isActive}
toggle={toggle}
reset={reset}
/>
</div>
);
};
export default App;
Timer.tsx
import React, { useMemo, useState, useEffect } from 'react';
export const useTimer = (timeAllowed: number) => {
const [seconds, setSeconds] = useState(0);
const [isActive, setIsActive] = useState(false);
const timeRemaining = useMemo(
() => timeAllowed - seconds,
[timeAllowed, seconds]
);
function toggle() {
setIsActive(!isActive);
}
function reset() {
setSeconds(0);
setIsActive(false);
}
useEffect(() => {
let interval: any = null;
if (isActive) {
interval = setInterval(() => {
setSeconds((seconds) => seconds + 1);
}, 1000);
} else if (!isActive && seconds !== 0) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [isActive, seconds]);
return { timeRemaining, toggle, reset, isActive };
};
const Timer = ({ timeRemaining }: any) => {
return <p className="time">{timeRemaining}s</p>;
};
export default Timer;
Startbutton.tsx
import React, { useState } from 'react';
import { Difficulty, fetchQuizQuestions } from '../API';
export type AnswerObject = {
question: string;
answer: string;
correct: boolean;
correctAnswer: string;
};
const TOTAL_QUESTIONS = 10;
interface IStartButton {
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
setQuestions: React.Dispatch<React.SetStateAction<any>>;
setUserAnswers: React.Dispatch<React.SetStateAction<any>>;
setNumber: React.Dispatch<React.SetStateAction<any>>;
setScore: React.Dispatch<React.SetStateAction<any>>;
setGameOver: React.Dispatch<React.SetStateAction<boolean>>;
toggleTimer: () => void;
}
const StartButton = ({
setLoading,
setQuestions,
setUserAnswers,
setNumber,
setScore,
setGameOver,
toggleTimer,
}: IStartButton) => {
const startTrivia = async () => {
setLoading(true);
setGameOver(false);
const newQuestions = await fetchQuizQuestions(
TOTAL_QUESTIONS,
Difficulty.EASY
);
toggleTimer();
setQuestions(newQuestions);
setScore(0);
setUserAnswers([]);
setNumber(0);
setLoading(false);
};
return (
<button className="start" onClick={startTrivia}>
Start
</button>
);
};
export default StartButton;
I thought about adding to the nextQuestion function in the if else statement that the Timer function would reset, but I wasn't too sure because of the useEffect in timer.tsx. So far when all the questions have been answered the timer keeps going until you hit the start button again. Thanks for the help in advance.

Typescript: How to update the state

In my project, I have an email field to implement using the chip component. But I am facing a problem here, first time, when I paste multiple email values it gets inserted into the field, but second time when I copy some other values and paste them into the field, it replaces the previous values.
In first time:
Secnod time when I paste "abc4#abc.com" :
previous values replace with the current value.
import Chip from "#material-ui/core/Chip";
import TextField from "#material-ui/core/TextField";
import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
export const TagActions = () => {
const [items, setItem] = useState<string[]>([]);
const [value, setValue] = useState("");
const [error, setError] = useState("");
const divRef = useRef<HTMLDivElement>(null);
const [flag, setFlag] = useState(false);
const handleDelete = (item: any) => {
console.log("handleDelete", item);
const result = items.filter((i) => i !== item);
setItem(result);
};
const handleItemEdit = (item: any) => {
console.log("handleItemEdit", item);
const result = items.filter((i) => i !== item);
setItem(result);
setValue(item);
console.log("value", value);
};
const handleKeyDown = (evt: any) => {
if (["Enter", "Tab", ","].includes(evt.key)) {
evt.preventDefault();
var test = value.trim();
if (test && isValid(test)) {
items.push(test);
setValue("");
}
}
};
const isValid = (email: any) => {
let error = null;
if (isInList(email)) {
error = `${email} has already been added.`;
}
if (!isEmail(email)) {
setFlag(true);
// error = `${email} is not a valid email address.`;
}
if (error) {
setError(error);
return false;
}
return true;
};
const isInList = (email: any) => {
return items.includes(email);
};
const isEmail = (email: any) => {
return /[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/.test(email);
};
const handleChange = (evt: any) => {
setValue(evt.target.value);
// setError("")
};
const handlePaste = (evt: any) => {
evt.preventDefault();
var paste = evt.clipboardData.getData("text");
console.log("pppp", paste);
var emails = paste.match(/[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/g);
if (emails) {
console.log("inside if", emails);
var toBeAdded = emails.filter((email: any) => !isInList(email));
setItem(toBeAdded);
}
};
return (
<>
<div>
<TextField
id="outlined-basic"
variant="outlined"
InputProps={{
startAdornment: items.map((item) => (
<Chip
className={!isEmail(item) ? "chip-tag-error" : "chip-tag"}
key={item}
tabIndex={-1}
label={item}
onDelete={() => handleDelete(item)}
onClick={() => handleItemEdit(item)}
/>
)),
}}
ref={divRef}
value={value}
placeholder="Type or paste email addresses and press `Enter`..."
onKeyDown={(e) => handleKeyDown(e)}
onChange={(e) => handleChange(e)}
onPaste={(e) => handlePaste(e)}
/>
</div>
{error && <p className="error">{error}</p>}
</>
);
};
I am a beginner in react typescript. Please give me a solution to solve this situation.
Append to the list instead of overwriting it like
setItem(i => [...i, ...toBeAdded]);

Unable to update react state with an array

I can make a successful call to getApiSuggestions with data returned. However I'm unable to assign this to my state.
As you can see my console output shows that the value for response has an array. However, when attempting to assign it to wikiResults:response the array remains empty.
note that this is a modification of react-search-autocomplete
Am I attempting to pass the variables incorrectly?
NarrativeSearch.js
import React, {useContext, useState, useEffect} from "react";
import './search.css'
import { ReactSearchAutocomplete } from 'react-search-autocomplete'
import { getApiSuggestions } from '../../requests/requests';
import {TextSearchContext} from "../../contexts/TextSearchContext"
import {SearchContext} from "../../contexts/SearchContext"
function Search() {
const {textFilterState, setTextFilterState} = useContext(TextSearchContext);
const [wikiTitleResults, setWikiTitleResults] = useState({wikiResults:[]});
var cnJson = wikiTitleResults;
const items = wikiTitleResults.wikiResults;
const handleOnSearch = (string, results) => {
console.log("STRING: ", string)
getApiSuggestions(string).then(response => {
console.log("RESPONSE: ", response);
setWikiTitleResults({wikiResults:response}); //<---- This doesn't update the state
console.log("WikiTitle: ", wikiTitleResults.wikiResults);
console.log("Items: ", items);
})
}
const handleOnHover = (result) => {
// the item hovered
console.log(result)
}
const handleOnSelect = (item) => {
// the item selected
setTextFilterState({textFilter:item.name});
console.log(item)
}
const handleOnFocus = () => {
console.log('Focused')
}
const handleOnClear = () => {
setTextFilterState({textFilter:""});
}
const formatResult = (item) => {
return (
<>
<span style={{ display: 'block', textAlign: 'left' }}>id: {item.title}</span>
</>
)
}
return (
<div >
<div className="searchbar">
<ReactSearchAutocomplete
items={items}
onSearch={handleOnSearch}
onHover={handleOnHover}
onSelect={handleOnSelect}
onFocus={handleOnFocus}
onClear={handleOnClear}
styling={{ zIndex: 4 }} // To display it on top of the search box below
autoFocus
/>
</div>
</div>
)
}
export default Search
getApiSuggesetions
const getApiSuggestions = (title) => {
//console.log("URL Being called"+ urlSingleResult);
//console.log(title);
let result = urlMultiResult
.get(`${title}`)
.then((response) => {
console.log(Object.values(response.data.query.pages))
return Object.values(response.data.query.pages);
})
.catch((error) => {
return error;
console.log(error);
});
console.log(result);
return result;
};
I fixed this by including a useEffect and a context from the parent component.
function Search() {
const {textFilterState, setTextFilterState} = useContext(TextSearchContext);
const {wikiTitleResults, setWikiTitleResults} = useContext(SearchContext);
var items = wikiTitleResults.wikiTitles;
useEffect(() => {
const fetchData = async () => {
const data = await getApiSuggestions(textFilterState.textFilter)
setWikiTitleResults({wikiTitles:data})
}
fetchData();
},
[textFilterState])
const handleOnSearch = (string, results) => {
setTextFilterState({textFilter:string});
}

useEffect only runs on hot reload

I have a parent/child component where when there is a swipe event occurring in the child the parent component should fetch a new profile. The problem is the useEffect in the child component to set up the eventListeneners currently is not running, only occasionally on hot-reload which in reality should run basically every time.
Child component
function Profile(props: any) {
const [name] = useState(`${props.profile.name.title} ${props.profile.name.first} ${props.profile.name.last}`);
const [swiped, setSwiped] = useState(0)
const backgroundImage = {
backgroundImage: `url(${props.profile.picture.large})`
};
const cardRef = useRef<HTMLDivElement>(null);
const card = cardRef.current
let startX:any = null;
function unify (e:any) { return e.changedTouches ? e.changedTouches[0] : e };
function lock (e:any) { if (card) {startX = unify(e).clientX; console.log(startX)} }
function move (e: any) {
console.log('move')
if(startX) {
let differenceX = unify(e).clientX - startX, sign = Math.sign(differenceX);
if(sign < 0 || sign > 0) {
setSwiped((swiped) => swiped +1)
props.parentCallback(swiped);
startX = null
}
}
}
// Following code block does not work
useEffect(() => {
if (card) {
console.log(card)
card.addEventListener('mousedown', lock, false);
card.addEventListener('touchstart', lock, false);
card.addEventListener('mouseup', move, false);
card.addEventListener('touchend', move, false);
}
})
return (
<div>
<h1 className="heading-1">{name}</h1>
<div ref={cardRef} className="card" style={backgroundImage}>
</div>
</div>
);
}
Parent component
function Profiles() {
const [error, setError] = useState<any>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [profiles, setProfiles] = useState<any[]>([]);
const [swiped, setSwiped] = useState(0)
useEffect(() => {
getProfiles()
}, [swiped])
const callback = useCallback((swiped) => {
setSwiped(swiped);
console.log(swiped);
}, []);
const getProfiles = () => {
fetch("https://randomuser.me/api/")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setProfiles(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}
if (error) {
return <h1 className="heading-1">Error: {error.message}</h1>;
} else if (!isLoaded) {
return <h1 className="heading-1">Loading...</h1>;
} else {
return (
<div id="board">
{profiles.map(profile => (
<Profile key={profile.id.value} profile={profile} parentCallback={callback}/>
))}
</div>
);
}
}
If you want the parent components swiped state to change, you need to pass "setSwiped" from the parent to the child compenent. You will also need to pass "swiped" to the child to use its current value to calculate the new value. I'm going to assume you declared the useState in the child component trying to set the parents state of the same name, so I'm going to remove that useState Declaration in the child altogether.
Here's an example of passing the setSwiped method and swiped value to the child:
PARENT
import React, {useState, useEffect, useCallback} from 'react';
import './Index.css';
import Profile from './Profile'
function Profiles() {
const [error, setError] = useState<any>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [profiles, setProfiles] = useState<any[]>([]);
const [swiped, setSwiped] = useState(0)
useEffect(() => {
getProfiles()
}, [swiped])
const callback = useCallback((swiped) => {
setSwiped(swiped);
console.log(swiped);
}, []);
const getProfiles = () => {
fetch("https://randomuser.me/api/")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setProfiles(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}
if (error) {
return <h1 className="heading-1">Error: {error.message}</h1>;
} else if (!isLoaded) {
return <h1 className="heading-1">Loading...</h1>;
} else {
return (
<div id="board">
{profiles.map(profile => (
<Profile key={profile.id.value} profile={profile} parentCallback={callback} setSwiped={setSwiped} swiped={swiped}/>
))}
</div>
);
}
}
export default Profiles;
CHILD
import React, {useState, useRef, useEffect } from 'react';
import './Index.css';
function Profile(props: any) {
const [name] = useState(`${props.profile.name.title} ${props.profile.name.first} ${props.profile.name.last}`);
const backgroundImage = {
backgroundImage: `url(${props.profile.picture.large})`
};
const cardRef = useRef<HTMLDivElement>(null);
const card = cardRef.current
let startX:any = null;
function unify (e:any) { return e.changedTouches ? e.changedTouches[0] : e };
function lock (e:any) { if (card) {startX = unify(e).clientX; console.log(startX)} }
function move (e: any) {
console.log('move')
if(startX) {
let differenceX = unify(e).clientX - startX, sign = Math.sign(differenceX);
if(sign < 0 || sign > 0) {
props.setSwiped((props.swiped) => props.swiped +1)
props.parentCallback(props.swiped);
startX = null
}
}
}
useEffect(() => {
if (card) {
console.log(card)
card.addEventListener('mousedown', lock, false);
card.addEventListener('touchstart', lock, false);
card.addEventListener('mouseup', move, false);
card.addEventListener('touchend', move, false);
}
})
return (
<div>
<h1 className="heading-1">{name}</h1>
<div ref={cardRef} className="card" style={backgroundImage}>
</div>
</div>
);
}
export default Profile;
I'm hoping I didn't miss anything here.
Best of luck.

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

I am creating a library in which depending on the input parameters it is shown as nodes or as a tree.
I have been days trying to solve the problem but it always gives me the same error, when I delete the redux part it loads but if it shows the error "Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:".
I have tried calling redux functions the way you are seeing or using mapStateToProps with mapDispatchToProps.
Since I saw that it was a hooks error, I changed the code from hook to class.
Can anyone tell me what I'm doing wrong?
Version Hooks (I'm using this)
import React, { useEffect } from "react";
import { compose, Dispatch } from "redux";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { updateBranchs, updateNumBranchs } from "../../redux/actions/portal";
import {
updateSelected,
updateDma,
updateHiddenBranchs,
} from "../../redux/actions/hierarchy";
import { makeSelectBranchItem, makeSelectHierarchyItem } from "./selectors";
import { generatePortalBranch } from "../../utils/portal";
import {
replaceHierarchyBranch,
findDistance,
findDuplicatedItems,
} from "../../utils/hierarhcy";
import PotalBranch from "../portalbranch";
import HierarchyBranch from "../hierarchybranch";
import { MainProps, IBranch, IHierarchyBranch } from "./interfaces";
const MainTree = ({
data,
portal,
branch,
hierarchy,
onUpdateBranchs,
onUpdateNumBranchs,
onUpdateSelected,
onUpdateDma,
onUpdateHiddenBranchs,
removeBranchWithChildren,
}: MainProps) => {
useEffect(() => {
if (data && !portal) {
onUpdateBranchs([data]);
}
}, [data, portal, onUpdateBranchs]);
const onUpdatedHiddenBranchs = (ids: Array<string>, show: boolean) => {
let hiddenBranchs: Array<string> = [...hierarchy.hiddenBranchs];
let items: Array<string> = [];
if (!show) {
items = [...hiddenBranchs, ...ids];
} else {
const copyItems: Array<string> = [...hiddenBranchs];
const duplicatedItems: Array<string> = findDuplicatedItems(
copyItems
);
items = copyItems.filter((item: string) => {
return !ids.includes(item);
});
items = [...items, ...duplicatedItems];
}
onUpdateHiddenBranchs(items);
};
const onUpdateSelectedBranch = (id: number, dma: IHierarchyBranch) => {
onUpdateDma(dma);
onUpdateSelected(id);
};
const onUpdateFavourites = (dma: IHierarchyBranch) => {
let branchs: Array<IBranch> = [...branch.branchs];
const updatedDma: IHierarchyBranch = Object.assign({}, dma, {
favourite: !dma.favourite,
});
const updated: Array<IHierarchyBranch> = replaceHierarchyBranch(
branchs,
updatedDma
);
onUpdateBranchs(updated);
onUpdateDma(dma);
};
const updateBranchs = () => {
let numBranchs: number = branch.numBranchs;
const branchToUpdate: IBranch = generatePortalBranch(numBranchs);
let updatedBranchs = [...branch.branchs];
updatedBranchs.push(branchToUpdate);
onUpdateBranchs(updatedBranchs);
onUpdateNumBranchs(numBranchs + 1);
};
const renderHierarchyBranch = (index: number, branch: IHierarchyBranch) => {
const selected: number = hierarchy.selected;
const hiddenBranchs: Array<string> = [...hierarchy.hiddenBranchs];
const level: number = findDistance(data, branch.value);
return (
<HierarchyBranch
key={index}
id={branch.id}
level={level}
label={branch.label}
alarms={branch.value}
favourite={branch.favourite}
icon={branch.icon}
sensorizable={branch.sensorizable}
children={branch.children}
branch={branch}
selectedId={selected}
hiddenBranchs={hiddenBranchs}
onUpdateSelectedBranch={onUpdateSelectedBranch}
onUpdateFavourites={onUpdateFavourites}
onUpdatedHiddenBranchs={onUpdatedHiddenBranchs}
renderHierarchyBranch={renderHierarchyBranch}
/>
);
};
const renderPortalBranch = (index: number, branchSelected: IBranch) => {
const branchs: Array<IBranch> = [...branch.branchs];
const numBranchs: number = branch.numBranchs;
return (
<PotalBranch
key={index}
id={branchSelected.id}
parentId={branchSelected.parentId}
active={branchSelected.active}
favorite={branchSelected.favorite}
level={branchSelected.level}
title={branchSelected.title}
editable={branchSelected.editable}
checkbox={branchSelected.checkbox}
checkboxEnabled={branchSelected.checkboxEnabled}
children={branchSelected.children}
branch={branchSelected}
branchs={branchs}
removeBranchWithChildren={removeBranchWithChildren}
onUpdateBranchs={onUpdateBranchs}
numBranchs={numBranchs}
onUpdateNumBranchs={onUpdateNumBranchs}
renderPortalBranch={renderPortalBranch}
/>
);
};
const renderPortal = () => {
const branchs: Array<IBranch> = [...branch.branchs];
return (
<div className="go-react-tree">
<div className="go-react-tree__create-button">
<i
className="icon-plus-squared-alt"
onClick={updateBranchs}
/>
<span>Crear nuevo</span>
</div>
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map((branch: IBranch, index: number) => {
return renderPortalBranch(index, branch);
})}
</div>
</div>
);
};
const renderHierarchy = () => {
const branchs: Array<IHierarchyBranch> = [...branch.branchs];
return (
<div className="go-react-tree">
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map(
(branch: IHierarchyBranch, index: number) => {
return renderHierarchyBranch(index, branch);
}
)}
</div>
</div>
);
};
return portal ? renderPortal() : renderHierarchy();
};
const mapStateToProps = createStructuredSelector({
branch: makeSelectBranchItem(),
hierarchy: makeSelectHierarchyItem(),
});
function mapDispatchToProps(dispatch: Dispatch) {
return {
onUpdateBranchs: (branchs: Array<IBranch>) => {
dispatch(updateBranchs(branchs));
},
onUpdateNumBranchs: (numbranchs: number) => {
dispatch(updateNumBranchs(numbranchs));
},
onUpdateSelected: (id: number) => {
dispatch(updateSelected(id));
},
onUpdateDma: (dma: any) => {
dispatch(updateDma(dma));
},
onUpdateHiddenBranchs: (dmas: any) => {
dispatch(updateHiddenBranchs(dmas));
},
dispatch,
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
export default compose(withConnect)(MainTree);
Version Class Component
import React, { Component } from "react";
import { connect } from "react-redux";
import HierarchyActions from "../../redux/actions/hierarchy";
import PortalActions from "../../redux/actions/portal";
import { generatePortalBranch } from "../../utils/portal";
import {
replaceHierarchyBranch,
findDistance,
findDuplicatedItems,
} from "../../utils/hierarhcy";
import PotalBranch from "../portalbranch";
import HierarchyBranch from "../hierarchybranch";
import { IBranch, IHierarchyBranch } from "./interfaces";
class MainLibrary extends Component<any, any> {
componentDidMount() {
const { data, portal, updateBranchs } = this.props;
if (data && !portal) {
updateBranchs([data]);
}
}
onUpdatedHiddenBranchs = (ids: Array<string>, show: boolean) => {
const { updateHiddenBranchs } = this.props;
let hiddenBranchs: Array<string> = [...this.props.hiddenBranchs];
let items: Array<string> = [];
if (!show) {
items = [...hiddenBranchs, ...ids];
} else {
const copyItems: Array<string> = [...hiddenBranchs];
const duplicatedItems: Array<string> = findDuplicatedItems(
copyItems
);
items = copyItems.filter((item: string) => {
return !ids.includes(item);
});
items = [...items, ...duplicatedItems];
}
updateHiddenBranchs(items);
};
onUpdateSelectedBranch = (id: number, dma: IHierarchyBranch) => {
const { updateDma, updateSelected } = this.props;
updateDma(dma);
updateSelected(id);
};
onUpdateFavourites = (dma: IHierarchyBranch) => {
const { updateBranchs, updateDma } = this.props;
let branchs: Array<IBranch> = [...this.props.branchs];
const updatedDma: IHierarchyBranch = Object.assign({}, dma, {
favourite: !dma.favourite,
});
const updated: Array<IHierarchyBranch> = replaceHierarchyBranch(
branchs,
updatedDma
);
updateBranchs(updated);
updateDma(dma);
};
updateBranchs = (event: any) => {
event.preventDefault();
const { updateBranchs, updateNumBranchs } = this.props;
let numBranchs: number = this.props.numBranchs;
const branchToUpdate: IBranch = generatePortalBranch(numBranchs);
let updatedBranchs = [...this.props.branchs];
updatedBranchs.push(branchToUpdate);
updateBranchs(updatedBranchs);
updateNumBranchs(numBranchs + 1);
};
renderHierarchyBranch = (index: number, branch: IHierarchyBranch) => {
const { data } = this.props;
const selected: number = this.props.selected;
const hiddenBranchs: Array<string> = [...this.props.hiddenBranchs];
const level: number = findDistance(data, branch.value);
return (
<HierarchyBranch
key={index}
id={branch.id}
level={level}
label={branch.label}
alarms={branch.value}
favourite={branch.favourite}
icon={branch.icon}
sensorizable={branch.sensorizable}
children={branch.children}
branch={branch}
selectedId={selected}
hiddenBranchs={hiddenBranchs}
onUpdateSelectedBranch={this.onUpdateSelectedBranch}
onUpdateFavourites={this.onUpdateFavourites}
onUpdatedHiddenBranchs={this.onUpdatedHiddenBranchs}
renderHierarchyBranch={this.renderHierarchyBranch}
/>
);
};
renderPortalBranch = (index: number, branchSelected: IBranch) => {
const {
updateBranchs,
updateNumBranchs,
removeBranchWithChildren,
} = this.props;
const branchs: Array<IBranch> = [...this.props.branchs];
const numBranchs: number = this.props.numBranchs;
return (
<PotalBranch
key={index}
id={branchSelected.id}
parentId={branchSelected.parentId}
active={branchSelected.active}
favorite={branchSelected.favorite}
level={branchSelected.level}
title={branchSelected.title}
editable={branchSelected.editable}
checkbox={branchSelected.checkbox}
checkboxEnabled={branchSelected.checkboxEnabled}
children={branchSelected.children}
branch={branchSelected}
branchs={branchs}
removeBranchWithChildren={removeBranchWithChildren}
onUpdateBranchs={updateBranchs}
numBranchs={numBranchs}
onUpdateNumBranchs={updateNumBranchs}
renderPortalBranch={this.renderPortalBranch}
/>
);
};
renderPortal = () => {
const branchs: Array<IBranch> = [...this.props.branchs];
return (
<div className="go-react-tree">
<div className="go-react-tree__create-button">
<i
className="icon-plus-squared-alt"
onClick={this.updateBranchs}
/>
<span>Crear nuevo</span>
</div>
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map((branch: IBranch, index: number) => {
return this.renderPortalBranch(index, branch);
})}
</div>
</div>
);
};
renderHierarchy = () => {
const branchs: Array<IHierarchyBranch> = [...this.props.branchs];
return (
<div className="go-react-tree">
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map(
(branch: IHierarchyBranch, index: number) => {
return this.renderHierarchyBranch(
index,
branch
);
}
)}
</div>
</div>
);
};
render() {
const { portal } = this.props;
return portal ? this.renderPortal() : this.renderHierarchy();
}
}
function selectStateApp(state: any) {
return {
numBranchs: state.branch.numBranchs,
branchs: state.branch.branchs,
hiddenBranchs: state.hierarchy.hiddenBranchs,
selected: state.hierarchy.selected,
selectedDma: state.hierarchy.selectedDma,
};
}
export default connect(selectStateApp, (dispath) => ({
updateSelected: (id: number) =>
HierarchyActions.updateSelected(id)(dispath),
updateDma: (dma: any) => HierarchyActions.updateDma(dma)(dispath),
updateHiddenBranchs: (dmas: Array<string>) =>
HierarchyActions.updateHiddenBranchs(dmas)(dispath),
updateBranchs: (branchs: Array<IBranch>) =>
PortalActions.updateBranchs(branchs)(dispath),
updateNumBranchs: (numBranchs: number) =>
PortalActions.updateNumBranchs(numBranchs)(dispath),
}))(MainLibrary);

Resources