React defaultProps not working as expected - javascript

By default I want my lastMessage prop to be an object with an id. But with the code below it comes up as null.
Code:
const App = ({ lastMessage }) => { // lastMessage is currently null
return (
<Message lastMessage={lastMessage} />
)
}
class Message extends Component {
constructor(props) {
super(props)
}
componentWillReceiveProps(nextProps) {
const data = [];
if (nextProps.lastMessage.id !== this.props.lastMessage.id) {
log.debug('woohoo');
// do something ...
}
}
render() {
return (
// something...
);
}
}
Message.defaultProps = {
lastMessage: {
id: 0,
},
};
In my App component I pass lastMessage as null to Message component. But I set defaultProps in Message component :
Message.defaultProps = {
lastMessage: {
id: 0,
},
I expect lastMessage to be an object with an id, however it is still null. Then my app crashes because nextProps.lastMessage.id doesn't work if lastMessage is null
Even stranger if I remove the lastMessage prop in my App component:
const App = () => {
return (
<Message />
)
}
Then the Message component creates the lastMessage defaultProp as an object with an id.
If my prop is null I am trying to pass it with a default value. I tried something like this but it still does not work:
const App = ({ lastMessage = { id: 0 } }) => { // lastMessage is still null
return (
<Message lastMessage={lastMessage} />
)
}
Am I not setting the defaultProps correctly?

If you pass null to Message component you basically overwrite default property - lastMessage. If I understood you correctly then it works when you remove passing lastMessage to Message component. It should stay this way.
You can do ternary truthiness check before passing argument to component:
<Message lastMessage={lastMessage ? lastMessage : undefined} />
As you've also figured out you can also pass default props this way:
<Message lastMessage={lastMessage ? lastMessage : { id: 0 }} />

Related

How to redirect to another page if prop is not defined in react

I have a component that receives props and displays the received props. So I decided to load this page url without receiving a prop and I get this error:
TypeError: Cannot destructure property 'myImage' of '(intermediate value)(intermediate value)(intermediate value)' as it is undefined.
So I tried to redirect the page if the props is not received (if it is undefined) with the following lines of code:
componentDidMount(){
if(this.props.location.myInfo === undefined){
this.props.history.push('/');
return;
}
this.setState({ name: this.props.location.myInfo });
}
render(){
const { myImage, myName, myJobType, myPrice } = this.props.location.myInfo ? this.props.location.myInfo : this.props.history.push('/');
return(
<div>
...
</div>
)
}
But none of these works. Been on it for hours trying different methods but none is working, how can I get if fixed?
You need more than one line to do this:
Destructure this.props.location.myInfo and add optional value {}.
Call this.props.history.push if this.props.location.myInfo is undefined.
render(){
const { myImage, myName, myJobType, myPrice } = this.props.location.myInfo || {};
if(!this.props.location.myInfo) {
this.props.history.push('/')
}
return(
<div>
//...
</div>
)
}
or
componentDidMount() {
if(!this.props.location.myInfo) {
this.props.history.push('/')
}
}
render(){
const { myImage, myName, myJobType, myPrice } = this.props.location.myInfo || {};
return
(
<div>
//...
</div>
)
}

In React Context, how can I use state variables in state functions?

I have a React Context which looks like this:
import React, { Component } from 'react'
const AlertsContext = React.createContext({
categoryList: [],
setCategoryList: () => {}
})
export class AlertsProvider extends Component {
state = {
categoryList: [],
setCategoryList: categoryString => (
this.categoryList.includes(categoryString)
? this.setState({ categoryList: this.categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: this.categoryList.concat([categoryString]) })
)
}
render() {
const { children } = this.props
const {categoryList, setCategoryList } = this.state
return (
<AlertsContext.Provider value={{categoryList, setCategoryList}}>
{children}
</AlertsContext.Provider>
)
}
}
export const AlertsConsumer = AlertsContext.Consumer
So, categoryList is an array of strings, each representing a category. setCategoryList should take a string; if that string is already in the array, it removes it, and if it's not in the array it adds it.
In one of my components the user can select categories from a list of checkboxes. When a checkbox is clicked, the AlertsContext setCategoryList should be called with the value of the clicked box:
import React, { Component } from 'react'
import { AlertsConsumer } from '../../../context/alerts-context'
class AlertFilters extends Component {
constructor(props) {
super(props)
this.state = {
categories: props.categories
}
}
render() {
const { categories } = this.state
return (
<AlertsConsumer>
{({ categoryList, setCategoryList }) => (
<>
{
categories.map(category => (
return (
<div key={category.id}>
<Checkbox id={category.id} value={category.value} onChange={e => setCategoryList(e.target.value)} checked={categoryList.includes(category.value)} />
<label htmlFor={category.id}>{category.value}</label>
</div>
)
))
}
</>
)}
</AlertsConsumer>
)
}
}
export default AlertFilters
This compiles ok, but when I run it and click a checkbox I get the following error:
alerts-context.jsx:77 Uncaught TypeError: Cannot read property 'includes' of undefined
This is in the line:
this.categoryList.includes(categoryString)
in the Context Provider, suggesting that "this.categoryList" is undefined at this point.
I tried changing it to
this.state.categoryList.includes(categoryString)
but it said I had to use state destructuring, so I changed to:
setCategoryList: (categoryString) => {
const { categoryList } = this.state
categoryList.includes(categoryString)
? this.setState({ categoryList: categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: categoryList.concat([categoryString]) })
}
which highlighted the ternary operator and gave the following lint error:
Expected an assignment or function call and instead saw an expression.
What am I doing wrong?
Use if/else syntax to update the state.
setCategoryList: categoryString => {
const { categoryList } = this.state;
if (categoryList.includes(categoryString)) {
this.setState({
categoryList: categoryList.filter(value => value !== categoryString)
});
} else {
this.setState({ categoryList: categoryList.concat([categoryString]) });
}
};

TypeError: Cannot read property 'bio' of undefined

so I've been trying to implement an edit form with ReactJs and I'm stuck with this error "TypeError: Cannot read property 'bio' of undefined "
I did hours of research and they suggested adding a constructor but even that couldn't fix it
Here is a part of the code of my EditForm.js :
const styles = (theme) => ({
...theme.spreadThis,
button: {
float: 'right'
}
});
class EditForm extends Component {
state = {
bio: '',
website: '',
location: '',
open: false
};
constructor(props){
super(props);
this.setState = this.setState.bind(this);
}
mapUserDetailsToState = (credentials) => {
this.setState({ //here is the problem
bio: credentials.bio ? credentials.bio : '',
website: credentials.website ? credentials.website : '',
location: credentials.location ? credentials.location : ''
});
};
componentDidMount() {
const { credentials } = this.props;
this.mapUserDetailsToState(credentials);
}
render() {
const { classes } = this.props;
return (
<Fragment>
<MyButton
tip="Edit Details"
onClick={this.handleOpen}
btnClassName={classes.button}
>
<EditIcon color="primary" />
</MyButton>
<Dialog
</Fragment>
);
}
}
EditForm.propTypes = {
editUserDetails: PropTypes.func.isRequired,
classes: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
credentials: state.user.credentials
});
export default (withStyles(styles)(EditForm));
Anyone knows how to fix it ?
edit : here is a screenshot of the error
error
Looks like credentials object from props is undefined. Hence the error undefined.bio (cannot read property bio of undefined).
Make sure credentials is availabe before accessing any properties of it.
if (credentials !== undefined && credentials !== null) {
// do your stuff
}
your state should be inside constructor and below super
constructor(props) {
super(props);
this.state = {
bio: '',
website: '',
location: '',
open: false
};
this.setState = this.setState.bind(this);
}
and you first need to check credentials is present and then you have to see whether credentials.bio present, because checking undefined.bio does not make sense.

