Using defaultProps with withStyles - javascript

I'm creating a component using material-ui withStylers and defaultProps, but when i try to use the props of the component, in the styles objects, i never get the value of my default props, only get the value if it is passed to the component.
My component structure is something like this:
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core";
const styles = theme => ({
ClassTest: {
anyAttr: props => console.log(props)
}
});
const Test = ({classes}) => {
return (
<span className={classes.ClassTest}/>
);
};
Test.defaultProps = {
val2: "hey"
};
Test.propTypes = {
val1: PropTypes.bool.isRequired,
val2: PropTypes.string,
};
export default withStyles(styles, { withTheme: true })(Test);
And i use like this
<Test val1="Hello">
I expect that the console log show something like this:
{
classes: {...},
val1: "Hello",
val2: "hey"
}
But instead, i got this
{
classes: {...},
val1: "Hello"
}
If I call the component that way:
<Test val1="Hello" val2="hey">
I got this:
{
classes: {...},
val1: "Hello",
val2: "hey"
}
So, the styles object shouldn't give the the value of the props, considering defaultProps? I'm doin it correctly or i miss something?
I'm using the following versions:
"#material-ui/core": "^4.3.0",
"react": "^16.8.6",
And I'm basing on this part of the documentation
https://material-ui.com/styles/basics/#adapting-based-on-props

withStyles is wrapping Test in a higher-order-component and has no visibility to the default props of Test. withStyles is adding to the properties of Test (injecting a classes prop), so the default props for Test can't be determined until after withStyles finishes its work (e.g. if Test had a default prop for classes, it wouldn't be leveraged when withStyles is providing classes, but the default could be leveraged when not wrapped by withStyles).
In the code below, I demonstrate three different approaches:
The first is the approach you tried which doesn't work because the default props aren't visible to withStyles.
The second approach applies the default props to the HOC returned by withStyles.
The third approach uses makeStyles/useStyles instead of withStyles which makes it possible to still have the default props on the initial component rather than the HOC.
All three approaches work when bgcolor is specified explicitly (orange), but only approaches 2 and 3 successfully see the default prop value.
import React from "react";
import ReactDOM from "react-dom";
import { withStyles, makeStyles } from "#material-ui/core/styles";
const styles = {
test: {
backgroundColor: props => props.bgcolor
}
};
const useStyles = makeStyles(styles);
const ApproachThatDoesNotWork = ({ classes }) => {
return <div className={classes.test}>ApproachThatDoesNotWork</div>;
};
ApproachThatDoesNotWork.defaultProps = {
bgcolor: "lightgreen"
};
const StyledApproachThatDoesNotWork = withStyles(styles)(
ApproachThatDoesNotWork
);
const DefaultPropsOnStyledHOC = ({ classes }) => {
return <div className={classes.test}>DefaultPropsOnStyledHOC</div>;
};
const StyledDefaultPropsOnStyledHOC = withStyles(styles)(
DefaultPropsOnStyledHOC
);
StyledDefaultPropsOnStyledHOC.defaultProps = {
bgcolor: "pink"
};
const MakeStylesAndUseStyles = props => {
const classes = useStyles(props);
return <div className={classes.test}>MakeStylesAndUseStyles</div>;
};
MakeStylesAndUseStyles.defaultProps = {
bgcolor: "lightblue"
};
function App() {
return (
<div className="App">
<StyledApproachThatDoesNotWork />
<StyledApproachThatDoesNotWork bgcolor="orange" />
<StyledDefaultPropsOnStyledHOC />
<StyledDefaultPropsOnStyledHOC bgcolor="orange" />
<MakeStylesAndUseStyles />
<MakeStylesAndUseStyles bgcolor="orange" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

Why is the keyword 'default' necessary when exporting a component withStyles

When I implement a simple React component with Mui's withStyles HOC, I have to use the keyword "default" when exporting the component. Why can't I use the HOC in the return statement within the functional component?
Is there something about Js or ReactJs that I'm missing?
Since I am forced to export this component as default, I lose the possibility to use the named import functionality, without using another import/export layer in between.
Below is the current working code:
// Card.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme => ({
card: {
margin: theme.spacing(2)
}
});
function CustomCard(props) {
const {classes} = props;
return (
<Card className={classes.card}>
Export me without being the default component.
</Card>
);
}
export default withStyles(styles)(MediaCard);
// Elsewhere.js
import CustomCard from "Card";
...
But i'd rather write something like this:
// Cards.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme =\> ({
card: {
margin: theme.spacing(2)
},
anotherCard: {
margin: theme.spacing(4)
}
});
export function CustomCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.card}>
Jeah. I'm not the default component.
</Card>
);
}
export function AnotherCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
}
// Elsewhere.js
import { CustomCard, AnotherCard } from "Cards";
...
You can do it the way you want to but you to change the way you define your components. The technical reason is that all exports except default need to be named, otherwise you can't import them and know what's what. Since withStyles() returns a statement and not a named variable/function you can't export it without a name.
export const AnotherCard = withStyles(styles)((props) => {
const {classes} = props;
return (
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
});
The downside of this is of course now your components aren't hoisted.

React Testing Library - Cannot read property 'contents' of undefined] - value from redux

