I have a svelte store that has the following code
import { writable } from "svelte/store";
import { onMount, onDestroy } from "svelte";
export const modalStore = () => {
const { subscribe, update } = writable({
showModal: false,
});
onMount(() => {
window.addEventListener("keydown", handleKeyDown);
});
onDestroy(() => {
window.removeEventListener("keydown", handleKeyDown);
});
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
update(stateObj => ({...stateObj, showModal: false}));
}
}
return {
subscribe,
openModal: () => update(stateObj => ({ ...stateObj, modal: true })),
closeModal: () => update(stateObj => ({ ...stateObj, modal: false })),
handleKeyDown,
}
}
Edit
I have accessed the store by the following code
let modalState = modalStore();
Then checked the state by $modalState and the accessed the function by modalStore.openModal();
It throws 500 error with - window is not defined
How can I solve it?
The problem is that onDestroy gets executed on the server. So instead of using onDestroy the function returned from onMount should be used.
Docs:
Out of onMount, beforeUpdate, afterUpdate and onDestroy, this is the only one that runs inside a server-side component.
export const modalStore = () => {
const { subscribe, update } = writable({
showModal: false,
});
onMount(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
update((storeObj) => {
storeObj.showModal = false;
return storeObj;
});
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
});
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
update(stateObj => ({...stateObj, showModal: false}));
}
}
return {
subscribe,
openModal: () => update(stateObj => ({ ...stateObj, showModal: true })),
closeModal: () => update(stateObj => ({ ...stateObj, showModal: false })),
handleKeyDown,
}
}
I dont know how. But its working now without error
Related
I have a rather simple requirement which is turning out to be quite complex for me. I'm developing a basic todo app with following UI:
Design
Now I need to update the array of object such that only the text of specific item should be updated. Here is my attempt but it just adds a new component on every key press:
import React, {useState} from "react";
const DynamicInput = () => {
const [todos, setTodos] = useState([])
const onAddClick = () => {
setTodos(prevState => {
return [...prevState, {id: prevState.length + 1, text: "", up: "↑", down: "↓", del: "x"}]
})
}
const onValueUpdate = (id) => (event) => {
let tempObject = todos[id]
setTodos(prevState => {
return [...prevState, {
id: id,
text: event.target.value,
up: "Up",
down: "Down",
del: "x"
}];
})
}
const onUpArrow = (event) => {
console.log("On up")
}
const onDownArrow = (event) => {
console.log("On down")
}
const onDeleteArrow = (event) => {
console.log("On delete")
}
return (
<>
<button onClick={onAddClick}>+</button>
{todos.map(todo => {
return(
<div key={todo.id}>
<input onChange={onValueUpdate(todo.id)} value={todo.text}></input>
<button onClick={onUpArrow}>{todo.up}</button>
<button onClick={onDownArrow}>{todo.down}</button>
<button onClick={onDeleteArrow}>{todo.del}</button>
</div>)
})}
</>
);
};
export default DynamicInput;
To simply solve your problem you can change your onValueUpdate() method to this :
const onValueUpdate = (id) => (event) => {
setTodos(prevState => {
let data = [...prevState];
let indexOfTodo = data.findIndex(todo => todo.id === id);
data[indexOfTodo] = {
...data[indexOfTodo],
text: event.target.value,
};
return data;
});
};
I am adding the tradingview charting library into my project and am having troubles getting the chart to re-render when I change the selected symbol.
When the chart loads initially it was calling a componentDidMount to submit parameters to their chart component which returns the chart. This is the charting component and I have a list of securities beside it that update redux state for symbol when clicked.
what I want to do is force the chart to update when the state changes so the correct symbol is displayed.
It is the same issue mentioned in this question, but I'm using hooks instead of class based components and when I try to use useEffect as componentDidUpdate I am getting symbol undefined.
Update:: in other question they said to use something like this in componentDidUpdate
this.tvWidget.chart().setSymbol('BINANCE:' + this.props.selectedSymbol.name)
but I cannot figure out how to do something similar with hooks
charting.js
export function TVChartContainer(props) {
const [symbol, setSymbol] = useState(props.symbol);
const tvWidget = null;
useEffect(() => {
setSymbol(props.symbol)
}, [props.symbol])
const componentDidMount = () => {
// setSymbol(props.symbol)
const widgetOptions = {
symbol: symbol,
//symbol: 'BTC/USDT',
//symbol: 'BTC/USD', //getUrlVars()["symbol"],
datafeed: Datafeed,
container_id: 'tv_chart_container',
library_path: '/charting_library/',
locale: getLanguageFromURL() || 'en',
disabled_features: ['use_localstorage_for_settings'],
enabled_features: ['study_templates'],
charts_storage_url: props.chartsStorageUrl,
charts_storage_api_version: props.chartsStorageApiVersion,
fullscreen: false,
autosize: true,
width: '100%',
timezone: 'America/New_York',
client_id: 'Hubcap',
user_id: 'public_user_id',
auto_save_delay: 10,
theme: 'Light',
loading_screen: { backgroundColor: '#222222', foregroundColor: '#229712' },
custom_indicators_getter: indicators,
};
const tvWidget = new widget(widgetOptions);
// tvWidget = tvWidget;
const thisComponent = props;
tvWidget.onChartReady(() => {
tvWidget.headerReady().then(() => {
const button = tvWidget.createButton();
button.setAttribute('title', 'Click to show a notification popup');
button.classList.add('apply-common-tooltip');
button.addEventListener('click', () =>
tvWidget.showNoticeDialog({
title: 'Notification',
body: 'TradingView Charting Library API works correctly',
callback: () => {
console.log('Noticed!');
},
})
);
button.innerHTML = '';
// thisComponent.getPattern(); //might need to uncomment later
tvWidget
.chart()
.onIntervalChanged()
.subscribe(null, function (interval, obj) {
console.log('On interval change');
thisComponent.getPattern();
});
tvWidget
.chart()
.onSymbolChanged()
.subscribe(null, function (symbolData) {
console.log('Symbol change ' + symbolData);
// thisComponent.getPattern();
});
// tvWidget.chart().createStudy('Strange Indicator', false, true);
// tvWidget.chart().createStudy('ESS Indicator', false, true);
// tvWidget.chart().createStudy('ESL Indicator', false, true);
// tvWidget.chart().createStudy('EPS Indicator', false, true);
// tvWidget.chart().createStudy('EPL Indicator', false, true);
// tvWidget.chart().createStudy('ETS Indicator', false, true);
// tvWidget.chart().createStudy('ETL Indicator', false, true);
});
});
};
const componentWillUnmount = () => {
if (tvWidget !== null) {
tvWidget.remove();
tvWidget = null;
}
};
// useEffect(() => {
// componentDidMount();
// // getPattern();
// // drawPattern();
// // // removeAllShape();
// return () => {
// componentWillUnmount();
// }
// }, [symbol])
useEffect(() => {
setSymbol(props.symbol)
componentDidMount();
// getPattern();
// drawPattern();
// // removeAllShape();
return () => {
componentWillUnmount();
}
}, []);
return <div id="tv_chart_container" className={'TVChartContainer'} />;
main page componenet
const TestPage = ({selected}) => {
const [symbol, setSymbol] = useState('AAPL');
useEffect(() => {
setSymbol(selected)
}, [selected])
return (
<div>
<TVChartContainer symbol={symbol} />
</div>
);
}
const mapStateToProps = (state) => {
return {
selected: state.Watchlist.stock.selected,
}
}
export default connect(mapStateToProps)(TestPage)
watchlist
const Security = ({index, name, stocks, selected}) => {
const dispatch = useDispatch();
const [taskName, setTaskName] =useState(name)
const [prevState, setPrevState] = useState(stocks)
const removeTask = (e) => {
e.stopPropagation()
setPrevState(stocks)
dispatch(removeStock(index))
}
const selectAStock = () => {
dispatch(stockSelected(name))
}
useEffect(() => {
setPrevState(stocks)
}, [])
useEffect(() => {
if(prevState !== stocks) dispatch(updateWatchlist(stocks, selected))
}, [stocks])
return (
<Row className="list-group-item">
<div className="item-titles" onClick={() => selectAStock()}>
{name}
</div>
<button onClick={(e) => removeTask(e)} className="remove-item">
<i className="glyphicon glyphicon-remove"></i>
</button>
</Row>
);
}
const mapStateToProps = (state) => {
return {
stocks: state.Watchlist.stock.watchlist,
}
}
export default connect(mapStateToProps, {removeStock, updateWatchlist, stockSelected})(Security);
this.tvWidget?.setSymbol("BINANCE", "5" as ResolutionString, () => null)
The setSymbol accept 3 parameters.
(symbol: string, interval: ResolutionString, callback: EmptyCallback): void
Symbol: which is a string
Interval: which is of type ResolutionString. ("5" as ResolutionString) use the 'as' to prevent error)
callback: just an empty callback
on componentDidUpdate() you can update the tradingView Widget with the following parameters.
I created a stopwatch using react. My stopwatch starts from 0 and stops at the press of the space button with componenDidMount and componentWillMount. My issue is, I can't seem to figure out how to create some sort of list with the numbers the stopwatch returns. I've created:
times = () => {
this.setState(previousState => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
and then in render() to print it.
<h1>{this.times}</h1>
What I'm trying to do is to create some sort of array that'll keep track of milliSecondsElapsed in my handleStart and handleStop method.
Here's what I have.
import React, {Component} from "react";
import Layout from '../components/MyLayout.js';
export default class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
this.updateState = this.updateState.bind(this);
this.textInput = React.createRef();
}
componentDidMount() {
window.addEventListener("keypress", this.keyPress);
}
componentWillUnmount() {
window.removeEventListener("keypress", this.keyPress);
}
textInput = () => {
clearInterval(this.timer);
};
updateState(e) {
this.setState({})
this.setState({ milliSecondsElapsed: e.target.milliSecondsElapsed });
}
keyPress = (e) => {
if (e.keyCode === 32) {
// some logic to assess stop/start of timer
if (this.state.milliSecondsElapsed === 0) {
this.startBtn.click();
} else if (this.state.timerInProgress === false) {
this.startBtn.click();
} else {
this.stopBtn.click();
}
}
};
handleStart = () => {
if (this.state.timerInProgress === true) return;
this.setState({
milliSecondsElapsed: 0
});
this.timer = setInterval(() => {
this.setState(
{
milliSecondsElapsed: this.state.milliSecondsElapsed + 1,
timerInProgress: true
},
() => {
this.stopBtn.focus();
}
);
}, 10);
};
handleStop = () => {
this.setState(
{
timerInProgress: false
},
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
times = () => {
this.setState(previousState => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
render() {
return (
<Layout>
<div className="index" align='center'>
<input
value={this.state.milliSecondsElapsed/100}
onChange={this.updateState}
ref={this.textInput}
readOnly={true}
/>
<button onClick={this.handleStart} ref={(ref) => (this.startBtn = ref)}>
START
</button>
<button onClick={this.handleStop} ref={(ref) => (this.stopBtn = ref)}>
STOP
</button>
<h1>{this.state.milliSecondsElapsed/100}</h1>
</div>
</Layout>
);
}
}
Issue
this.times is a function that only updates state, it doesn't return any renderable JSX.
times = () => {
this.setState((previousState) => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
Solution
Create a myArray state.
this.state = {
myArray: [], // <-- add initial empty array
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
Move the state update logic from this.times to this.handleStop.
handleStop = () => {
this.setState(
(previousState) => ({
timerInProgress: false,
myArray: [
...previousState.myArray, // <-- shallow copy existing data
this.state.milliSecondsElapsed / 100 // <-- add new time
]
}),
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
Render the array of elapsed times as a comma separated list.
<div>{this.state.myArray.join(", ")}</div>
Full code
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
myArray: [],
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
this.updateState = this.updateState.bind(this);
this.textInput = React.createRef();
}
componentDidMount() {
window.addEventListener("keypress", this.keyPress);
}
componentWillUnmount() {
window.removeEventListener("keypress", this.keyPress);
}
textInput = () => {
clearInterval(this.timer);
};
updateState(e) {
this.setState({ milliSecondsElapsed: e.target.milliSecondsElapsed });
}
keyPress = (e) => {
if (e.keyCode === 32) {
// some logic to assess stop/start of timer
if (this.state.milliSecondsElapsed === 0) {
this.startBtn.click();
} else if (this.state.timerInProgress === false) {
this.startBtn.click();
} else {
this.stopBtn.click();
}
}
};
handleStart = () => {
if (this.state.timerInProgress === true) return;
this.setState({
milliSecondsElapsed: 0
});
this.timer = setInterval(() => {
this.setState(
{
milliSecondsElapsed: this.state.milliSecondsElapsed + 1,
timerInProgress: true
},
() => {
this.stopBtn.focus();
}
);
}, 10);
};
handleStop = () => {
this.setState(
(previousState) => ({
timerInProgress: false,
myArray: [
...previousState.myArray,
this.state.milliSecondsElapsed / 100
]
}),
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
render() {
return (
<div>
<div className="index" align="center">
<input
value={this.state.milliSecondsElapsed / 100}
onChange={this.updateState}
ref={this.textInput}
readOnly={true}
/>
<button
onClick={this.handleStart}
ref={(ref) => (this.startBtn = ref)}
>
START
</button>
<button onClick={this.handleStop} ref={(ref) => (this.stopBtn = ref)}>
STOP
</button>
<h1>{this.state.milliSecondsElapsed / 100}</h1>
</div>
<div>{this.state.myArray.join(", ")}</div>
</div>
);
}
}
I understand from this SO answer, that we must manually remove Firebase listeners. How can I do that in the following use case? My successful attempt is shown in the below code.
I tried to use some of the ideas from this answer too. But unsuccessfully.
What am I doing wrong?
import React, { Component } from 'react';
// redacted for brevity
import firebase from '#firebase/app';
import '#firebase/firestore';
class CRUDContainer extends Component {
state = {
items: [],
path: null,
isError: false,
isLoading: true,
};
componentWillUnmount () {
// cancel subscriptions and async tasks to stop memory leaks
this.unsubscribe(this.path);
}
unsubscribe = path => path && firebase.firestore().collection(path).onSnapshot(() => {})
getItems = path => {
const out = [];
const db = firebase.firestore();
if(!db) return;
db.collection(path)
.orderBy('timestamp', 'desc')
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
out.push(doc.data());
});
return out;
})
.then(result => {
const newState = {
path,
items: result,
isError: false,
isLoading: false,
};
this.setState(newState);
return result;
})
.then(() => {
this.unsubscribe(path);
return path;
})
.catch(error => {
console.error('Error getting documents: \n', error);
const newState = {
isError: true,
isLoading: false,
};
this.setState(newState);
});
};
Child = ({ match: { params: { id }}}) => {
// redacted for brevity
getItems(path);
return (
this.state.isLoading
?
<Loading/>
:
(
this.state.isError
?
<ErrorMessage/>
:
(items && (
<CRUDView items={items} />
)))
)
};
render() {
return <Route path="/:id" component={this.Child} />
}
}
export default CRUDContainer;
I am learning rxjs. I create decorator "toggleable" for Dropdown component. All work fine, but I don't like it. How can I remove condition "toggle/hide".
Uses rxjs, react.js, recompose.
It's toogleable decorator for Dropdown component.
export const toggleable = Wrapped => componentFromStream((props$) => {
// toogleHandler called with onClick
const { handler: toogleHandler, stream: toogle$ } = createEventHandler();
// hideHandler called with code below
const { handler: hideHandler, stream: hide$ } = createEventHandler();
const show$ = Observable.merge(
toogle$.mapTo('toogle'),
hide$.mapTo('hide'))
.startWith(false)
.scan((state, type) => {
if (type === 'toogle') {
return !state;
}
if (type === 'hide') {
return false;
}
return state;
});
return props$
.combineLatest(
show$,
(props, show) => (
<Wrapped
{...props}
show={show}
onToggle={toogleHandler}
onHide={hideHandler}
/>
));
});
It's decorator for Dropdown button
// hideHandler caller
class Foo extends Component {
constructor(props) {
super(props);
this.refButton.bind(this);
this.documentClick$ = Observable.fromEvent(global.document, 'click')
.filter(event => this.button !== event.target)
.do((event) => { this.props.onHide(event); });
}
componentDidMount() {
this.documentClick$.subscribe();
}
componentWillUnmount() {
this.documentClick$.unsubscribe();
}
refButton = (ref) => {
this.button = ref;
}
}
You can implement show$ with no conditions by mapping the toggle$/hide$ to functions on the previous state:
const show$ = Observable.merge(
toggle$.mapTo(prev => !prev),
hide$.mapTo(prev => false))
.startWith(false)
.scan((state, changeState) => changeState(state));
Another improvement you can do is with your toggleable implementation. Instead of using recompose componentFromStream, you can use recompose mapPropsStream:
export const toggleable = mapPropsStream(props$ => {
const { handler: toogleHandler, stream: toogle$ } = createEventHandler();
const { handler: hideHandler, stream: hide$ } = createEventHandler();
const show$ = Observable.merge(
toggle$.map(() => prev => !prev),
hide$.map(() => prev => false))
.startWith(false)
.scan((state, changeState) => changeState(state));
return props$
.combineLatest(
show$,
(props, show) => ({
...props,
show
onToggle: toogleHandler
onHide: hideHandler
})
);
});