React Context API - get updated state value

I am experimenting with React context api,
Please check someComponent function where I am passing click event (updateName function) then state.name value update from GlobalProvider function
after updated state.name it will reflect on browser but not getting updated value in console ( I have called console below the line of click function to get updated value below )
Why not getting updated value in that console, but it is getting inside render (on browser) ?
Example code
App function
<GlobalProvider>
<Router>
<ReactRouter />
</Router>
</GlobalProvider>
=== 2
class GlobalProvider extends React.Component {
state = {
name: "Batman"
};
render() {
return (
<globalContext.Provider
value={{
name: this.state.name,
clickme: () => { this.setState({ name: "Batman 2 " }) }
}}
>
{this.props.children}
</globalContext.Provider>
);
}
}
export default GlobalProvider;
=== 3
const SomeComponent = () => {
const globalValue = useContext(globalContext);
const updateName = ()=> {
globalValue.clickme();
console.log(globalValue.name ) //*** Here is my concern - not getting updated value here but , getting updated value in browser
}
return (
<div onClick={(e)=> updateName(e) }>
{globalValue.name}//*** In initial load display - Batman, after click it display Batman 2
</div>) }
React state isn't an observer like Vue or Angular states which means you can't get updated values exactly right after changing them.
If you want to get the updated value after changing them you can follow this solution:
class A extends Component {
state = {
name: "Test"
}
updateName = () => {
this.setState({name: "Test 2"}, () => {
console.log(this.state.name) // here, name has been updated and will return Test 2
})
}
}
So, you need to write a callback function for the clickme and call it as below:
class GlobalProvider extends React.Component {
state = {
name: "Batman"
};
render() {
return (
<globalContext.Provider
value={{
name: this.state.name,
clickme: (callback) => { this.setState({ name: "Batman 2 " }, () => callback(this.state.name)) }
}}
>
{this.props.children}
</globalContext.Provider>
);
}
}
export default GlobalProvider;
And for using:
const SomeComponent = () => {
const globalValue = useContext(globalContext);
const updateName = ()=> {
globalValue.clickme((name) => {
console.log(name) // Batman 2
});
}
return (
<div onClick={(e)=> updateName(e) }>
{globalValue.name}//*** In initial load display - Batman, after click it display Batman 2
</div>)
}

