State changing but not rendering - javascript

I want to render a settings menu onclick when an element is added to my list and disable all other setting menus when one is active. I also have a sample element in a different state and would like to deactivate all setting menus when it is on and have it disabled if a settings menu is on. I am storing the setting states of all elements in an array and in my update function the copy of my setting state is changing but my actual settings state isn't. I think it has to do with my useeffect and when I comment it out my settings state does change but it isn't rendered. It was working before I mapped my chart data, but I am not sure what caused it to stop working as commenting it out still does not render settings.
const [settings, setSettings] = useState([]);
const [sampSettings, setSampSettings] = useState(false);
const updateSettings = (val, index) => {
if(val){
let copySettingsFalse = [...settings]
copySettingsFalse[index]=false;
return setSettings(copySettingsFalse);
}
let copySettingsTrue = settings.fill(false);
copySettingsTrue[index] = true;
console.log(copySettingsTrue)
return setSettings(copySettingsTrue);
};
useEffect(() => {
if (stockList.length>0){
stockList.map((val,index) => {
// Chart
setLineData([
...lineData,
{
labels:trimLineDataHistory[index],
datasets: [
{
label: val.Tick,
data: trimLineDataHistory[index].reverse(),
pointRadius: 0,
fill: false,
backgroundColor: day ? "#e9dfd4" : "#141e28",
borderColor: "#52ad59",
},
],
},
]);
// Settings
setSettings([...settings, false]);
})}
return;
},[stockList]);
// Settings
// useEffect(() => {
// if (settings.includes(true)) {
// setSampSettings(false);
// }
// if (sampSettings === true) {
// setSettings(settings.fill(false));
// }
// return;
// }, [settings,sampSettings]);
if(stockList.length>1)
return(
<>
{stockList.map((val, index) => {
const { Tick, Name } = val;
return(
<div className="stockItemIcon">
<BsFillGearFill
className="gearIcon"
onClick={() => updateSettings(settings[index], index)}></BsFillGearFill>
<div className={settings[index]?
(day?"settingsContainerAlt showSettings daySettings":"settingsContainerAlt showSettings nightSettings")
:(day?"settingsContainerAlt hideSettings daySettings":"settingsContainerAlt hideSettings nightSettings")}>
</div>)}
</>)

You try to add the "Key" attribute BsFillGearFill tag and also set & update value for it. it may be any value

Related

How do I set a state to a value in localStorage?

There is a button that toggles dark and light mode, and the state of what mode the page is on is saved in localStorage. However, I cannot change the initial value of the state (dark) and I don't know why. This is done in a useEffect function but no matter what the value of dark is, it is always set to its initial value of false.
How do I set the value of the localStorage to the dark state?
function Mode() {
const [dark, setDark] = useState(false);
// localStorage.removeItem("dark");
const onClick = () => {
if (dark) {
setDark(false);
document.querySelector("body").classList.remove("dark");
} else {
setDark(true);
document.querySelector("body").classList.add("dark");
}
localStorage.setItem("dark", dark);
};
const localDark = JSON.parse(localStorage.getItem("dark"));
useEffect(() => {
if (localDark !== null) {
setDark(!JSON.parse(localStorage.getItem("dark"))); // this is what does not change the value of dark
onClick();
}
}, []);
return (
<div onClick={onClick} className="mode">
{dark ? <Light /> : <Dark />}
</div>
);
}
Directly use the value from localStorage in useState as the default. useEffect is unnecessary here.
const [dark, setDark] = useState(JSON.parse(localStorage.getItem("dark")));
document.body.classList.toggle('dark', dark);
The click event handler should set the localStorage dark value to the logical complement of the current value.
const onClick = () => {
localStorage.setItem("dark", !dark);
setDark(!dark);
};
Use a function to initialize the state from local storage. Update the storage and the body's class on init, and when dark state changes:
const getLocalDark = () => !!JSON.parse(localStorage.getItem("dark"));
function Mode() {
const [dark, setDark] = useState(getLocalDark);
const onClick = () => {
setDark(d => !d);
};
useEffect(() => {
const classList = document.querySelector("body").classList;
if (dark) classList.add("dark");
else classList.remove("dark");
localStorage.setItem("dark", dark);
}, [dark]);
return (
<div onClick={onClick} className="mode">
{dark ? <Light /> : <Dark />}
</div>
);
}
Perhaps you'd be interested in a useLocalStorage hook. Here's how that can be implemented:
export const useLocalStorage = (key, initialState) => {
const [value, setValue] = useState(() => {
// Initialize with the value in localStorage if it
// already exists there.
const data = localStorage.getItem(key);
// Otherwise, initialize using the provided initial state.
return data ? JSON.parse(data) : initialState;
});
// Each time "value" is changed using "setValue", update the
// value in localStorage to reflect these changes.
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [value]);
return [value, setValue];
};
This hook syncs the value seen in localStorage with the value stored in memory under the value variable.
The usage looks like this (almost identical to regular useState):
export const Counter = () => {
const [count, setCount] = useLocalStorage('count', 0);
return (
<div>
<p>{count}</p>
<button
onClick={() => {
setCount((prev) => prev + 1);
}}>
Increase count
</button>
</div>
);
};
However, the main caveat of this hook is that it's only really meant to be used in one component. That means, if you change the value from light to dark in one component using this hook, any other components using it won't be updated. So instead, you should look into using a similar implementation of what is demonstrated above, but using the React Context API. That way, you'll ensure your in-memory values are in sync with those stored in localStorage. Theming is one of the main uses of the Context API.
Good luck! :)

