how to make proper default props with single objects - javascript

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

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

Context state being updated unexpectedly - React Typescript

First time poster so let me know if more information is need.
Trying to figure out why my global state using context API is being updated even when my setSate method is commented out. I thought i might have been mutating the state directly accidently but I dont believe I am
"specialModes" in actionOnClick() is the state in question
const SpecialFunctions: FC = (props: Props) => {
const { currentModeContext, specialModesContext: specialActionsContext, performCalc, inputValueContext } = useContext(AppContext)
const { specialModes, setSpecialModes } = specialActionsContext
const { currentMode, setCurrentMode } = currentModeContext
const { inputValue, setInputValue } = inputValueContext
const categoryOnClick = (index: number) => {
setCurrentMode(specialModes[index])
console.log(specialModes[index].title);
}
const actionOnClick = (action: IAction) => {
let newAction = action
newAction.value = performCalc()
let newSpecialModes = specialModes.map((mode) => {
if (mode === currentMode) {
let newMode = mode
newMode.actions = mode.actions.map((element) => {
if (element === action) {
return newAction
}
else return element
})
return newMode
}
else return mode
})
//setSpecialModes(newSpecialModes)
}
let headings = specialModes.map((categorgy, index) => {
return <Heading isActive={categorgy === currentMode ? true : false} onClick={() => categoryOnClick(index)} key={index}>{categorgy.title}</Heading>
})
let actions = currentMode.actions.map((action, index) => {
return (
<Action key={index} onClick={() => actionOnClick(action)}>
<ActionTitle>{action.title}</ActionTitle>
<ActionValue>{action.value}</ActionValue>
</Action>
)
})
return (
<Wrapper>
<Category>
{headings}
</Category>
<ActionsWrapper toggleRadiusCorner={currentMode === specialModes[0] ? false : true}>
{actions}
</ActionsWrapper>
</Wrapper>
)
}
Context.tsx
interface ContextType {
specialModesContext: {
specialModes: Array<ISpecialModes>,
setSpecialModes: React.Dispatch<React.SetStateAction<ISpecialModes[]>>
},
currentModeContext: {
currentMode: ISpecialModes,
setCurrentMode: React.Dispatch<React.SetStateAction<ISpecialModes>>
},
inputValueContext: {
inputValue: string,
setInputValue: React.Dispatch<React.SetStateAction<string>>
},
inputSuperscriptValueContext: {
inputSuperscriptValue: string,
setInputSuperscriptValue: React.Dispatch<React.SetStateAction<string>>
},
performCalc: () => string
}
export const AppContext = createContext({} as ContextType);
export const ContextProvider: FC = ({ children }) => {
const [SpecialModes, setSpecialModes] = useState([
{
title: 'Rafter',
actions: [
{
title: 'Span',
active: false
},
{
title: 'Ridge Thickness',
active: false
},
{
title: 'Pitch',
active: false
}
],
},
{
title: 'General',
actions: [
{
title: 'General1',
active: false
},
{
title: 'General2',
active: false
},
{
title: 'General3',
active: false
}
],
},
{
title: 'Stairs',
actions: [
{
title: 'Stairs1',
active: false
},
{
title: 'Stairs2',
active: false
},
{
title: 'Stairs3',
active: false
}
],
}
] as Array<ISpecialModes>)
const [currentMode, setCurrentMode] = useState(SpecialModes[0])
const [inputValue, setInputValue] = useState('0')
const [inputSuperscriptValue, setInputSuperscriptValue] = useState('')
const replaceCharsWithOperators = (string: string): string => {
let newString = string.replaceAll(/\s/g, '') // delete white space
newString = newString.replace('×', '*')
newString = newString.replace('÷', '/')
console.log(string)
console.log(newString)
return newString
}
const performCalc = (): string => {
let originalEquation = `${inputSuperscriptValue} ${inputValue} =`
let equation = inputSuperscriptValue + inputValue
let result = ''
equation = replaceCharsWithOperators(equation)
result = eval(equation).toString()
setInputSuperscriptValue(`${originalEquation} ${result}`)
setInputValue(result)
console.log(result)
return result
}
return (
<AppContext.Provider value={
{
specialModesContext: {
specialModes: SpecialModes,
setSpecialModes: setSpecialModes
},
currentModeContext: {
currentMode,
setCurrentMode
},
inputValueContext: {
inputValue,
setInputValue
},
inputSuperscriptValueContext: {
inputSuperscriptValue,
setInputSuperscriptValue
},
performCalc
}}>
{children}
</AppContext.Provider>
)
}
In your mode.actions.map() function you are indirectly changing actions field of your original specialModes array.
To fix this problem you need to create shallow copy of specialModes array Using the ... ES6 spread operator.
const clonedSpecialModes = [...specialModes];
let newSpecialModes = clonedSpecialModes.map((mode) => {
// rest of your logic here
})

Update tvWidget in charting component using hooks

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.

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');
}, []);

Categories