Pass this.state not working ReactJS

I have a table that gets data and dynamically displays it,
Passing the "example" the table shows the data without problems,
but when i try to pass
< ResponseTable data={this.state.sprints} ,
which is also contains the same structure as example ( Array of objects )
it doesn't work
var example = [
{id: 1, sequence: 2, name: "Sprint 2018", state: "CLOSED", linkedPagesCount: 0},
{id: 2, sequence: 2, name: "Sprint 2018-1", state: "OPEN", linkedPagesCount: 0}
];
class Table extends React.Component{
constructor(props){
super(props);
this.state = {}
}
fetchData() {
fetch('http://localhost:8000/sprints/23')
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState(myJson));
}
componentDidMount(){
this.fetchData();
}
render(){
console.log(this.state.sprints)
console.log(example)
return(
<div>
<ResponseTable data={example} />
</div>
);
}
}
TypeError: Cannot read property '0' of undefined
const props = Object.keys(data[0]);
columnsBuilder (data) {
> 12 | const props = Object.keys(data[0]);
13 | const columns = props.map( (item, index) => ({
14 | Header : item,
15 | accessor : item,
ReactTable ->
export default class ResponseTable extends React.Component {
constructor() {
super();
this.columnsBuilder = this.columnsBuilder.bind(this);
}
columnsBuilder (data) {
const props = Object.keys(data[0]);
const columns = props.map( (item, index) => ({
Header : item,
accessor : item,
Cell : propss => propss.original[item].length === 0 ? '[]' : propss.original[item].toString(),
}));
const built = [
{
Header : 'Response',
columns,
},
];
return built;
}
render() {
return (
<div>
<ReactTable
data={this.props.data}
columns={this.columnsBuilder(this.props.data)}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
);
}
}
You are making your API call on componentDidMount and sending the data to your component on render.
But render comes before componentDidMount in the component lifecycle.
That's why you need to verify if you have the data before passing it down. Like this:
...
render() {
return this.state.sprints ? (
<div>
<ResponseTable data={example} />
</div>
) : (
<div>
Loading ...
</div>
);
}
When you have this.state.sprints loaded, your component will update and a re-render will be triggered, showing the table properly.
export default class ResponseTable extends React.Component {
constructor(props) { // Use Props
super(props); // Use Props
this.columnsBuilder = this.columnsBuilder.bind(this);
}
columnsBuilder () { //Remove data
const props = Object.keys(this.props.data[0]); //Use Props
const columns = props.map( (item, index) => ({
Header : item,
accessor : item,
Cell : propss => propss.original[item].length === 0 ? '[]' : propss.original[item].toString(),
}));
const built = [
{
Header : 'Response',
columns,
},
];
return built;
}
render() {
return (
<div>
<ReactTable
data={this.props.data}
columns={this.columnsBuilder()} // Remove Props
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
);
}
}
Please Do check propss use have used for Cell.
That's it easy!!!
You'll need to initialize the sprints state with a value if you want to use it in the render method, otherwise it's looking for a key that doesn't exist.
Your constructor could look something like this:
constructor(props){
super(props);
this.state = {
sprints: {}
};
}
Try set property name:
setState({sprints: myJson})
as it is missing in this line:
.then((myJson) => this.setState(myJson));
You are overriding the global state with
.then((myJson) => this.setState(myJson)) basicaly you are overriding this.state with new object which is not allowed you always need to only extend this.state as you are reffering in your code to this.state.sprints correct call would be
.then((sprints) => this.setState({sprints))
which leverages of using destructuring of objects so it will be same as {sprints: sprints React will then merge this.state (which is {} at that time from constructor) and merge it with {sprints: sprints} which will result to the correct state
this.state = {
sprints: data
}
also your code is asynchronous you need to add check inside render it can be easily done with returning null
if (!this.state.sprints) return null
which will guard the render method from errors
The issue that until your request is done - your state is empty thus there is no sprints property.
You should add it to your constructor in Table component.
Table component.
constructor(props){
super(props);
this.state = {
sprints: []
}
}
Also, you should check if there is items inside of your data in columnsBuilder method cause it can also produce an error.
ResponseTable component.
columnsBuilder (data) {
const props = data.length ? Object.keys(data[0]) : data;
const columns = props.map( (item, index) => ({
Header : item,
accessor : item,
Cell : propss => propss.original[item].length === 0 ? '[]' : propss.original[item].toString(),
}));
const built = [
{
Header : 'Response',
columns,
},
];
return built;
}
Another approach is to check in render method if sprints is exists in the state and use loader as mentioned Tiago Alves.

Categories

Resources