Am new in writing testcases using React Test library.
Here is my component
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
class MyContainer extends React.Component {
static propTypes = {
graphicalData: PropTypes.object.isRequired,
};
render() {
const { graphicalData } = this.props;
return (
graphicalData && (
<div>
/////some action and rendering
</div>
))}
}
const mapStateToProps = (state) => {
return {
graphicalData: state.design.contents ? state.design.contents.graphicalData : {},
};
};
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(MyContainer)));
So i am writing my test case file using React Testing library
import React from 'react';
import '#testing-library/jest-dom';
import { render, cleanup, shallow } from '#testing-library/react';
import MyContainer from '../../MyContainer';
import configureMockStore from 'redux-mock-store';
import ReactDOM from 'react-dom';
const mockStore = configureMockStore();
import { Provider } from 'react-redux';
const store = mockStore({
state: {
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
},
});
afterEach(cleanup);
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<MyContainer />
</Provider>,
div
);
ReactDOM.unmountComponentAtNode(div);
});
Am not sure what went wrong , my idea is to check if the component loads without crashing , also if the cars array length is greater than 0 , check something rendered on page.
But am getting some error, any help with example or suggestion will save my day
The error seems correct. Check the structure that you have passed into the mockStore() function.
It is
state: {
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
},
So, when you access in your MyContainer, you should access it as
state.state.design.contents
It is just that you have an extra hierarchy in the state object.
Or, if you don't want to change the component, change the structure of the state passed into your mockStore() method as:
const store = mockStore({
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
});

Why cant I export and use my custom js styles?

This my main class
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import styles from './FoodStyles';
class Food extends Component {
render () {
return (
<div>
<h2 className="header">Food</h2>
</div>
)
}
}
export default withStyles(styles) (Food);
And this is my style class called FoodStyles.js
const styles = theme => ({
header: {
backgroundColor: 'red'
},
});
export default styles;
They both are in the same folder but still styles cannot be accessed
This could be the solution to your problem: (You need destructuring as done in line 7 for your styles to be used in your file.) With React, which fully embraces the ES6 syntax, destructuring adds a slew of benefits to improving your code.
Food.js:
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import styles from './styles';
class Food extends Component {
render () {
const { classes } = this.props;
return (
<div>
<h2 className={classes.header}>Food</h2>
</div>
)
}
}
export default withStyles(styles)(Food);
styles.js:
const styles = theme => ({
header: {
backgroundColor: 'red'
},
});
export default styles;
Result:
Reasons to destructure:
1. Improves readability:
This is a huge upside in React when you’re passing down props. Once you take the time to destructure your props, you can get rid of props / this.props in front of each prop.
2. Shorter lines of code:
Insead of:
var object = { one: 1, two: 2, three: 3 }
var one = object.one;
var two = object.two;
var three = object.three
console.log(one, two, three) // prints 1, 2, 3
We can write:
let object = { one: 1, two: 2, three: 3 }
let { one, two, three } = object;
console.log(one, two, three) // prints 1, 2, 3
3. Syntactic sugar:
It makes code look nicer, more succinct, and like someone who knows what they’re doing wrote it.
I would just use makeStyles instead of withStyles.
App.jsx
import Food from "./Food";
const App = () => {
return (
<div className="App">
<Food />
</div>
);
};
export default App;
Food.jsx
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import styles from "./FoodStyles";
const useStyles = makeStyles(styles);
const Food = () => {
const classes = useStyles();
return (
<div>
<h2 className={classes.header}>Food</h2>
</div>
);
};
export default Food;
FoodStyles.js
const styles = (theme) => ({
header: {
backgroundColor: "red"
}
});
export default styles;

