How implement toggle with Rxjs - javascript

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

Related

window is not defined in svelte - using svelte store

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

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

Converting Class app into Hooks for React Next app

I am going through documentation for Algolia and Next and am trying to get URLs to show up in the address bar, most of the examples are as Class Components but the app I am working on uses Hooks. I am trying to test some of the examples on my site, but am stuck on how to correctly convert a class app in React to hooks as I keep getting errors.
Class Example:
const updateAfter = 400;
const createURL = (state) => `?${qs.stringify(state)}`;
const searchStateToUrl = (props, searchState) =>
searchState ? `${props.location.pathname}${createURL(searchState)}` : '';
const urlToSearchState = ({ search }) => qs.parse(search.slice(1));
class App extends Component {
state = {
searchState: urlToSearchState(this.props.location),
lastLocation: this.props.location,
};
static getDerivedStateFromProps(props, state) {
if (props.location !== state.lastLocation) {
return {
searchState: urlToSearchState(props.location),
lastLocation: props.location,
};
}
return null;
}
onSearchStateChange = searchState => {
clearTimeout(this.debouncedSetState);
this.debouncedSetState = setTimeout(() => {
const href = searchStateToURL(searchState);
this.props.router.push(href, href, {
shallow: true
});
}, updateAfter);
this.setState({ searchState });
};
My converted attempt:
const createURL = state => `?${qs.stringify(state)}`;
const pathToSearchState = path =>
path.includes("?") ? qs.parse(path.substring(path.indexOf("?") + 1)) : {};
const searchStateToURL = searchState =>
searchState ? `${window.location.pathname}?${qs.stringify(searchState)}` : "";
const DEFAULT_PROPS = {
searchClient,
indexName: "instant_search"
};
const Page = () => {
const [searchState, setSearchState] = useState(<not sure what goes here>)
const [lastRouter, setRouterState] = useState(router)
Page.getInitialProps = async({ asPath }) => {
const searchState = pathToSearchState(asPath);
const resultsState = await findResultsState(App, {
...DEFAULT_PROPS,
searchState
});
return {
resultsState,
searchState
};
}
//unsure how to convert here
static getDerivedStateFromProps(props, state) {
if (!isEqual(state.lastRouter, props.router)) {
return {
searchState: pathToSearchState(props.router.asPath),
lastRouter: props.router
};
}
return null;
}
const onSearchStateChange = searchState => {
clearTimeout(debouncedSetState);
const debouncedSetState = setTimeout(() => {
const href = searchStateToURL(searchState);
router.push(href, href, {
shallow: true
});
}, updateAfter);
setSearchState({ searchState });
};

How to fix onClick element. ReactJS

I new in ReactJS and i have one few question. I defined function showModal and but console.log() and
this.setState({show:!this.state.show});.
And after that I applied
this function onClick event for div element inside map function.
1st question: When I click on div element showModal work but in console I don't see my console.log.
2nd question: I want to make when you click on one div element it must add/show few new div elements but only for one div element (on which I clicked). But now when I click on one div element it add/show new elements for all div elements which had this showModal function.
How can i fix this
import React, { Component } from "react";
import Modal from '../components/modal/form'
const DEFAULT_QUERY = 'redux';
const PATH_BASE = 'URL which work correct';
class Actions extends React.PureComponent{
constructor(){
super();
this.state = {
result:null,
show:false,
helpId:null
};
this.setSearchTopStories = this.setSearchTopStories.bind(this);
this.showModal = this.showModal.bind(this);
this.handleClickFromParent = this.handleClickFromParent.bind(this);
this.onClose = this.onClose.bind(this);
}
onClose = e => {
this.setState({ show: false});
}
handleClickFromParent = e => {
this.setState({show: !this.state.show});
}
showModal = e => {
console.log('BABE');
this.setState({show: !this.state.show})
};
setSearchTopStories(result) {
this.setState({ result });
};
componentDidMount() {
fetch(`${PATH_BASE}`)
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
.catch(error => error);
};
render(){
const { searchTerm, result } = this.state;
console.log('* Actions Pure*');
console.log(result);
console.log('=');
return(
<div>
{
(result !== null) ?
result.map(
(item,index) =>
<div>
<div onClick={()=>this.showModal()}>{item.name}</div>
<Modal
id = {index}
handleClickFromParent {this.handleClickFromParent}
item = {[item]}
show = {this.state.show}
onClose = {this.onClose}>
YOLO
</Modal>
</div>
)
: null
}
</div>
)
}
}
export default Actions;
While selecting u can pass the item on method, and on click u can set the item value. Please check the below code.
Demo:
https://codesandbox.io/s/stackoverflowmodal-19i36
this.state = {
result: null,
show: false,
selectedItem:null,
helpId: null
};
//
showModal = (selectedItem) => {
this.setState({
show: !this.state.show,
selectedItem
});
};
//
class Actions extends React.PureComponent {
constructor() {
super();
this.state = {
result: null,
show: false,
selectedItem: null,
helpId: null
};
this.setSearchTopStories = this.setSearchTopStories.bind(this);
this.showModal = this.showModal.bind(this);
this.handleClickFromParent = this.handleClickFromParent.bind(this);
this.onClose = this.onClose.bind(this);
}
onClose = e => {
this.setState({
show: false
});
};
handleClickFromParent = e => {
this.setState({
show: !this.state.show
});
};
showModal = selectedItem => {
this.setState({
show: !this.state.show,
selectedItem
});
};
setSearchTopStories(result) {
this.setState({ result });
}
componentDidMount() {
fetch(`${PATH_BASE}`)
.then(response => response.json())
.then(result => this.setSearchTopStories(result))
.catch(error => error);
}
render() {
const { searchTerm, result, selectedItem } = this.state;
return (
<div>
{result && result.length
? result.map((item, index) => (
<div>
<div onClick={() => this.showModal(item)}>{item.name}</div>
</div>
))
: null}
{selectedItem && (
<Modal
id={index}
handleClickFromParent={this.handleClickFromParent}
item={[selectedItem]}
show={this.state.show}
onClose={this.onClose}
>
YOLO
</Modal>
)}
</div>
);
}
}
export default Actions;

React-Redux Refactoring Container Logic

I got one container connected to one component. Its a select-suggestion component. The problem is that both my container and component are getting too much repeated logic and i want to solve this maybe creating a configuration file or receiving from props one config.
This is the code:
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { goToPageRequest as goToPageRequestCompetitions } from '../ducks/competitions/index';
import { getSearchParam as getSearchCompetitionsParam, getCompetitionsList } from '../ducks/competitions/selectors';
import { goToPageRequest as goToPageRequestIntermediaries } from '../ducks/intermediaries/index';
import { getSearchParam as getSearchIntermediariesParam, getIntermediariesList } from '../ducks/intermediaries/selectors';
import SelectBox2 from '../components/SelectBox2';
export const COMPETITIONS_CONFIGURATION = {
goToPageRequest: goToPageRequestCompetitions(),
getSearchParam: getSearchCompetitionsParam(),
suggestions: getCompetitionsList()
};
export const INTERMEDIARIES_CONFIGURATION = {
goToPageRequest: goToPageRequestIntermediaries(),
getSearchParam: getSearchIntermediariesParam(),
suggestions: getIntermediariesList()
};
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(SelectBox2));
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Flex, Box } from 'reflexbox';
import classname from 'classnames';
import styles from './index.scss';
import Input from '../Input';
import { AppButtonRoundSquareGray } from '../AppButton';
import RemovableList from '../RemovableList';
const MIN_VALUE_TO_SEARCH = 5;
const NO_SUGGESTIONS_RESULTS = 'No results found';
class SelectBox extends Component {
/**
* Component setup
* -------------------------------------------------------------------------*/
constructor(props) {
super(props);
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
}
/**
* Component lifecycle
* -------------------------------------------------------------------------*/
componentWillMount() {
console.log(this.props);
document.addEventListener('mousedown', this.onClickOutside, false);
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
}
componentWillUnmount() {
console.log(this.props);
document.removeEventListener('mousedown', this.onClickOutside, false);
}
componentWillReceiveProps(nextProps){
console.log(this.props);
if (this.props.suggestionsType === 'competition') {
this.state.suggestions = nextProps.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries') {
this.state.suggestions = nextProps.intermediariesSuggestions;
}
}
/**
* DOM event handlers
* -------------------------------------------------------------------------*/
onButtonClick = (ev) => {
ev.preventDefault();
const itemIncluded = this.state.items.find(item => item.id === this.state.selection);
if (this.state.selection && !itemIncluded) {
const item =
this.state.suggestions.find(suggestion => suggestion.id === this.state.selection);
this.setState({ items: [...this.state.items, item] });
}
};
onChangeList = (items) => {
const adaptedItems = items
.map(item => ({ label: item.name, id: item.itemName }));
this.setState({ items: adaptedItems });
};
onClickOutside = (ev) => {
if (this.wrapperRef && !this.wrapperRef.contains(ev.target)) {
this.setState({ displayBox: false });
}
};
onSuggestionSelected = (ev) => {
this.setState({
displayBox: false,
value: ev.target.textContent,
selection: ev.target.id });
};
onInputChange = (ev) => {
this.generateSuggestions(ev.target.value);
};
onInputFocus = () => {
this.generateSuggestions(this.state.value);
};
/**
* Helper functions
* -------------------------------------------------------------------------*/
setWrapperRef = (node) => {
this.wrapperRef = node;
};
executeSearch = (value) => {
if (this.props.suggestionsType === 'competition'){
this.props.searchCompetitions(value);
}
if (this.props.suggestionsType === 'intermediaries'){
this.props.searchIntermediaries(value);
}
};
generateSuggestions = (value) => {
if (value.length > MIN_VALUE_TO_SEARCH) {
this.executeSearch(value);
this.setState({ displayBox: true, value, selection: '' });
} else {
this.setState({ displayBox: false, value, selection: '' });
}
};
renderDataSuggestions = () => {
const { listId } = this.props;
const displayClass = this.state.displayBox ? 'suggestions-enabled' : 'suggestions-disabled';
return (
<ul
id={listId}
className={classname(styles['custom-box'], styles[displayClass], styles['select-search-box__select'])}
>
{ this.state.suggestions.length !== 0 ?
this.state.suggestions.map(suggestion => (<li
className={classname(styles['select-search-box__suggestion'])}
onClick={this.onSuggestionSelected}
id={suggestion.get(this.props.suggestionsOptions.id)}
key={suggestion.get(this.props.suggestionsOptions.id)}
>
<span>{suggestion.get(this.props.suggestionsOptions.label)}</span>
</li>))
:
<li className={(styles['select-search-box__no-result'])}>
<span>{NO_SUGGESTIONS_RESULTS}</span>
</li>
}
</ul>
);
};
renderRemovableList = () => {
if (this.state.items.length > 0) {
const adaptedList = this.state.items
.map(item => ({ name: item.name, itemName: item.id }));
return (<RemovableList
value={adaptedList}
className={classname(styles['list-box'])}
onChange={this.onChangeList}
uniqueIdentifier="itemName"
/>);
}
return '';
};
render() {
const input = {
onChange: this.onInputChange,
onFocus: this.onInputFocus,
value: this.state.value
};
return (
<Flex className={styles['form-selectBox']}>
<Box w={1}>
<div
ref={this.setWrapperRef}
className={styles['div-container']}
>
<Input
{...this.props}
input={input}
list={this.props.listId}
inputStyle={classname('form-input--bordered', 'form-input--rounded', styles.placeholder)}
/>
{ this.renderDataSuggestions() }
</div>
</Box>
<Box>
<AppButtonRoundSquareGray type="submit" className={styles['add-button']} onClick={this.onButtonClick}>
Add
</AppButtonRoundSquareGray>
</Box>
<Box>
{ this.renderRemovableList() }
</Box>
</Flex>
);
}
}
SelectBox.propTypes = {
items: PropTypes.instanceOf(Array),
placeholder: PropTypes.string,
listId: PropTypes.string,
className: PropTypes.string
};
SelectBox.defaultProps = {
items: [],
placeholder: 'Choose an option...',
listId: null,
className: ''
};
export default SelectBox;
As you see, in many places i am validating the type of suggestions and do something with that. Its suppose to be a reusable component, and this component could accept any kind of type of suggestions. If this grows, if will have very big validations and i don't want that. So i think that i want something similar to this:
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchGoToPage: goToPageRequestObj =>
dispatch(ownProps.reduxConfiguration.goToPageRequest(goToPageRequestObj)),
});
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
search: searchParam => dispatchProps.dispatchGoToPage({
searchParam
}),
...stateProps
});
How can i make something similar to that?
Here are a few things to consider:
The purpose of using Redux is to remove state logic from your components.
What you've currently got has Redux providing some state and your component providing some state. This is an anti-pattern (bad):
// State from Redux: (line 22 - 24)
const mapStateToProps = (state, ownProps) => ({
searchString: ownProps.reduxConfiguration.getSearchParam(state),
});
// State from your component: (line 65 - 71)
this.state = {
displayBox: false,
selection: null,
value: '',
items: [],
suggestions: [],
};
If you take another look at your SelectBox component - a lot of what it is doing is selecting state:
// The component is parsing the state and choosing what to render (line 79 - 86)
if (this.props.suggestionsType){
if (this.props.suggestionsType === 'competition'){
this.state.suggestions = this.props.competitionsSuggestions;
}
if (this.props.suggestionsType === 'intermediaries'){
this.state.suggestions = this.props.intermediariesSuggestions;
}
}
Turns out, this is precisely what mapStateToProps() is for. You should move this selection logic to mapStateToProps(). Something like this:
const mapStateToProps = (state) => {
let suggestions = null;
switch (state.suggestionType) {
case 'competition':
suggestions = state.suggestions.competition;
break;
case 'intermediaries':
suggestions = state.suggestions.intermediaries;
break;
default:
break;
}
return {
suggestions
};
};
Every time the state updates (in Redux) it will pass new props to your component. Your component should only be concerned with how to render its part of the state. And this leads me to my next point: When your application state is all being managed by Redux and you don't have state logic in your components, your components can simply be functions (functional components).
const SelectBox3 = ({ suggestions }) => {
const onClick = evt => { console.log('CLICK!'); };
const list = suggestions.map((suggestion, index) => {
return (
<li key={index} onClick={onClick}>suggestion</li>
);
});
return (
<ul>
{list}
</ul>
);
};
Applying these patterns, you get components that are very easy to reason about, and that is a big deal if you want to maintain this code into the future.
Also, by the way, you don't need to use mergeProps() in your example. mapDispatchToProps can just return your search function since connect() will automatically assemble the final props object for you.:
const mapDispatchToProps = (dispatch, ownProps) => ({
// 'search' will be a key on the props object passed to the component!
search: searchParam => {
dispatch(ownProps.reduxConfiguration.goToPageRequest({ searchParam });
// (also, your 'reduxConfiguration' is probably something that belongs in
// the Redux state.)
}
});
I highly recommend giving the Redux docs a good read-through. Dan Abramov (and crew) have done a great job of laying it all out in there and explaining why the patterns are the way they are.
Here's the link: Redux.
Also, look into async actions and redux-thunk for dealing with asynchronous calls (for performing a search on a server, for example).
Finally let me say: you're on the right track. Keep working on it, and soon you will know the joy of writing elegant functional components for your web apps. Good luck!

Categories