Update tvWidget in charting component using hooks - javascript

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.

Related

Show and Hide Condition in React

I have a simple problem here which I can't figure out. I wanted to hide menus depending on the condition.
For example if status contains at least one "Unlinked". "All unlinked images" menu should appear. I did used .some and I wonder why it doesn't return a boolean.
Codesandbox is here Click here
const showDeleteAllInvalidButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Invalid");
};
const showDeleteAllUnlinkedButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Unlinked");
};
The methods do return a boolean. But in the menus array you are assigning a function reference not the result -
show: showDeleteAllInvalidButton // function reference
show is now assigned a reference to the function showDeleteAllInvalidButton not the result of productImages?.some. You need to invoke the functions when assigning -
show: showDeleteAllInvalidButton() // result of productImages?.some
In your menus object you have a key that contains a function, so if you want this function to filter out your elements you need to execute the show method in side the filter method.
import React, { useState } from "react";
import Button from "#mui/material/Button";
import MenuItem from "#mui/material/MenuItem";
import KeyboardArrowDownIcon from "#mui/icons-material/KeyboardArrowDown";
import CustomMenu from "../../Menu";
const products = [
{
productName: "Apple",
productImages: [
{
status: "Unlinked"
}
]
},
{
productName: "Banana",
productImages: [
{
status: "Unlinked"
}
]
},
{
productName: "Mango",
productImages: [
{
status: "Unlinked"
},
{
status: "Unlinked"
}
]
}
];
const HeaderButtons = () => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const showDeleteAllInvalidButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Invalid");
};
const showDeleteAllUnlinkedButton = () => {
const productImages = products?.flatMap((product) =>
product.productImages.filter((image) => image?.status)
);
return productImages?.some((e) => e?.status === "Unlinked");
};
const menus = [
{
id: 1,
name: "Invalid images",
action: () => {
handleClose();
},
show: showDeleteAllInvalidButton
},
{
id: 2,
name: "Unlinked images",
action: () => {
handleClose();
},
show: showDeleteAllUnlinkedButton
},
{
id: 3,
name: "All images",
action: () => {
handleClose();
},
show: () => true // not that I changed it to a function for consistency, but you can check for type in the filter method instead of running afunction
}
];
return (
<div>
<Button
color="error"
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
variant="outlined"
onClick={handleClick}
endIcon={<KeyboardArrowDownIcon />}
>
Options
</Button>
<CustomMenu anchorEl={anchorEl} open={open} onClose={handleClose}>
{menus
.filter((e) => e.show()) // here is your mistake
.map(
({
id = "",
action = () => {},
icon = null,
name = "",
divider = null
}) => (
<>
<MenuItem key={id} onClick={action} disableRipple>
{icon}
{name}
</MenuItem>
{divider}
</>
)
)}
</CustomMenu>
</div>
);
};
export default HeaderButtons;
In your code, it will always render because your filter functions are evaluating as truth.

React: Update a specific attribute of an array of objects

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;
});
};

how to make proper default props with single objects

I have this code:
const setup = (props: SchemaModalProps = { isOpen: false, onClose: () => { }, row: {}, onSchemaChange: () => { }, updateSchema: () => { }, hasPermission: false }) => {
const wrapper: any = mount(<SchemaModal {...props} />);
const driver = new SchemaModalDriver(wrapper);
return driver;
};
and when I call the setup function I need to specify the inner object items like so:
const driver = setup({ isOpen: true, row: someTriggerConfiguration, onClose: () => { }, onSchemaChange: () => { }, updateSchema: () => { }, hasPermission: true });
how can I rewrite the code in such a way that if I do setup({isOpen:false}) it will only overwrite the isOpen and not the rest of them ( use their default values).
You can destructure the props object and declare the function like below:
const setup = ({
isOpen = false,
onClose = () => {},
row = {},
onSchemaChange = () => {},
updateSchema = () => {},
hasPermission = false
}: SchemaModalProps) => {
/**
* Your code
*/
return <></>;
};
Now setup({isOpen:false}) will only override the isOpen property.
You can use Object.assign() to combine two objects. This way only supplied new values will override default.
const setup = (props: SchemaModalProps) =>
{
SchemaModalProps = Object.assign({
isOpen: false,
onClose: () => { },
row: {},
onSchemaChange: () => { },
updateSchema: () => { },
hasPermission: false
}, SchemaModalProps || {});
const wrapper: any = mount(<SchemaModal {...props} />);
const driver = new SchemaModalDriver(wrapper);
return driver;
};
Based on previous questions I found this to work best:
const setup = ({ isOpen = false, onClose = () => { }, row = {}, onInfoChange = () => { }, hasPermission = false }) => {
const props: TriggerInfoModalProps = { isOpen, onClose, row, onInfoChange, hasPermission }
const wrapper: any = mount(<TriggerInfoModal {...props} />);
const driver = new TriggerInfoModalDriver(wrapper);
return driver;
};

