Modifying styles of sub-elements for selected ListItems - reactjs

I'm having some issue troubleshooting how to pass styles down to material UI components and modifying the styling of elements with active states. I'm currently following the example provided under the "Selectable List" option here.
I can modify some styles of the list active list item though the selectedItemStyle prop in the HOC but some styles are overridden. For example the color of the secondaryTextColor is overridden and I'm not sure how to override the default other than giving it a class and using !important which is not preferred. An example of my HOC is below.
import {List, makeSelectable} from 'material-ui/List';
import { tealA700 } from 'material-ui/styles/colors'
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
defaultValue: PropTypes.number.isRequired,
};
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
selectedItemStyle={{backgroundColor: tealA700, color: 'white', secondaryTextColor: 'white'}}
>
{this.props.children}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
export default SelectableList

Related

How to change React component from outside?

The React component, which can be considered as third-party component, looks as following:
import * as React from 'react';
import classnames from 'classnames';
import { extractCommonClassNames } from '../../utils';
export const Tag = (props: React.ElementConfig): React$Node =>{
const{
classNames,
props:
{
children,
className,
...restProps
},
} = extractCommonClassNames(props);
const combinedClassNames = classnames(
'tag',
className,
...classNames,
);
return (
<span
className={combinedClassNames}
{...restProps}
>
{children}
<i className="sbicon-times txt-gray" />
</span>
);
};
The component where I use the component above looks as following:
import React from 'react';
import * as L from '#korus/leda';
import type { KendoEvent } from '../../../types/general';
type Props = {
visible: boolean
};
type State = {
dropDownSelectData: Array<string>,
dropDownSelectFilter: string
}
export class ApplicationSearch extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {
dropDownSelectData: ['Имя', 'Фамилия', 'Машина'],
dropDownSelectFilter: '',
};
this.onDropDownSelectFilterChange = this.onDropDownSelectFilterChange.bind(this);
}
componentDidMount() {
document.querySelector('.sbicon-times.txt-gray').classList.remove('txt-gray');
}
onDropDownSelectFilterChange(event: KendoEvent) {
const data = this.state.dropDownSelectData;
const filter = event.filter.value;
this.setState({
dropDownSelectData: this.filterDropDownSelectData(data, filter),
dropDownSelectFilter: filter,
});
}
// eslint-disable-next-line class-methods-use-this
filterDropDownSelectData(data, filter) {
// eslint-disable-next-line func-names
return data.filter(element => element.toLowerCase().indexOf(filter.toLowerCase()) > -1);
}
render() {
const {
visible,
} = this.props;
const {
dropDownSelectData,
dropDownSelectFilter,
} = this.state;
return (
<React.Fragment>
{
visible && (
<React.Fragment>
<L.Block search active inner>
<L.Block inner>
<L.Block tags>
<L.Tag>
option 1
</L.Tag>
<L.Tag>
option 2
</L.Tag>
<L.Tag>
...
</L.Tag>
</L.Block>
</L.Block>
</React.Fragment>
)}
</React.Fragment>
);
}
}
Is it possible to remove "txt-gray" from the component from outside and if so, how?
Remove the class from where you're using the Tag component:
componentDidMount() {
document.querySelector('.sbicon-times.txt-gray').classList.remove('txt-gray')
}
Or more specific:
.querySelector('span i.sbicon-times.txt-gray')
As per your comment,
I have multiple components with "txt-gray", but when I use your code, "txt-gray" has been removed from first component only. How to remove it from all components?
I will suggest you to use the code to remove the class in the parent component of using the Tag component. And also use querySelectorAll as in this post.
Refactoring
A clean way is to modify the component to allow it to conditionally add txt-gray through a prop:
<i className={classnames('sbicon-times', { 'txt-gray': props.gray })} />
If the component cannot be modified because it belongs to third-party library, this involves forking a library or replacing third-party component with its modified copy.
Direct DOM access with findDOMNode
A workaround is to access DOM directly in parent component:
class TagWithoutGray extends React.Component {
componentDidMount() {
ReactDOM.findDOMNode(this).querySelector('i.sbicon-times.txt-gray')
.classList.remove('txt-gray');
}
// unnecessary for this particular component
componentDidUpdate = componentDidMount;
render() {
return <Tag {...this.props}/>;
}
}
The use of findDOMNode is generally discouraged because direct DOM access is not idiomatic to React, it has performance issues and isn't compatible with server-side rendering.
Component patching with cloneElement
Another workaround is to patch a component. Since Tag is function component, it can be called directly to access and modify its children:
const TagWithoutGray = props => {
const span = Tag(props);
const spanChildren = [...span.props.children];
const i = spanChildren.pop();
return React.cloneElement(span, {
...props,
children: [
...spanChildren,
React.cloneElement(i, {
...i.props,
className: i.props.className.replace('txt-gray', '')
})
]
});
}
This is considered a hack because wrapper component should be aware of patched component implementation, it may break if the implementation changes.
No, it is not possible
The only way is to change your Tag component
import * as React from 'react';
import classnames from 'classnames';
import { extractCommonClassNames } from '../../utils';
export const Tag = (props: React.ElementConfig): React$Node =>{
const{
classNames,
props:
{
children,
className,
...restProps
},
} = extractCommonClassNames(props);
const combinedClassNames = classnames(
'tag',
className,
...classNames,
);
const grayClass = this.props.disableGray ? 'sbicon-times' : 'sbicon-times txt-gray';
return (
<span
className={combinedClassNames}
{...restProps}
>
{children}
<i className={grayClass} />
</span>
);
};
Now, if you pass disableGray={true} it will suppress the gray class, otherwise of you pass false or avoid passing that prop at all it will use the gray class. It is a small change in the component, but it allows you not to change all the points in your code where you use this component (and you are happy with grey text)