React 16.3 Context API -- Provider/Consumer issues

I have been doing some experiment on React 16.3.1 ContextAPI. and I encountered into something that I couldn't fathom. I was hoping I could use your help.
Note: The problem have been solved but, its not the solution I am looking for.
Let start with first experiment on multiple components within same file Index.js.
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class AppProvider extends Component {
state = {
name: 'Superman',
age: 100
};
render() {
const increaseAge = () => {
this.setState({ age: this.state.age + 1 });
};
const decreaseAge = () => {
this.setState({ age: this.state.age - 1 });
};
return (
<Provider
value={{
state: this.state,
increaseAge,
decreaseAge
}}
>
{this.props.children}
</Provider>
);
}
}
class Person extends Component {
render() {
return (
<div className="person">
<Consumer>
{context => (
<div>
<p>I'm {context.state.name}</p>
<p>I'm {context.state.age}</p>
<button onClick={context.increaseAge}>
<span>+</span>
</button>
<button onClick={context.decreaseAge}>
<span>-</span>
</button>
</div>
)}
</Consumer>
</div>
);
}
}
class App extends Component {
render() {
return (
<AppProvider>
<div className="App">
<p>Imma Apps</p>
<Person />
</div>
</AppProvider>
);
}
}
export default App;
As result, this render out perfect without any error. I am able to see name (Superman) and age (100). I am able to increase and decrease age by 1.
As you can see, I have imported {createContext} from react then created {Provider, Consumer}. Wrapped <Provider> with state value and <Consumer>.
Next Experiment, was exact copy each component from index.js and paste them separately into their own files.
AppProvider.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class AppProvider extends Component {
state = {
name: 'Superman',
age: 100
};
render() {
const increaseAge = () => {
this.setState({ age: this.state.age + 1 });
};
const decreaseAge = () => {
this.setState({ age: this.state.age - 1 });
};
return (
<Provider
value={{
state: this.state,
increaseAge,
decreaseAge
}}
>
{this.props.children}
</Provider>
);
}
}
export default AppProvider;
Person.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class Person extends Component {
render() {
return (
<div className="person">
<Consumer>
{context => (
<div>
<p>I'm {context.state.name}</p>
<p>I'm {context.state.age}</p>
<button onClick={context.increaseAge}>
<span>+</span>
</button>
<button onClick={context.decreaseAge}>
<span>-</span>
</button>
</div>
)}
</Consumer>
</div>
);
}
}
export default Person;
App.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class App extends Component {
render() {
return (
<AppProvider>
<div className="App">
<p>Imma Apps</p>
<Person />
</div>
</AppProvider>
);
}
}
export default App;
As result, I am getting error - TypeError: Cannot read property 'state' of undefined.
I am unable to grasp what the exactly error was.. All I did was copy and paste each into files without changing any syntax.
Although, Alternative method was to create a new file and add syntax following...
Context.js
import { createContext } from 'react';
const Context = createContext();
export default Context;
Then go into each files (AppProvider.js. Person.js and App.js) and replace...
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();'
...into...
import Context from './Context.js';. Also replace... <Provider> into <Context.Provider> and <Consumer> into <Context.Consumer>.
And this killed the error. However, this is not the solution I am looking for. I wanted to use <Provider> tag instead of <Context.Provider>.
Question is, Why am I getting this error?
Why am I unable to use this method...
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();'
for each components in separate files so I could use <Provider> tag ?
Are there any way around to get the solution I'm looking for?
Your help is appreciated and Thanks in advance.
Your are getting TypeError: Cannot read property 'state' of undefined.
Beacuse every time you call const { Provider, Consumer } = createContext(); it creates a new object, this object need to be exported in order for consumers to consume this specific object.
So in person.js
when you try doing {context.state.age} it really does not have state on this object, you just created a new Context which is empty or rather with React internal methods and properties.
So in order to consume the same object just export it, like you did in Context.js and instead of doing:
import { createContext } from 'react';
const Context = createContext();
export default Context;
replace to:
import { createContext } from 'react';
const { Provider, Consumer } = createContext();
export { Consumer, Provider };
Then when you want to use it in other files ( meaning import it ) just call:
import { Consumer, Provider } from './Context.js';

