There is an array in the parent class(TodolistActivity), and the child class(TaskCallFunc) displays the elements in the array. When I use a.list= []; to clear the array, there is no clearing on the page
but a.list.length = 0 is ok. why?
Here is my code:
interface IListData {
list: IActivityData[]
}
interface IActivityData {
id: number,
content: string,
finish: boolean
}
export function TodolistActivity(activty: IListData) {
const [acty, setActivity] = useState(activty);
const [content, setContent] = useState('');
const input_ref = React.createRef<HTMLInputElement>();
const [selectCount, setSelect] = useState(0);
const handleAdd = () => {
if (input_ref.current) {
if (input_ref.current.value === '') {
alert("输入内容 不能为空!");
return;
}
let id = acty.list.length;
acty.list.unshift({ id: id, content: content, finish: false })
let a = { ...acty }
setActivity(a);
input_ref.current.value = "";
}
}
const calcuateSelect = () => {
let a = acty.list.filter((v, i) => { return v.finish === true })
setSelect(a.length);
}
const handleChange = (input: React.ChangeEvent<HTMLInputElement>) => {
setContent(input.target.value);
}
const clearTask = () => {
let a = { ...acty};
a.list= [];
//a.list.length = 0;
setActivity(a);
}
return (
<div>
<input type='text' onChange={handleChange} ref={input_ref} />
<button className="add task" onClick={handleAdd}>add task</button>
<button className="clear task" onClick={clearTask}>clear task</button>
{console.log(acty)}
<TaskCallFunc data={acty} action={() => { calcuateSelect() }} />
<br />
<label htmlFor="">select{selectCount}/{acty.list.length}</label>
</div>
);
}
interface ItaskCell {
data: IListData,
action: () => void
}
function TaskCallFunc(taskData: ItaskCell) {
const [data, setData] = useState(taskData);
const HandleSlecet = (x: number) => {
for (let index = 0; index < data.data.list.length; index++) {
if (data.data.list[index].id === x) {
let newState = { ...data };
newState.data.list[index].finish = !data.data.list[index].finish;
setData(newState);
data.action();
}
}
}
const handleMap = () => {
return data.data.list.map((v, i) => { return <li key={v.id}>{v.id}: {v.content} <input type="checkbox" checked={v.finish} onChange={() => { HandleSlecet(v.id) }} /> </li> });
}
return (
<ul>{handleMap()}</ul>
);
}
If you know the answer, please let me know thank you
TaskCallFunc component doesn't "listen" for changes on the taskData prop to update the local copy stored in state. Use an useEffect hook with a dependency on taskData prop to update the state when it changes.
function TaskCallFunc(taskData: ItaskCell) {
const [data, setData] = useState(taskData);
useEffect(() => {
setData(taskData);
}, [taskData]);
...
You can clear the array easely by doing setActivity({ list: [] }) consider also to add useEffect as Drew says to listen for changes
Related
To provide some context, I am working on building a multi-page form where each page is its own component that should re-render itself in response to (valid/invalid) user input and prevent the form to move to the next page for invalid inputs on current page. Since there is no two-way binding in React, I leveraged a shared context provider between parent-child and used a custom hook to access validation logic. In order to perform the validation checks from the parent I attach a reference to a function which I call inside the child by sending it via props. Each time the parent has to move to next page it simply calls the function that is pointed to by this reference.
Shared context:
class Role {
id: string = '';
description: string = '';
tenure: string = '';
}
class Person {
name: string = '';
age: string = '';
roles: Role[] = [];
}
export const SharedContextProvider: React.FC = ({children}) => {
const [person, setPerson] = useState<Person>(new Person('', ''));
const validateName = () => {
let isValid = person.name !== '';
return {isValid: () => isValid, error: () => isValid ? '' : 'Invalid Name'};
};
...; // other validation methods here skipped
const isValidSection = (errorSetters: Map<FIELDS, (error: string) => void>, validatorHelper: Map<FIELDS, Helper>) => {
let isValidSection = true;
errorSetters.forEach((setError, field) => {
let validation = validatorHelper.get(field);
if (!validation) {
throw new Error('validation not defined!');
}
let isValidField = validation.isValid(); // check if field is valid
isValidSection = isValidSection && isValidField;
let error = validation?.error() || '';
console.log('Validating', field, 'isValid:', isValidField, 'error:', error);
let doSth = errorSetters.get(field)!!;
doSth(error); // debugging
});
return isValidSection;
}
const personalDetailsValidator = (errorSetters: Map<FIELDS, (error: string) => void>) => {
const validatorHelper = new Map<FIELDS, Helper>(
[
[FIELDS.NAME, validateName()],
[FIELDS.AGE, validateAge()],
]
);
return {isValidSection: () => isValidSection(errorSetters, validatorHelper)};
};
const professionalDetailsValidator = (role: Role, errorSetters: Map<FIELDS, (error: string) => void>) => {
const validatorHelper = new Map<FIELDS, Helper>(
[
[FIELDS.DESCRIPTION, validateRoleDescription(role)],
[FIELDS.TENURE, validateTenure(role)],
]
);
return {isValidSection: () => isValidSection(errorSetters, validatorHelper)};
};
const data: SharedContextValue = {
person,
personalDetailsValidator,
professionalDetailsValidator,
setPerson
};
return <SharedContext.Provider value={data}>{children}</SharedContext.Provider>
}
const useSharedContext = () => {
const context = useContext(SharedContext);
if (context === undefined) {
throw new Error('Cannot be undefined');
}
return context;
}
Parent
const App = () => {
const [
activeStepIndex, // controls page on the form
setActiveStepIndex
] = React.useState(0);
let isCurrentSectionValid: () => boolean; // reference to function
const validationHandler = (validateSection: () => boolean) => {
isCurrentSectionValid = validateSection;
}
return (
<Wizard
i18nStrings={{...}}
onNavigate={({detail}) => {
let isValidSection = isCurrentSectionValid();
console.log('[isValidSection]', isValidSection);
if (isValidSection) {
setActiveStepIndex(detail.requestedStepIndex)
}
}
}
activeStepIndex={activeStepIndex}
steps={[
{
content: <PersonalDetails validationHandler={validationHandler}/>
},
{
title: "Completed",
content: <Final validationHandler={validationHandler}/>,
isOptional: true
},
]}
/>
);
}
Child1
const PersonalDetails: React.FC<PersonalProps> = ({validationHandler}) => {
const {person, personalDetailsValidator, setPerson} = useSharedContext();
const [name, setName] = useState(person.name || '');
const [age, setAge] = useState(person.age || '');
const [nameErrorText, setNameErrorText] = useState<string>('');
const [ageErrorText, setAgeErrorText] = useState<string>('');
useEffect(() => {
setPerson(new Person(name, age));
}, [name, age]);
const validator = personalDetailsValidator(new Map<FIELDS, (error: string) => void>([
[FIELDS.NAME, setNameErrorText],
[FIELDS.AGE, setAgeErrorText],
]));
// store validation state for each child
const rolesValidators: Map<string, () => boolean> = new Map<string, () => boolean>();
const roleValidationHandler = ((validateSection: () => boolean, id: string) => {
rolesValidators.set(id, validateSection);
});
validationHandler(() => {
let isValidSection = validator.isValidSection();
let areValidRoles = Array.from(rolesValidators.values()).map(item => item())
.reduce((i, j) => i && j, true);
console.log('isValidSection', isValidSection, 'areValidRoles', areValidRoles);
return isValidSection && areValidRoles;
});
return (
<Container
header={
<Header variant="h2">
Personal Info
</Header>
}
>
<SpaceBetween direction="vertical" size="l">
<FormField label="Name" errorText={nameErrorText}>
<Input value={name} placeholder={'Enter Name'} onChange={({detail}) => {
setName(detail.value);
}}/>
</FormField>
<FormField label="Age" errorText={ageErrorText}>
<Input value={age} placeholder={'Enter Age'}
onChange={({detail}) => {
setAge(detail.value);
}}
/>
</FormField>
{person.roles.map((role, index) =>
<ProfessionalDetails key={role.id} validationHandler={roleValidationHandler} role={role}/>)}
</SpaceBetween>
</Container>
);
}
Child2:
const ProfessionalDetails: React.FC<ProfessionalProps> = ({validationHandler, role}) => {
const [description, setDescription] = useState(role.description || '');
const [tenure, setTenure] = useState(role.tenure || '');
const [descriptionErrorText, setDescriptionErrorText] = useState<string>();
const [tenureErrorText, setTenureErrorText] = useState<string>();
const {professionalDetailsValidator} = useSharedContext();
const validator = professionalDetailsValidator(role, new Map<FIELDS, (error: string) => void>([
[FIELDS.DESCRIPTION, setDescriptionErrorText],
[FIELDS.TENURE, setTenureErrorText],
]));
validationHandler(() => {
return validator.isValidSection();
}, role.id);
useEffect(() => {
role.description = description;
role.tenure = tenure;
console.log('Updating role...', JSON.stringify(role));
}, [role]);
return (
<Container
header={
<Header variant="h2">
Professional Info
</Header>
}
>
<SpaceBetween direction="vertical" size="l">
<FormField label="Description" errorText={descriptionErrorText}>
<Input value={description} placeholder={'Enter Occupation'}
onChange={event => setDescription(event.detail.value)}/>
</FormField>
<FormField label="Tenure" errorText={tenureErrorText}>
<Input value={tenure} placeholder={'Enter Hobby'}
onChange={event => setTenure(event.detail.value)}/>
</FormField>
</SpaceBetween>
</Container>
);
}
Child1,Child2 is validated as expected in isolation. But for list of child2 components if previous states capture an error (for empty input) then the state is not reset when the error is rectified and the errors are retained preventing moving to the next page.
I had two questions:
Is this a sustainable approach to validate forms with deep nested components?
What are good patterns for parent-child communication in these situations?
Note: I cannot use Redux due to my company constraints. It has to be done via React Context/Hooks.
I have these codes trying to implement a crypto return calculator that takes in a coin ID, a buy and sell date and the coin amount.
My issue is that the API is being fetched after every input and by the time I reach the sell date, my API limit has been reached.
Is there a way to prevent this from happening?
Here are the codes below.
export default function App() {
const [trade, setTrade] = useState({
sellData: {},
buyData: {},
gains: 0
});
const coinList = [
{ id: 0, name: "bitcoin" },
{ id: 1, name: "ethereum" },
{ id: 2, name: "tezos" },
{ id: 3, name: "cardano" }
];
const [buyDate, setBuyDate] = useState("");
const [sellDate, setSellDate] = useState("");
const [volume, setVolume] = useState(0);
const [coin, setCoin] = useState("");
const coingeckoUrl = (coin, date) => {
return `https://api.coingecko.com/api/v3/coins/${coin}/history?date=${date}&localization=false`;
};
const calcGains = () => {
setTrade({
...trade,
gains:
(trade.sellData.market_data?.current_price.usd -
trade.buyData.market_data?.current_price.usd) *
volume
});
};
const coingeckoFetch = async (buy, coin, date) => {
fetch(coingeckoUrl(coin, date)).then((response) =>
response.json().then((jsonData) => {
if (buy) {
setTrade({ ...trade, buyData: jsonData });
} else {
setTrade({ ...trade, sellData: jsonData });
}
})
);
};
const handleBuyChange = (e) => {
let val = e.target.value;
setBuyDate(val);
coingeckoFetch(true, coin, val);
};
const handleSellChange = (e) => {
let val = e.target.value;
setSellDate(val);
coingeckoFetch(false, coin, val);
};
const handleCoinChange = (e) => {
let val = e.target.value;
setCoin(val);
coingeckoFetch(null, coin, val);
};
return (
<div className="App">
<select defaultValue={coin} onChange={(val) => handleCoinChange(val)}>
{coinList.map((item) => (
<option key={item.id}>{item.name}</option>
))}
</select>
<input
placeholder="Insert Buy Date"
defaultValue={buyDate}
onChange={(val) => handleBuyChange(val)}
/>
<h3> {trade.buyData.market_data?.current_price.usd} USD</h3>
<input
placeholder="Insert Sell Date"
defaultValue={sellDate}
onChange={(val) => handleSellChange(val)}
/>
<h3> {trade.sellData.market_data?.current_price.usd} USD</h3>
<input
placeholder="Insert Amount of Tokens"
value={volume}
onChange={(e) => setVolume(e.target.value)}
/>
<h3>{volume}</h3>
<button onClick={calcGains}> Calculate </button>
<h3>{trade.gains} USD</h3>
</div>
);
}
Thank you for your help.
Summary
if you are just don't want to request too many times.
Maybe you can add a state like
const [fetchNow, setFetchNow] = useState(false);
And in your handleChange() functions, do not carry out the coingeckoFetch(). But execute the setFetchNow()
like this
const handleBuyChange = (e) => {
let val = e.target.value;
setBuyDate(val);
// or your buy condition
if (val) {
setFetchNow(true)
}
};
const handleSellChange = (e) => {
let val = e.target.value;
setSellDate(val);
// or your sell condition
if (val) {
setFetchNow(true)
}
};
const handleCoinChange = (e) => {
let val = e.target.value;
setCoin(val);
// or your coin condition
if (val) {
setFetchNow(true)
}
};
Then add a useEffect(), and use a conditional if statement to determine that whether the coingeckoFetch() be carry out or not
useEffect(()=>{
if (fetchNow) {
coingeckoFetch(true, coin, val);
}
}, [fetchNow]);
Other
Or just simply add conditional if statement in your coingeckoFetch()
like
const coingeckoFetch = async (buy, coin, date) => {
// conditional if statement
if (buy !== "MyCondition") {
return;
}
else if (coin !== "MyCondition") {
return;
}
else if (date !== "MyCondition") {
return;
}
fetch(coingeckoUrl(coin, date)).then((response) =>
response.json().then((jsonData) => {
if (buy) {
setTrade({ ...trade, buyData: jsonData });
} else {
setTrade({ ...trade, sellData: jsonData });
}
})
);
};
Like with my solution, I have to type "Back-End Developer" to get filter results. Can I just show results while typing "back" or "backend"? Even if I don't type the "-" filter doesn't work.
I guess I have to use some and toLowerCase but I don't know where to use it.
const Positions = ({ positions }: DataProps) => {
const [selectLocation, setSelectLocation] = useState<any>('');
const [selectJobType, setSelectJobType] = useState<any>('');
const [filtered, setFiltered] = useState<any[]>([]);
const [searchTerm, setSearchTerm] = useState<any>('');
useEffect(() => {
if (positions.length > 0) {
let newList = [...positions];
if (searchTerm) {
newList = newList.filter((i) => i.position === searchTerm);
}
if (selectJobType) {
newList = newList.filter((i) => i.position === selectJobType);
}
if (selectLocation) {
newList = newList.filter((i) => i.location === selectLocation);
}
setFiltered(newList);
}
}, [positions, searchTerm, selectJobType, selectLocation]);
return (
<>
<div>
<input
type='search'
placeholder='Search'
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Select
defaultValue={selectLocation}
onChange={setSelectLocation}
options={locations}
instanceId={'1'}
placeholder='Location'
/>
</div>
<Select
defaultValue={selectJobType}
onChange={setSelectJobType}
options={jobs}
placeholder='Job Type'
instanceId={'2'}
/>
{positions?.map((position: any) => (
<SinglePosition
category={position.category}
type={position.type}
location={position.location}
position={position.position}
key={position._id}
/>
))}
</>
);
};
Do not need to use the === operator just use the includes method
useEffect(() => {
if(positions.length > 0) {
let newList = [...positions];
if(searchTerm) {
newList = newList.filter(i => i.position.toLowerCase().includes(searchTerm.toLowerCase()));
}
if(selectJobType) {
newList = newList.filter(i => i.position.toLowerCase().includes(selectJobType.toLowerCase()));
}
if(selectLocation) {
newList = newList.filter(i => i.location.toLowerCase().includes(selectLocation.toLowerCase()));
}
setFiltered(newList);
}
}, [positions, searchTerm, selectJobType, selectLocation]);
i am woking on weather api and storing perticular data in an array arr but value is not available in arr. also state arrdata is null too.
i tried to not use state but still not getting data in arr . it show reading undefined value.
export default function App() {
const [cityName, setCityName] = useState("delhi");
const [arrData, setArrData] = useState(null);
const getWeatherInfo = async () => {
const url = "https://api.openweathermap.org/data/2.5/forecast";
const api = "4beffc863037e89f0f181d893d1cf79b";
fetch(`${url}?q=${cityName}&units=metric&appid=${api}`)
.then((res) => res.json())
.then((getData) => {
if(getData.list[4].main !== null){
const arr = [];
for (let i = 0; i <= 40; i++) {
if (i % 8 === 0) {
arr.push({
temprature: getData.list[i].main.temp,
Min_temp: getData.list[i].main.temp_min,
Max_temp: getData.list[i].main.temp_max,
date: getData.list[i].dt_txt,
mood: getData.list[i].weather[0].main,
weathermoodIcon: getData.list[i].weather[0].icon,
Humidity: getData.list[i].main.humidity,
});
}}
setArrData(arr);
}});
};
useEffect(() => {
getWeatherInfo()
}, []);
console.log(arrData)
const onInputChange = (e) => {
setCityName(e.target.value);
};
const onSubmitCity = () => {
getWeatherInfo();
};
return (
<>
<Input onChangeValue={onInputChange} onSubmit={onSubmitCity} />
</>
);
}
This seems to be working. Please do not forget to use optional chaining
import {useState, useEffect } from 'react';
export default function App() {
const [cityName, setCityName] = useState("delhi");
const [arrData, setArrData] = useState(null);
const getWeatherInfo = async () => {
const url = "https://api.openweathermap.org/data/2.5/forecast";
const api = "4beffc863037e89f0f181d893d1cf79b";
fetch(`${url}?q=${cityName}&units=metric&appid=${api}`)
.then((res) => res.json())
.then((getData) => {
if(getData.list[40]?.main !== null){
const arr = [];
console.log(getData.list)
for (let i = 0; i <= 4; i++) {
if (i % 8 === 0) {
arr.push({
temprature: getData.list[i]?.main.temp,
Min_temp: getData.list[i]?.main.temp_min,
Max_temp: getData.list[i]?.main.temp_max,
date: getData.list[i]?.dt_txt,
mood: getData.list[i]?.weather[0].main,
weathermoodIcon: getData.list[i]?.weather[0].icon,
Humidity: getData.list[i]?.main.humidity,
});
}}
setArrData(arr);
}});
};
useEffect(() => {
getWeatherInfo();
}, []);
console.log(arrData)
const onInputChange = (e) => {
setCityName(e.target.value);
};
const onSubmitCity = () => {
getWeatherInfo();
};
return (
<>
<input onChange={onInputChange} onSubmit={onSubmitCity} />
<h1> {JSON.stringify(arrData)} </h1>
<button onClick = {onSubmitCity}> Submit </button>
</>
);
}
I'm trying to make an autocomplete search for Alpha Vantage API, but the array which should contain the matches (suggestion) always returns empty when I type in the input field and I don't know what could be the reason, I'm stuck for a while on this and would appreciate if someone could help me with this.
The related code here is mostly in useEffect and the inputHandler:
const Search = () => {
useEffect(() => {
const getSymbols = async () => {
const searchURL = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
const res = await axios.get(searchURL);
if(res) {
setSecurity(res.data.bestMatches);
if(security !== undefined && security.length > 0) {
let symbols = security.map(sec => sec['1. symbol'])
setAllSymbol(symbols);
}
}
}
getSymbols();
}, [])
console.log(allSymbol);
const inputHandler = (e) => {
setTextInput(e.target.value);
let matches = [];
if(textInput.length > 0) {
matches = allSymbol.filter(sym => {
const regex = new RegExp(`${textInput}`, "gi");
return sym.match(regex);
})
setSuggestion(matches);
}
console.log(suggestion);
setTextInput(e.target.value);
}
const showData = async (e) => {
e.preventDefault();
const searchURL = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
const monthlyURL = `https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
try {
const res = await axios.get(searchURL);
const data = await axios.get(monthlyURL);
if(res) {
setTickers(res.data.bestMatches[0]);
setSymbol(res.data.bestMatches[0]['1. symbol']);
setSecurity(res.data.bestMatches);
if(data) {
const monthlyTimeSeries = Object.values(data.data['Monthly Time Series']);
const result = [monthlyTimeSeries[1]];
const resultValues = Object.keys(result[0]).map(key => {
return Math.floor(result[0][key]);
})
setPrices(resultValues);
}
}
} catch(err) {
console.log(err)
}
setDailyPrices([]);
setWeeklyPrices([]);
setIntraPrices([]);
}
return (
<StyledSearch>
<div className="wrapper">
<h1>Security Price Monitor App </h1>
<form onSubmit={showData} className="search-form">
<input type="text" value={textInput} onChange={inputHandler} placeholder='Enter Stock Symbol (GOOG, MSFT)'/>
<button type="submit">Search</button>
</form>
</div>
{prices.length > 0 && (
<>
<Table prices={prices} symbol={symbol}/>
<TimeFrames symbol={symbol} textInput={textInput} weeklyPrices={weeklyPrices} setWeeklyPrices={setWeeklyPrices} dailyPrices={dailyPrices} setDailyPrices={setDailyPrices} intraPrices={intraPrices} setIntraPrices={setIntraPrices} />
</>
)}
</StyledSearch>
)
}