React native set state is not working

I am trying to update state in react native component.
But its getting errors, Could someone help me.
I'm using react-native-cli verions: 2.0.1
react-native verions: 0.55.4
Here is my code:
import React, { Component } from 'react'
import {
Button,
Text,
View,
} from 'react-native';
export class ToggleButton extends Component {
state = {
isDone: false
};
onAction() {
const value = !this.state.isDone;
this.setState({ isDone: value });
const newValue = this.state.isDone;
console.log(newValue);
}
render() {
return (
<View>
<Button
title="Action"
onPress={this.onAction}
/>
</View>
)
}
}
export default ToggleButton;
You have three different solutions.
Bind your function in the constructor.
Use the experimental public class fields syntax.
Pass a lambda to executing your function.
The problem is that you're loosing the reference to this, because the function is not executed in the original context, so this.setState is not a function, but a undefined.
In this page there are examples for all of the approaches: https://reactjs.org/docs/handling-events.html
Change
onPress={this.onAction}
to
onPress={this.onAction.bind(this)}
Check: this
Below is the solution
import React, { Component } from 'react'
import {
Button,
Text,
View,
} from 'react-native';
export class ToggleButton extends Component {
// Add state in constructor like this
constructor(props){
super(props);
this.state = {
isDone: false
};
}
onAction() {
const value = !this.state.isDone;
this.setState({ isDone: value });
const newValue = this.state.isDone;
console.log(newValue);
}
render() {
return (
<View>
<Button
title="Action"
// Add function with onPress
onPress={() => this.onAction}
/>
</View>
)
}
}
export default ToggleButton;

How to disable a link in reactjs?

I have this in my reactjs app:
import Link from 'react-router/lib/Link'
Been trying to disable this link but this does not have the desired effect:
<Link disable={true}/>
It just renders it invisible. How can I disable( based on a condition) the reactjs Link?
Contain many issues on react-router, there is no support disabled attribute in Link Component, so you can try some with this issue:
1. onClick event
Use preventDefault() to handle onClick event.
/* YourComponent.js */
class YourComponent extends React.Component {
render() {
return (
<Link onClick={e => e.preventDefault()} />
);
}
}
2. CSS's pointer-events attribute
/* YourComponent.js */
class YourComponent extends React.Component {
render() {
return (
<Link className='disabled-link' />
);
}
}
/* css file */
.disable-link {
pointer-events: none;
}
or you can use inline style
/* YourComponent.js */
class YourComponent extends React.Component {
render() {
return (
<Link style={{ pointerEvents: 'none' }} />
);
}
}
What I used was method 2, it's more clearly for me on my project.
Another option is to have a function return 2 different links based on some condition....
const fnSomePath = () =>{
return somecondition ? `www.abc.xyz` : `#`
}
Then call the function where your link is being used:
<ListGroupItem>
<NavLink to={{pathname: fnSomePath()}}>
TEXT
</NavLInk>
</ListGroupItem>
You could conditionally render something that looks like a disabled link based upon some state.
For instance in typescript:
export interface Location {
pathname: string;
search: string;
state: any;
hash: string;
key ?: string;
}
interface LinkProps {
to: string | Location
replace?:boolean
}
interface DisableLinkProps extends LinkProps {
enabled: boolean
linkText:string
}
export class DisableLink extends React.Component<DisableLinkProps, undefined> {
render() {
var element= this.props.enabled ? <span className="disableLinkDisabled">{this.props.linkText}</span> : <Link to={this.props.to} replace={this.props.replace}>{this.props.linkText}</Link>
return element;
}
}
interface DemoClassState {
linkEnabled:boolean
}
export class DemoClass extends React.Component<undefined, DemoClassState> {
constructor(props) {
super(props);
this.state = { linkEnabled:false }
}
toggleLinkEnabled = () => {
this.setState((prevState) => {
return {
linkEnabled: !prevState.linkEnabled
}
});
}
render() {
return <div>
<DisableLink enabled={this.state.linkEnabled} to="/somewhere" linkText="Some link" />
<button onClick={this.toggleLinkEnabled}>Toggle link enabled</button>
</div>
}
}
This is actually a bit tricky. And maybe even somewhat ill-advised.
https://css-tricks.com/how-to-disable-links/
The route (no pun intended) I took was to not render the link.
I composed a new component from react-router's Link.
import React from 'react';
import { Link } from 'react-router-dom';
export default function ToggleableLink(props) {
const { disabled, ...rest } = props;
return disabled ? props.children : <Link {...rest}>{props.children}</Link>;
}
Usage:
<ToggleableLink disabled={!showTheLink}>Foobar</ToggleableLink>