How to prevent mui datatables render when set states in onRowSelectionChange

I'm currently working on React js project with "mui-datatables": "^4.2.2".
I have a list of data divided on pages with the possibility of selecting an item through a checkbox :
My problem :
when I select an item in the second page, the component rerender and automatically back to the first page.
I think the problem is a set state inside onRowSelectionChange :
const options = {
filter: false,
onRowClick: (rowData, rowMeta) => {
console.log("--rowData --");
console.log(rowData);
console.log("--rowMeta --");
console.log(rowMeta);
},
onRowSelectionChange: (
currentRowsSelected,
allRowsSelected,
rowsSelected
) => {
let items = [];
allRowsSelected.forEach((row) => {
items.push(data[row.dataIndex]);
});
setSelectedRows(items);
},
How can i fix this problem ?
you should store page number in the state as well say for example
curPage:1
when page change you should update the curPage as well,now you can use this inside the options props you pass to mui-datatable.like this
const options = {
page:this.state.curPage,//incase its class component
onChangePage:(page)=>{
this.setState({curPage:page});//assuming its a class component
},
filter: false,
onRowClick: (rowData, rowMeta) => {
console.log("--rowData --");
console.log(rowData);
console.log("--rowMeta --");
console.log(rowMeta);
},
onRowSelectionChange: (
currentRowsSelected,
allRowsSelected,
rowsSelected
) => {
let items = [];
allRowsSelected.forEach((row) => {
items.push(data[row.dataIndex]);
});
setSelectedRows(items);
},
hope this will help

JS/React How to prevent data from being fetched if URL doesn't exist?

I'm writing an APP that fetches data from an API - based on user's actions.
I have a problem, because I have a search bar that changes the value of state. Then I use that state in the place of ${search} in this link
var baseUrl = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${search}&apikey=demo`;
using that link I then fetch data based on user's input which is stored in search/setSearch state..
However the app completely crashes quite often.. because the app starts to pull data from the API whenever state changes.. for example if user wants to check data for "MSFT" ( Microsoft company ), and starts typing M-S-F-T.. the app will crash as it's trying to fetch data for every single letter that the user enters
so it goes to:
`alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=M&apikey=demo`; // M
`alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MS&apikey=demo`; // MS
`alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSF&apikey=demo`; // MSF
`alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&apikey=demo`; // MSFT
and tries to fetch data for every letter entered in the search bar
If the link doesn't exist, the app will crash ( this is the PROBLEM! )
and give me an error saying! "Cannot convert undefined or null to object" - of couse.. the link doesnt exist so its undefined/null as there's nothing to get
Anyways, I need to find a way to handle the app in a way that at least it doesn't crash, and only fetches data if the link is correct.. I tried to use lodash.debounce ( something like setTimeout ) to wait a few seconds to allow the user to finish writing a full name of the stock.. however if the user erases the search, then the app tries to fetch data for an empty string
https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=&apikey=demo;
(after symbol= it passes nothing, there's no such link so the app crashes after failing to fetch proper data )
I thought this could potentially be fixed with conditional rendering - and I tried to render the data conditionally (if chart == undefined then ... ).. but if the link is wrong - the app crashes as always, whether its rendered conditionally or not!
Just for reference, here's the component in which I'm rendering the search bar //
const StockSearchBar = () => {
const { search, setSearch } = useContext(SearchContext); // state is stored in context API in another file
function handleButtonEvents(e) {
handleClick();
setTimeout(() => window.location.reload(), 3000);
}
const handleSearch = (e) => { // searchbar functionality
e.preventDefault();
setSearch(e.target.value.toUpperCase());
debounce(() => setSearch(e.target.value.toUpperCase()), 2000); // debounce 2000 milliseconds
// setTimeout(() => setSearch(e.target.value.toUpperCase()), 2500);
};
const handleClick = (e) => { // this is for backend - ignore it
if (setSearch !== '') {
const options =
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({search}),
};
const response = fetch(`http://localhost:3001/api/search`, options);
console.log("search entered");
console.log(response)
};
}
return (
<>
<div className="searchBar">
<label>Look for Stocks or Cryptos: </label>
// this is the searchbar
<input type='text' onChange={handleSearch} onKeyPress={(ev) => {
if (ev.key === "Enter") {
handleButtonEvents();
}}}
placeholder={search} required/>
<Button variant="success" type="submit" onClick={handleButtonEvents} >Search</Button>
</div>
<h1 className="src">{search}</h1>
</>
);
};
export default StockSearchBar;
Basically, I need to figure out a way to prevent data from being fetched if the link (based on state) isn't a proper link. Or at least that's what I think my problem is.
import { createContext, useEffect, useState } from "react";
const SearchContext = createContext();
export function SearchProvider({ children }) {
const [search, setSearch] = useState("AAPL")
return (
<SearchContext.Provider value={{ search, setSearch }}>{children}</SearchContext.Provider>
)
}
export default SearchContext;
^ This is my context api file - it's where state is stored and shared across the app
One solution I think could possibly help is switching onChange with onSubmit or onClick and then saving state to localStorage ( because onClick/onSubmit refreshes the whole page including state.. I tried e.preventDefault but for some reason it refuses to work in the browser giving me an error that "preventDefault(); is not a function"...) however, I'm still trying to figure out how to do it.. but logically it makes sense
https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&apikey=demo --> demo link to the API for public use
EDIT // UPDATE
ok, so I got the app to work a lot better with an if (searchbar is empty then dont set state) - thanks to Robert's comment..
const handleSearch = (e) => {
e.preventDefault();
if (e.target.value !== ``) {
setSearch(e.target.value.toUpperCase())
}
};
I have not added this file (component), however I think it's important to include it for obvious reasons as the error is coming from here
function LineChart(props) {
const { search, setSearch } = useContext(SearchContext);
var baseUrl = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${search}&apikey=demo`;
const [chart, setChart] = useState();
useEffect(() => { // fetching data from api
axios.get(baseUrl)
.then(res => {
setChart(res.data);
})
}, [baseUrl]);
useEffect(()=>{}, [chart]);
const stockPrices = useMemo(() => chart && Object.values(chart['Time Series (Daily)']).map(i => i['1. open']).reverse(), [chart]); // store stock prices in stockPrices
const stockDates = useMemo(() => chart && Object.keys(chart['Time Series (Daily)']).map(x => x.replace(/\d{4}-/, "")).reverse(), [chart]); // store dates in stockDates
// display stock chart + settings using chart.js
var data = {
labels: stockDates,
datasets: [{
label: `${search}`,
data: stockPrices,
backgroundColor: "rgba(255,116,89,0.05)",
borderColor: "rgba(255,105,75,1)",
borderWidth: 2,
pointBorderColor: "#2984c5",
pointBackgroundColor: "#fff",
pointHoverRadius: 5,
pointHoverBackgroundColor: 'black',
fill: true,
pointHoverBorderColor: "rgba(41, 132, 197, 1)",
pointHoverBorderWidth: 7,
pointRadius: 0,
pointHitRadius: 30,
tension: 0.3,
}]
};
var options = {
plugins: {
legend: {
display: '',
labels: {
color: 'white',
font: {
size: 30,
family: 'verdana',
},
}
}
},
maintainAspectRatio: true,
responsive: false,
radius: 3,
scales: {
y: {
ticks: {
callback: function (value) {
return "$" + value;
},
color: 'rgba(210,230,244,1)',
font: {
size: 12,
family: "Verdana" // Add your font here to change the font of your legend label
}
},
grid: {
color: 'rgba(255,255,255,0.02)',
}
},
x: {
grid: {
color: 'rgba(255,255,255,0.02)',
},
ticks: {
color: 'rgba(210,230,244,1)',
font: {
family: "Verdana" // Add your font here to change the font of your legend label
}
}
},
},
}
return (
<>
<div className='chartcont'>
<Line data={data} height={800} width={1200} options={options} /> {/* DISPLAY CHART BASED ON THE FETCHED DATA */}
</div>
<button onClick={() => setSearch("NIO")}>CLICK ME</button>
<h1 className='src'>{search}</h1> {/* ADDED THIS FOR TESTING PURPOSES */}
</>
);
}
export default LineChart;
There are two types of error that might defeat a fetch request, either the link doesn't exist, or a link exists but doesn't have data to be read. A error-catch block can be used to log the error without crashing the programme. I have used the following structure to fetch from a few thousand generated links where every now and then, one of the links is bad or the json data not present. It prevented crashes and reported silently to console.
fetch(`${url}`)
.then(response => {
if (!response.ok){throw new Error ('Bad Response');}
else { return response.json()};
})
.then(data => {
// process data here or pass to processing function;
parseData(data)
})
.catch(error => {
// if in a loop can also log which url failed;
console.log('error made: ', error);
});

react-widgets DropDownList dynamic load on demand

I would like to use the awesome react-widgets DropDownList to load records on demand from the server.
My data load all seems to be working. But when the data prop changes, the DropDownList component is not displaying items, I get a message
The filter returned no results
Even though I see the data is populated in my component in the useEffect hook logging the data.length below.
I think this may be due to the "filter" prop doing some kind of client side filtering, but enabling this is how I get an input control to enter the search term and it does fire "onSearch"
Also, if I use my own component for display with props valueComponent or listComponent it bombs I believe when the list is initially empty.
What am I doing wrong? Can I use react-widgets DropDownList to load data on demand in this manner?
//const ItemComponent = ({item}) => <span>{item.id}: {item.name}</span>;
const DropDownUi = ({data, searching, fetchData}) => {
const onSearch = (search) => {
fetchData(search);
}
// I can see the data coming back here!
useEffect(() => {
console.log(data.length);
}, [data]);
<DropDownList
data={data}
filter
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
Got it! This issue is with the filter prop that you are passing to the component. The filter cannot take a true as value otherwise that would lead to abrupt behavior like the one you are experiencing.
This usage shall fix your problem:
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
Working demo
The entire code that I had tried at codesandbox:
Warning: You might have to handle the clearing of the values when the input is empty.
I thought that the logic for this was irrelevant to the problem statement. If you want, I can update that as well.
Also, I added a fakeAPI when searchTerm changes that resolves a mocked data in 2 seconds(fake timeout to see loading state).
import * as React from "react";
import "./styles.css";
import { DropdownList } from "react-widgets";
import "react-widgets/dist/css/react-widgets.css";
// Coutesy: https://usehooks.com/useDebounce
import useDebounce from "./useDebounce";
interface IData {
id: string;
name: string;
}
const fakeAPI = () =>
new Promise<IData[]>((resolve) => {
window.setTimeout(() => {
resolve([
{
name: "NA",
id: "user210757"
},
{
name: "Yash",
id: "id-1"
}
]);
}, 2000);
});
export default function App() {
const [state, ss] = React.useState<{
searching: boolean;
data: IData[];
searchTerm: string;
}>({
data: [],
searching: false,
searchTerm: ""
});
const debounceSearchTerm = useDebounce(state.searchTerm, 1200);
const setState = (obj: Record<string, any>) =>
ss((prevState) => ({ ...prevState, ...obj }));
const getData = () => {
console.log("getting data...");
setState({ searching: true });
fakeAPI().then((response) => {
console.log("response: ", response);
setState({ searching: false, data: response });
});
};
React.useEffect(() => {
if (debounceSearchTerm) {
getData();
}
}, [debounceSearchTerm]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
</div>
);
}
Let me know if you have more queries on this 😇
So it i think that list should be loaded a then you can filtering your loaded data.In your example on the beginning you don't have value so list is empty, you tape in some text and then value of list re render but it look like is not filtered.....
However I look through code base, and it's look like is not ready until you don't set manually open prop drop down list component. In getDerivedStateFromprops, next data list is read only if in next props is open set. to true
From DropDwonList
static getDerivedStateFromProps(nextProps, prevState) {
let {
open,
value,
data,
messages,
searchTerm,
filter,
minLength,
caseSensitive,
} = nextProps
const { focusedItem } = prevState
const accessors = getAccessors(nextProps)
const valueChanged = value !== prevState.lastValue
let initialIdx = valueChanged && accessors.indexOf(data, value)
//-->> --- -- --- -- -- -- -- - - - - - - - - - --- - - --------
//-->>
if (open)
data = Filter.filter(data, {
filter,
searchTerm,
minLength,
caseSensitive,
textField: accessors.text,
})
const list = reduceToListState(data, prevState.list, { nextProps })
const selectedItem = data[initialIdx]
const nextFocusedItem = ~data.indexOf(focusedItem) ? focusedItem : data[0]
return {
data,
list,
accessors,
lastValue: value,
messages: getMessages(messages),
selectedItem: valueChanged
? list.nextEnabled(selectedItem)
: prevState.selectedItem,
focusedItem:
(valueChanged || focusedItem === undefined)
? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem)
: nextFocusedItem,
}
}
I would try:
<DropDownList
data={data}
filter
open
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
if it will be works, then you just have to
manage your open state by yourself.

How to solve Cannot update during an existing state issue - react?

I have a function I calling it in render() method
and it's setState a Flag from the state.
So I got this error
Cannot update during an existing state transition (such as within
render).
I read about this error, and what I understand it's because I setState in render method and this is the wrong way.
So I'm forced to do it if u have any idea to handle this tell me.
The main idea about this function
I have an array of an object "Name as Tools" so in every object I have "id, name, count, price"
so that will render a three input in UI like this
and I have a boolean flag in-state "isEmpty" that checks every input in array before sending this data to the database.
Code
State = {
toolsUsed: [
{
id: 0,
name: '',
price: '',
count: '',
},
],
// Checker
isEmpty: false,
}
renderToolsUsed = () => {
const {toolsUsed} = this.state;
const tools = toolsUsed.map((item, i) => {
const {count, price, name, id} = item;
this.setState({
isEmpty: ['name', 'price', 'count'].every(key => item[key].length > 0),
});
return (
<View key={i} style={styles.tools}>
.... Inputs here ...
</View>
);
});
return tools;
};
JSX
render() {
return (
<View>
{this.renderToolsUsed()}
</View>
);
}
You can't update the state like this. It is like infinite loop. setState will trigger render, then render will trigger another setState, then you keep repeat the circle.
I don't know why you need isEmpty when you already have toolsUsed which you can use it to check if all input are empty.
Lets say if you insist to have isEmpty, then you can set it inside input change event.
The code is not tesed. I wrote the code directly from browser. But you can get the idea before the code.
renderToolsUsed = () => {
const { toolsUsed } = this.state;
const tools = toolsUsed.map((item, i) => {
return (
<View key={i} style={styles.tools}>
<TextInput value={item.name} onChangeText={(text) => {
this.setState({
toolsUsed: [
...toolsUsed.slice(0, i - 1),
{...item, name: text },
...toolsUSed.slice(i)
]
}, this.updateEmptyState)
}>
// other input here
</View>
);
});
// ...
};
updateEmptyState = () => {
this.setState({
isEmpty: this.state.toolsUsed.every(x => x.name === '' && x.price === '' && x.count === '')
})
}
The state is not designed to store all the data you have in the app
For what you need isEmpty inside the state?
To do this, use a global variable
Or check it out when you want it out of the render

Categories

Resources