I'm getting a Reference Error
Cannot access 'STRUCTURE_COLUMN_ID' before initialization
I have a structureColumn file which contains the following:
import React from 'react';
import Column from './column';
export const STRUCTURE_COLUMN_ID = 'Structure';
export default class StructureColumn extends Column {
constructor(name) {
super(STRUCTURE_COLUMN_ID);
}
clone() {
return new StructureColumn(this.name);
}
getKey() {
return STRUCTURE_COLUMN_ID;
}
}
When trying to access StructureColumn class or STRUCTURE_COLUMN_ID variable from a component I get the mentioned error.
The component looks the following:
import React from 'react';
import { List, ListItem, Tooltip } from '#material-ui/core';
import { STRUCTURE_COLUMN_ID } from '../../../../models/structureColumn';
console.log(STRUCTURE_COLUMN_ID);
const CustomColumnsList = ({ onSelect }) => {
return (
<List>
{Object.values({}).map(col => (
<Tooltip title={col.description}>
<ListItem onClick={() => onSelect(col.column)} button>
{col.name}
</ListItem>
</Tooltip>
))}
</List>
);
};
export default CustomColumnsList;
I can use the variable inside the functional component body but the thing is I wanted to create a constant variable out of it's scope. Never seen this issue before in React. Someone has an experience dealing with it?
Related
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.
I'm fairly new to react native and redux and was trying to render the library title from a JSON file in a flat list using redux, but my FlatList component does not render anything on the screen.
here's my code :
LibraryList.js
import React, { Component } from "react";
import { FlatList } from "react-native";
import { connect } from "react-redux";
import ListItem from "./ListItem";
class LibraryList extends Component {
renderItem(library) {
return <ListItem library={library} />;
}
render() {
return (
<FlatList
data={this.props.libraries}
renderItem={this.renderItem}
keyExtractor={library => library.id}
/>
);
}
}
const mapStateToProps = state => {
return { libraries: state.libraries };
};
export default connect(mapStateToProps)(LibraryList);
ListItem.js
import React, { Component } from "react";
import { Text } from "react-native";
import { CardSection } from "./common";
class ListItem extends Component {
render() {
return (
<CardSection>
<Text>{this.props.library.title}</Text>
</CardSection>
);
}
}
export default ListItem;
App.js
import React from "react";
import { View } from "react-native";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from "./reducers";
import { Header } from "./components/common";
import LibraryList from "./components/LibraryList";
const App = () => {
return (
<Provider store={createStore(reducers)}>
<View>
<Header headerText="Tech Stack" />
<LibraryList />
</View>
</Provider>
);
};
export default App;
The JSON file is like
[
{
"id": '' ,
"title": '' ,
"description":''
},
{
"id":'' ,
"title":'' ,
"description":''
}
]
I read some solutions for this suggesting changing the renderItem function to something like this
renderItem = ({ library }) => <ListItem library={library} />
still does not work. Can someone help me with this problem?
Thanks.
You have to make your renderItem as an arrow function. Otherwise you have to bind your function inside constructor in order to access function as renderItem={this.renderItem}.
import React, { Component } from 'react';
import { FlatList } from 'react-native';
import { connect } from 'react-redux';
import ListItem from './ListItem';
class LibraryList extends Component {
renderItem = ({ item }) => {
return <ListItem library={item} />
}
render() {
return (
<FlatList
data={this.props.libraries}
renderItem={this.renderItem}
keyExtractor={library => library.id}
/>
);
}
}
const mapStateToProps = state => {
return { libraries: state.libraries };
};
export default connect(mapStateToProps)(LibraryList);
or you can call your renderItem as an arrow function inside render like below
renderItem={(item) => this.renderItem(item)}
but using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison.
Hope this helps you. Feel free for doubts.
In your flatlist try thi s:
<FlatList
data={this.props.libraries}
renderItem={({item, index}) => {
this.renderItems(item); // change this name to renderItems so that it doesnt clash with flatlist default renderItem
}}
/>
Hope it helps. feel free for doubts
You have several approaches to your problem.
Firstly your renderItem should be binded, so either do this
renderItem = (library) => {
or this
renderItem={this.renderItem.bind(this)}
besides the binding problem, flatlist prop renderItem will return to your function an object with this structure
{ item, index }
so in reality your renderItem should be like this
renderItem({ item }){
return <ListItem library={item} />;
}
I have found a code that solved my problem in Next JS re rendering when changing pages. But now i need to send props to the children component. I got no idea how i can make it works here, this is my layout.js code. As you can see i can send props to Header component but for children i dont know how, because it is a variable and not a component.
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{children}
<Footer />
</Fragment>
);
}
Thank you for the help
Have you considered using React's Context API? The idea is that when using the Context API your component's state get's lifted, to be managed at a global scale. If a component needs a prop, instead of passing props down manually (prop drilling) you can simply wrap you component in what's known as a context provider. This will allow that Component to access the global state of your application. This is good because, when your application gets bigger, you may need to pass props down through many components which can clutter and add unneeded confusion.
React provides some great documentation to set your React application up to use the Context API. Highly recommend checking it out!
https://reactjs.org/docs/context.html
Try this
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
function recursiveMap(children, fn) {
return React.Children.map(children, child => {
if (!React.isValidElement(child) || typeof child.type == 'string') {
return child;
}
if (child.props.children) {
child = React.cloneElement(child, {
children: recursiveMap(child.props.children, fn)
});
}
return fn(child);
});
}
// Add props to all child elements.
const childrenWithProps = recursiveMap(children, child => {
// Checking isValidElement is the safe way and avoids a TS error too.
if (isValidElement(child)) {
// Pass additional props here
return cloneElement(child, { currentUser: { ...user } })
}
return child;
});
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{childrenWithProps}
<Footer />
</Fragment>
);
}
You can use React's cloneElement to achieve that.
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
Complete example in your case:
import Header from "../components/header";
import Footer from "../components/footer";
import React, { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
}
<Footer />
</Fragment>
);
}
From the answer of Lucas Raza, below is an example that uses Context API to apply themes to different components
1.Create a context File
//ThemeContex.js
import { createContext, useState } from "react";
export const ThemeContext = createContext();
export const withThemeContext = Component => {
const WrappedComp = props => {
const [darkColor,lightColor] = ["#3b3b3b", "#ddd"]
const [lightBackgoround,darkBackgoround] = ["#ececec","#1d2a35"]
const darkTheme = {
backgroundColor: darkBackgoround,
color:lightColor,
}
const lightTheme = {
backgroundColor: lightBackgoround,
color:darkColor,
}
const themes = {
darkTheme, lightTheme
}
const [theme, setTheme] = useState(lightTheme)
const children ={
theme,
themes,
setTheme,
}
return(
<StylesContext.Provider value={{...children}} >
<Component {...props} />
</StylesContext.Provider>
)
}
return WrappedComp;
}
In _app.js, import withThemeContext higher component and wrap MyApp with it when exporting it.
import { withThemeContext } from '../components'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default withThemeContext(MyApp)
You can know use theme any where in a component
import { useContext } from 'react'
import {ThemeContext} from '../components'
export default function Home() {
const { theme } =useContext(ThemeContext)
return (
<div id="home" style={theme}>
// Home logic...
</div>
)
}
I was given below task in an interview, here the task is about getting a response from API using ajax call on button click and display it on a page.
I have a top component inside App.js, with two child components as MyButton.js and MyPage.js and the service code in MyAPI.js
Below are the file contents:
App.js
import React, { Component } from 'react';
import MyAPI from './services/MyAPI';
import MyButton from './components/MyButton';
import MyPage from './components/MyPage';
class App extends Component {
constructor() {
super();
this.state= {
'apiResponse': ''
};
}
handleButtonClick = () => {
MyAPI.getAPIResponse().then((res) => {
res => this.setState({ apiResponse })
});
}
render() {
return (
<div>
<center><MyButton onClickButton={this.handleButtonClick}></MyButton></center>
<MyPage apiResponse={this.props.apiResponse}></MyPage>
</div>
);
}
}
export default App;
MyButton.js
import React from 'react';
import PropTypes from 'prop-types';
import Button from '#material-ui/core/Button';
const MyButton = (() => (
<div className="button-container">
<MyButton variant="extendedFab" color="primary"
onClick={this.props.onClickButton}>
Call API
</MyButton>
</div>
));
MyButton.propTypes = {
onClickButton: PropTypes.func
}
export default MyButton;
MyPage.js
import React from 'react';
import PropTypes from 'prop-types';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import Paper from '#material-ui/core/Paper';
const MyPage = (() => (
<Paper className="container">
<List>
<ListItem>
<ListItemText>Name: {this.props.apiResponse.split(" ")[0]}</ListItemText>
</ListItem>
</List>
</Paper>
));
MyPage.propTypes = {
apiResponse: PropTypes.string
}
export default MyPage;
MyAPI.js
import axios from 'axios';
export default {
getAPIResponse() {
return axios.get("--url to get user name and age as json--").then(response => {
return response.data;
});
}
};
Here the JSON data contains the name of a sample user just for demo purpose eg: John Doe. I need to display only John on my page as per the given task.
When I run this application I am getting errors at my MyButton.js and MyPage.js in logs.
In MyButton.js the error is at line onClick={this.props.onClickButton}, it says cannot access props on undefined. If I change it to onClick={this.onClickButton}, I got an error as, cannot access onClickButton on undefined. What is the correct way to do this here, please help.
Also same applies to MyPage.js at line {this.props.apiResponse.split(" ")[0], also is it the right way to use the split method here to get the first name from John Doe?
Your MyButtn and MyPage both are functional components. To access the props you do not need to use this. props are taken as params in case of functional components.
MyButton
const MyButton = ((props) => (
<div className="button-container">
<MyButton variant="extendedFab" color="primary"
onClick={props.onClickButton}>
Call API
</MyButton>
</div>
));
MyPage
const MyPage = ((props) => (
<Paper className="container">
<List>
<ListItem>
<ListItemText>Name: {props.apiResponse.split(" ")[0]}</ListItemText>
</ListItem>
</List>
</Paper>
));
once the response success you have to store in the variable
var a = "jhon doe";
var data = a.split(" ");
data[0];
you can do this in a parent component.
I hope, that you can help me with my problem. I can't understand, why property rightIconButton in my example of <ListItem> doesn't work (Material UI component).
<List>
import React from 'react';
import { List } from 'material-ui';
import UsersItem from './UsersItem';
class UsersList extends React.Component {
render() {
return <List>
{this.props.users.map(user => {
return <UsersItem key={user.id} deleteUser={this.props.deleteUser}
user={user}/>
})}
</List>;
}
}
export default UsersList;
<ListItem>
import React from 'react';
import { ListItem, IconButton, Avatar, FontIcon } from 'material-ui';
class UsersItem extends React.Component {
constructor() {
super();
this.deleteUser = this.deleteUser.bind(this);
}
deleteUser(e) {
this.props.deleteUser(this.props.user.id);
e.stopPropagation();
}
render() {
let deleteButton = <IconButton iconClassName="icomoon-icon-delete"
onClick={this.deleteUser}>
</IconButton>;
return <ListItem className="user-item" secondaryText={this.props.user.description}
leftAvatar={<Avatar src="./web/images/avatar.jpg" />}
rightIconButton={deleteButton} secondaryTextLines={2}>
tt { this.props.user.name }
</ListItem>
}
}
export default UsersItem;
Font icon is correct and if I place it on <Toolbar>, it render correct. But it doesn't render in property rightIconButton. I tried SVG icon too from source code on official site of MaterialUI.
Screenshot of result site page (to small reputation for third link) https://github.com/jestonedev/Auction/blob/master/issue.png
What I doing incorrect? Why rightIconButton doesn't work?
In deleteButton > onClick, you are missing to bind this context:
onClick={this.deleteUser.bind(this)}
This is necessary when using the es6 class syntax.