How to get the theme outside styled-components?

I know how to get the theme from components that are created using the styled way:
const StyledView = styled.View`
color: ${({ theme }) => theme.color};
`;
But how to get from normal components or apply it for different properties? Example:
index.js
<ThemeProvider theme={{ color: 'red' }}>
<Main />
</ThemeProvider>
main.js
<View>
<Card aCustomColorProperty={GET COLOR FROM THEME HERE} />
</View>
Notice how the property that needs the theme is not called style
You can use the useTheme hook since v5.0:
import React, { useTheme } from 'styled-components';
export function MyComponent() {
const theme = useTheme();
return <p style={{ color: theme.color }}>Text</p>;
}
You can also use the withTheme higher order component that I contributed a long time ago since v1.2:
import { withTheme } from 'styled-components'
class MyComponent extends React.Component {
render() {
const { theme } = this.props
console.log('Current theme: ', theme);
// ...
}
}
export default withTheme(MyComponent)
original response below (ignore this!)
While there is no official solution, I came up by now:
Create a Higher Order Component that will be responsable to get the current theme and pass as a prop to a component:
import React from 'react';
import { CHANNEL } from 'styled-components/lib/models/ThemeProvider';
export default Component => class extends React.Component {
static contextTypes = {
[CHANNEL]: React.PropTypes.func,
};
state = {
theme: undefined,
};
componentWillMount() {
const subscribe = this.context[CHANNEL];
this.unsubscribe = subscribe(theme => {
this.setState({ theme })
});
}
componentWillUnmount() {
if (typeof this.unsubscribe === 'function') this.unsubscribe();
}
render() {
const { theme } = this.state;
return <Component theme={theme} {...this.props} />
}
}
Then, call it on the component you need to access the theme:
import Themable from './Themable.js'
const Component = ({ theme }) => <Card color={theme.color} />
export default Themable(Component);
You can use useTheme hook
import { useTheme } from 'styled-components';
const ExampleComponent = () => {
const theme = useTheme();
return (
<View>
<Card aCustomColorProperty={theme.color.sampleColor} />
</View>
);
};
Creating a HOC is a good way to tackle theming. Let me share another idea using React's Context.
Context allows you to pass data from a parent node to all it’s children.
Each child may choose to get access to context by defining contextTypes in the component definition.
Let's say App.js is your root.
import themingConfig from 'config/themes';
import i18nConfig from 'config/themes';
import ChildComponent from './ChildComponent';
import AnotherChild from './AnotherChild';
class App extends React.Component {
getChildContext() {
return {
theme: themingConfig,
i18n: i18nConfig, // I am just showing another common use case of context
}
}
render() {
return (
<View>
<ChildComponent />
<AnotherChild myText="hola world" />
</View>
);
}
}
App.childContextTypes = {
theme: React.PropTypes.object,
i18n: React.PropTypes.object
};
export default App;
Now our `ChildComponent.js who wants some theme and i18n strings
class ChildComponent extends React.Component {
render() {
const { i18n, theme } = this.context;
return (
<View style={theme.textBox}>
<Text style={theme.baseText}>
{i18n.someText}
</Text>
</View>
);
}
}
ChildComponent.contextTypes = {
theme: React.PropTypes.object,
i18n: React.PropTypes.object
};
export default ChildComponent;
AnotherChild.js who only wants theme but not i18n. He might be stateless as well:
const AnotherChild = (props, context) {
const { theme } = this.context;
return (<Text style={theme.baseText}>{props.myText}</Text>);
}
AnotherChild.propTypes = {
myText: React.PropTypes.string
};
AnotherChild.contextTypes = {
theme: React.PropTypes.object
};
export default AnotherChild;
To use withTheme in a functional component create a Higher-order-component.
Higher-order-component:
higher-order components, or HOCs, are functions that take a component and output a new component after enhancing it in some manner:
const EnhancedHOCComponent = hoc(OriginalReactComponent)
Sample withTheme in a functional Component
const MyButton = ({theme}) => {
const red = theme.colors.red;
return (<div style={{ color: red}} >how are you</div>)
}`
const Button = withTheme(MyButton);
export default Button;