Redux: Single container, multiple components

I'm quite new to both React and Redux, and I'm unsure about both best practices and technical solution to a case I'm working on. I'm using "component" and "container" as defined by Dan Abramov here.
The component I'm working on is a small collection of filter components: One input text field and two buttons, all filtering a list of entities. I've tried two approaches:
First approach: Single component containing three instances of two types of containers, containers connected to corresponding components.
This was what I first made. Here, the root component looks like the following:
import React, { PropTypes, Component } from 'react';
import Config from '../../config';
import FilterInput from '../containers/FilterInput';
import FilterLink from '../containers/FilterLink'
class FilterController extends Component {
render() {
return (
<div className='filterController'>
<FilterInput displayName="Search" filterName={Config.filters.WITH_TEXT} />
<FilterLink displayName="Today" filterName={Config.filters.IS_TODAY} />
<FilterLink displayName="On TV" filterName={Config.filters.ON_TV} />
</div>
)
}
}
export default FilterController;
The two containers referenced here look pretty much as expected, as do the connected components. I'll show the FilterLink as an example:
import React, { PropTypes, Component } from 'react';
import {connect} from 'react-redux';
import {toggleFilter} from '../actions';
import FilterButton from '../components/filterbutton'
const mapStateToProps = (state, ownProps) => {
return {
active: !!state.program.filters[ownProps.filterName]
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(toggleFilter(ownProps.filterName, ownProps.input))
}
}
}
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(FilterButton)
export default FilterLink
And the corresponding FilterButton component:
import React, { PropTypes, Component } from 'react';
class FilterButton extends Component {
render() {
return (
<button className={this.props.active ? 'active' : ''}
onClick={this.props.onClick}>
{this.props.displayName}
</button>
)
}
}
FilterButton.propTypes = {
active: PropTypes.bool.isRequired,
displayName: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
filterName: PropTypes.string.isRequired
};
export default FilterButton;
This approach works, but I'm thinking that it shouldn't be necessary to create two different containers. Which again lead me to my second attempt.
Second approach: single container containing multiple components.
Here, I made a larger container:
import React, { PropTypes, Component } from 'react';
import Config from '../../config';
import {connect} from 'react-redux';
import {toggleFilter} from '../actions';
import FilterButton from '../components/filterbutton'
import FilterInput from '../components/filterinput'
class FilterContainer extends Component {
render() {
const { active, currentInput, onChange, onClick } = this.props;
return (
<div className='filterController'>
<FilterInput displayName="Search" filterName={Config.filters.WITH_TEXT} currentInput={currentInput} onChange={onChange} />
<FilterButton displayName="Today" filterName={Config.filters.IS_TODAY} active={active} onClick={onClick}/>
<FilterButton displayName="On TV" filterName={Config.filters.ON_TV} active={active} onClick={onClick}/>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
active: !!state.program.filters[ownProps.filterName],
currentInput: state.program.filters[ownProps.filterName] ? state.program.filters[ownProps.filterName].input : ''
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(toggleFilter(ownProps.filterName, ownProps.input))
},
onChange: newValue => {
dispatch(toggleFilter(ownProps.filterName, newValue.target.value))
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(FilterContainer);
Here, all state interaction for the entire component is gathered in a single compoment. The components are the same here as in the first approach. This doesn't, however, really work: ownProps is empty in both mapStateToProps and mapDispathToProps. I may have misunderstood how the react-redux connection works.
So, given these things I have two questions: What's the best way to structure this component, in terms of containers and components? And secondly, why won't ownProps work similarily in the second approach as in the first?
Thank you for your time.
Not sure I have a specific answer regarding structure at the moment. As for "ownProps", that represents props that are specifically being passed in to a given component by its parent. Since you're connect()-ing FilterController, that means that "ownProps" would be coming from wherever you render that component, like: return <FilterController prop1="a" prop2={someVariable} />.
Based on how you have those map functions written, it looks like you really want to be connecting the FilterInput and FilterButton components rather than FilterController.

Categories