Rendering multiple times?

I have a form component, and the reference of input fields are linked to the useForm reducer with references. I have to set a initial form state after setting the input field references? I have done as below. But it is rendering thrice. How to solve this rendering issue?
import React, { useState } from 'react';
const useForm = () => {
const [ formState, setFormState ] = useState({});
const refs = useRef({});
const register = useCallback(( fieldArgs ) => ref => {
if(fieldArgs) {
const { name, validations, initialValue } = fieldArgs;
refs.current[name] = ref;
}
console.log('Register rendered');
}, []);
useEffect(() => {
console.log('Effect Rendered');
const refsKeys = Object.keys(refs.current);
refsKeys.forEach(refKey => {
if(!formState[refKey]) {
setFormState(prevState => {
return {
...prevState,
[refKey]: {
value: '',
touched: false,
untouched: true,
pristine: true,
dirty: false
}
}
});
}
});
}, [ refs ]);
return [ register ];
}
export { useForm };
And the app component as below
const App = () => {
const [ register ] = useFormsio();
return(
<form>
<input
type = 'email'
placeholder = 'Enter your email'
name = 'userEmail'
ref = { register({ name: 'userEmail' }) } />
<button
type = 'submit'>
Submit
</button>
</form>
)
}
How to solve this multiple rendering issue?
I think the issue in the code above is whenever refs changes you need to loop through all the fields in form and set the state.
Why don't you set the state in register method?
const register = useCallback(( fieldArgs ) => ref => {
if(fieldArgs) {
const { name, validations, initialValue } = fieldArgs;
if(!refs.current[name] ) {
refs.current[name] = ref;
setFormState(prevState => {
return {
...prevState,
[refKey]: {
value: '',
touched: false,
untouched: true,
pristine: true,
dirty: false
}
}
});
}
}
console.log('Register rendered');
}, []);

How to update state array?

I am making a App in Reactjs. In it I have an API that gets data from a AWS database table and populates a state array which then populates a tables.
I cant figure out how to let a user in turn update the values of the table and then save those changes and let the API upload it back into the data base.
I have a update method but it gives an error saying that I cant update the state Array or would complain that the column array is undefined.
I have the following code:
export default function Complaints(props) {
const [complaint, setComplaint] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isInEditMode, setIsEditMode] = useState(false);
const [defaultValue, setDefaultValue] = useState("");
var columsArr = [
{ title: 'Customer ID', field: 'id' },
{ title: 'Issue', field: 'complaintName' },
{ title: 'Description', field: 'complaintDescription'},
{ title: 'Order ID', field: 'complaintOrderId'},
{ title: 'Submitted', field: 'createdAt'},
{ title: 'Updated', field: 'updatedAt'},
{ title: 'Admin Comment', field: 'adminComment'},
];
useEffect(() => {
async function onLoad() {
if (!props.isAuthenticated) {
return;
}
try {
const complaint = await loadComplaint();
setComplaint(complaint);
setState({
columns: [state.columns, ...columsArr],
complaint: [...state.complaint, ...complaint]
});
console.log(complaint)
} catch (e) {
alert(e);
}
setIsLoading(false);
}
onLoad();
}, [props.isAuthenticated]);
function loadComplaint() {
return API.get("kleen", "/Complaint");
}
// function edit(adminComment) {
// setIsEditMode(true);
// setDefaultValue(adminComment);
// console.log("value is"+ adminComment);
// }
// function updateComplaint() {
// return API.put("kleen", `/Complaint/${props.}`);
// }
const [state, setState] = React.useState({
columns: [],
complaint: []
});
return (
<MaterialTable style={{
marginTop: "8rem",
marginLeft: "auto",
marginRight: "auto",
position: "sticky",
}}
title="Complaints"
columns={state.columns}
data={state.complaint}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
let key = 1;
setState(prevState => {
complaint: prevState.complaint.map(el => el.key === key ? {...el, adminComment: "testing"}: el)
// const data = [...prevState.data];
// data[data.indexOf(oldData)] = newData;
// return { ...prevState, data };
});
}
}, 600);
}),
}}
/>
);
}
I am very new to Reactjs, any help would be greatly appreciated.
One way that you can achieve is by:
First store the state in a variable
Update the array variable
Restore the variable to the state.
If you want to use state then better to use component class
export default class Complaints extends Component {
//assign initial value to state
state={
propsObj : this.props
}
...
}
If you want to use component you have to import it
import React, { Component } from "react";
inside component class you have to use this.props instead of props
later if you want to change state just use this.setState({ propsObj: 'some thing new'});

Categories