Carousel built with ReactSwipeableViews does not show items

I am trying to implement this carousel using material-ui and react-swipeable-views.
I have a carousel item that looks like this:
import React, {Component, PropTypes} from 'react'
export default class CarouselItem extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
}
static defaultProps = {
href:'#'
}
constructor(props) {
super(props)
}
render() {
const carouselItemStyle = {
width:'100%',
height:'100%',
minHeight:'400px',
position:'absolute',
top:0,
left:0,
zIndex:-1,
opacity:1,
display:'block'
}
const {prepareStyles} = this.context.muiTheme
const {href,image} = this.props
debugger
return (<a href={href} style={prepareStyles(carouselItemStyle)}>
<img src={image}/>
</a>
)
}
}
I have a Carousel component that looks like this:
import React, {Component, PropTypes} from 'react'
import {v4} from 'node-uuid'
import CarouselItem from './CarouselItem'
import autoPlay from 'react-swipeable-views/lib/autoPlay'
import SwipeableViews from 'react-swipeable-views'
const AutoplaySwipeableViews = autoPlay(SwipeableViews)
export default class Carousel extends Component {
static contextTypes = {
muiTheme: PropTypes.object.isRequired
}
static propTypes = {
items:PropTypes.arrayOf(PropTypes.string),
autoplay:PropTypes.bool
}
static defaultProps = {
autoplay:false
}
constructor(props) {
super(props)
}
render() {
const carousel = {
overflow:'hidden',
position:'relative',
width:'100%',
perspective:'500px',
transformStyle:'preserve-3d',
transformOrigin:'0% 50%'
}
const carouselSlider = {
top:0,
left:0,
height:0
}
const {style:customStyles} = this.props
const style = Object.assign(
carousel,
carouselSlider,
customStyles
)
const {prepareStyles} = this.context.muiTheme
const SwipeImplementation = this.props.autoplay?AutoplaySwipeableViews:SwipeableViews
debugger
const carouselItems = this.props.items.map(function(item){
debugger
return <CarouselItem key={v4()} href="#" image={item}/>
})
return (<div style={prepareStyles(style)}>
<SwipeImplementation>
{carouselItems}
</SwipeImplementation>
</div>
)
}
}
I use the Carousel like this:
const items = [
'http://estruct.com.au/wp-content/uploads/2014/10/old-gccc-logo.png',
'http://www.activehealthycommunities.com.au/wp-content/uploads/2014/07/City-of_Gold-Coast_stacked_CMYK-01.jpg'
]
return (
<Carousel items={items} autoplay={true} />
)
I find that the carousel items do not appear, when I look in the developer tools, I find that transitions are happening but I do not see the items.
I have created a webpackbin with the code
I get an error in the bin that I do not have in my dev environment.
UPDATE:
If I remove the style for the a tag and change it to a div within CarouselItem:
//style={prepareStyles(carouselItemStyle)}
return (<div><img src={image}/></div>)
The images are displayed but are not full width. I notice that the transform css as well as height are determined using jQuery. How can we establish proper styling for the CarouselItem.
I think the problem is with your helloWorld.js. You're not creating the component correctly. Switching it to this is rendering the images for me.
export default class HelloWorld extends React.Component {
render() {
const items = [
'http://estruct.com.au/wp-content/uploads/2014/10/old-gccc-logo.png',
'http://www.activehealthycommunities.com.au/wp-content/uploads/2014/07/City-of_Gold-Coast_stacked_CMYK-01.jpg'
]
return (
<MuiThemeProvider>
<Carousel items={items} autoplay={true}/>
</MuiThemeProvider>
);
}
}

Resources