I have a fairly simple stateful component whose value I wish to toggle whenever someone clicks on a button:
class Layout extends Component {
state = {
sidedrawer: false
}
sideDrawerCloseHandler = () => {
this.setState({sidedrawer: false})
}
sideDrawerTogglerHandler = () => {
this.setState({sidedrawer: !sidedrawer})
}
render () {
return (
<Aux>
<Toolbar sideDrawerTogglerHandler={this.sideDrawerTogglerHandler}/>
<SideDrawer SideDrawerOpen={this.state.sidedrawer} sideDrawerCloseHandler={this.sideDrawerCloseHandler} />
<main className={classes.co}>
{this.props.children}
</main>
</Aux>
)
}
}
export default Layout;
Now, when I run my react App it throws an error saying:
sidedrawer is not defined in line 19
Can someone help me in fixing and understanding the error?
The line 18 - 19 happens to be this part in my code
sideDrawerTogglerHandler = () => {
this.setState({sidedrawer: !sidedrawer})
}
You have to get sidedrawer from your state:
sideDrawerTogglerHandler = () => {
this.setState((previousState) => {
return {sidedrawer: !previousState.sidedrawer}
})
}
Side drawer isn't defined, what is defined is this.state.sidedrawer, what you are seeking is
sideDrawerTogglerHandler = () => {
this.setState({sidedrawer: !this.state.sidedrawer})
}
But this isn't the best way to setState, as explained on React docs, the best way to update state based on the previous state is using setState with a function that receives the prevState as a prop, you can try this instead
sideDrawerTogglerHandler = () => {
this.setState((prevState) => ({
sidedrawer: !prevState.sidedrawer
}));
}
Related
My UI was working fine until it was using a class component. Now I am refactoring it to a functional component.
I have to load my UI based on the data I receive from an API handler. My UI will reflect the state of the camera which is present inside a room. Every time the camera is turned on or off from the room, I should receive the new state from the API apiToGetCameraState.
I want the console.log present inside the registerVideoStateUpdateHandlerWrapper to print both on UI load for the first time and also to load every time the video state is changed in the room. However, it doesn't work when the UI is loaded for the first time.
This is how my component looks like:
const Home: React.FunctionComponent<{}> = React.memo(() => {
const [video, setToggleVideo] = React.useState(true);
const registerVideoStateUpdateHandlerWrapper = React.useCallback(() => {
apiToGetCameraState(
(videoState: boolean) => {
// this log does not show up when the UI is loaded for the first time
console.log(
`Video value before updating the state: ${video} and new state is: ${videoState} `
);
setToggleVideo(videoState);
}
);
}, [video]);
React.useEffect(() => {
//this is getting called when the app loads
alert(`Inside use effect for Home component`);
registerVideoStateUpdateHandlerWrapper ();
}, [registerVideoStateUpdateHandlerWrapper ]);
return (
<Grid>
<Camera
isVideoOn={video}
/>
</Grid>
);
});
This was working fine when my code was in class component. This is how the class component looked like.
class Home extends Component {
registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
console.log(`ToggleVideo value before updating the state: ${this.state.toggleCamera} and new state is: ${videoState}`);
this.setStateWrapper(videoState.toString());
})
}
setStateWrapper = (toggleCameraUpdated) => {
console.log("Inside setStateWrapper with toggleCameraUpdated:" + toggleCameraUpdated);
this.setState({
toggleCamera: (toggleCameraUpdated === "true" ) ? "on" : "off",
});
}
constructor(props) {
super(props);
this.state = {
toggleCamera: false,
};
}
componentDidMount() {
console.log(`Inside componentDidMount with toggleCamera: ${this.state.toggleCamera}`)
this.registerVideoStateUpdateHandlerWrapper ();
}
render() {
return (
<div>
<Grid>
<Camera isVideoOn={this.state.toggleCamera} />
</Grid>
);
}
}
What all did I try?
I tried removing the useCallback in the registerVideoStateUpdateHandlerWrapper function and also the dependency array from React.useEffect and registerVideoStateUpdateHandlerWrapper. It behaved the same
I tried updating the React.useEffect to have the code of registerVideoStateUpdateHandlerWrapper in it but still no success.
Move registerVideoStateUpdateHandlerWrapper() inside the useEffect() callback like this. If you want to log the previous state when the state changes, you should use a functional update to avoid capturing the previous state through the closure:
const Home = () => {
const [video, setVideo] = useState(false);
useEffect(() => {
console.log('Inside useEffect (componentDidMount)');
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo((prevVideo) => {
console.log(`Video value before updating the state: ${prevVideo} and new state is: ${videoState}`);
return videoState;
});
});
};
registerVideoStateUpdateHandlerWrapper();
}, []);
return (
<Grid>
<Camera isVideoOn={video} />
</Grid>
);
};
When you no longer actually need to log the previous state, you should simplify registerVideoStateUpdateHandlerWrapper() to:
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo(videoState);
});
};
import React from 'react'
const Home = () => {
const [video, setVideo] = useState(null);
//default video is null, when first load video will change to boolean, when the Camera component will rerender
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo(videoState);
});
};
useEffect(() => {
registerVideoStateUpdateHandlerWrapper();
}, []);
return (
<Grid>
<Camera isVideoOn={video} />
</Grid>
);
};
export default Home
componentDidMount() === useEffect()
'useEffect' => import from 'react'
// componentDidMount()
useEffect(() => {
// Implement your code here
}, [])
// componentDidUpdate()
useEffect(() => {
// Implement your code here
}, [ update based on the props, state in here if you mention ])
e.g:
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
// Implement the code here
}, [ loggedIn ]);
the above code will act as equivalent to the componentDidUpdate based on 'loggedIn' state
Below is the HOC and it is connected to redux store too. The WrappedComponent function is not fetching the redux state on change of storedata. What could be wrong here?
export function withCreateHOC<ChildProps>(
ChildComponent: ComponentType,
options: WithCreateButtonHOCOptions = {
title: 'Create',
},
) {
function WrappedComponent(props: any) {
const { createComponent, title } = options;
const [isOpen, setisOpen] = useState(false);
function onCreateClick() {
setisOpen(!isOpen);
Util.prevDefault(() => setisOpen(isOpen));
}
return (
<div>
<ChildComponent {...props} />
<div>
<Component.Button
key={'add'}
big={true}
round={true}
primary={true}
onClick={Util.prevDefault(onCreateClick)}
className={'float-right'}
tooltip={title}
>
<Component.Icon material={'add'} />
</Component.Button>
</div>
<OpenDrawerWithClose
open={isOpen}
title={title}
setisOpen={setisOpen}
createComponent={createComponent}
/>
</div>
);
}
function mapStateToProps(state: any) {
console.log('HOC mapStateToProps isOpen', state.isOpen);
return {
isOpen: state.isOpen,
};
}
// Redux connected;
return connect(mapStateToProps, {})(WrappedComponent);
}
Expecting isOpen to be used from ReduxStore and update the same with WrappedComponent here. By any chance this should be changed to class component?
The above HOC is used as:
export const Page = withCreateHOC(
PageItems,
{
createComponent: <SomeOtherComponent />,
title: 'Create',
},
);
Overview
You don't want isOpen to be a local state in WrappedComponent. The whole point of this HOC is to access isOpen from your redux store. Note that nowhere in this code are you changing the value of your redux state. You want to ditch the local state, access isOpen from redux, and dispatch an action to change isOpen in redux.
Additionally we've got to replace some of those anys with actual types!
It seems a little suspect to me that you are passing a resolved JSX element rather than a callable component as createComponent (<SomeOtherComponent /> vs SomeOtherComponent), but whether that is correct or a mistake depends on what's in your OpenDrawerWithClose component. I'm going to assume it's correct as written here.
There's nothing technically wrong with using connect, but it feels kinda weird to use an HOC inside of an HOC so I am going to use the hooks useSelector and useDispatch instead.
Step By Step
We want to create a function that takes a component ComponentType<ChildProps> and some options WithCreateButtonHOCOptions. You are providing a default value for options.title so we can make it optional. Is options.createComponent optional or required?
interface WithCreateButtonHOCOptions {
title: string;
createComponent: React.ReactNode;
}
function withCreateHOC<ChildProps>(
ChildComponent: ComponentType<ChildProps>,
options: Partial<WithCreateButtonHOCOptions>
) {
We return a function that takes the same props, but without isOpen or toggleOpen, if those were properties of ChildProps.
return function (props: Omit<ChildProps, 'isOpen' | 'toggleOpen'>) {
We need to set defaults for the options in the destructuring step in order to set only one property.
const { createComponent, title = 'Create' } = options;
We access isOpen from the redux state.
const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);
We create a callback that dispatches an action to redux -- you will need to handle this in your reducer. I am dispatching a raw action object {type: 'TOGGLE_OPEN'}, but you could make an action creator function for this.
const dispatch = useDispatch();
const toggleOpen = () => {
dispatch({type: 'TOGGLE_OPEN'});
}
We will pass these two values isOpen and toggleOpen as props to ChildComponent just in case it want to use them. But more importantly, we can use them as click handlers on your button and drawer components. (Note: it looks like drawer wants a prop setIsOpen that takes a boolean, so you may need to tweak this a bit. If the drawer is only shown when isOpen is true then just toggling should be fine).
Code
function withCreateHOC<ChildProps>(
ChildComponent: ComponentType<ChildProps>,
options: Partial<WithCreateButtonHOCOptions>
) {
return function (props: Omit<ChildProps, 'isOpen' | 'toggleOpen'>) {
const { createComponent, title = 'Create' } = options;
const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);
const dispatch = useDispatch();
const toggleOpen = () => {
dispatch({ type: 'TOGGLE_OPEN' });
}
return (
<div>
<ChildComponent
{...props as ChildProps}
toggleOpen={toggleOpen}
isOpen={isOpen}
/>
<div>
<Component.Button
key={'add'}
big={true}
round={true}
primary={true}
onClick={toggleOpen}
className={'float-right'}
tooltip={title}
>
<Component.Icon material={'add'} />
</Component.Button>
</div>
<OpenDrawerWithClose
open={isOpen}
title={title}
setisOpen={toggleOpen}
createComponent={createComponent}
/>
</div>
);
}
}
This version is slightly better because it does not have the as ChildProps assertion. I don't want to get too sidetracked into the "why" but basically we need to insist that if ChildProps takes an isOpen or toggleOpen prop, that those props must have the same types as the ones that we are providing.
interface AddedProps {
isOpen: boolean;
toggleOpen: () => void;
}
function withCreateHOC<ChildProps>(
ChildComponent: ComponentType<Omit<ChildProps, keyof AddedProps> & AddedProps>,
options: Partial<WithCreateButtonHOCOptions>
) {
return function (props: Omit<ChildProps, keyof AddedProps>) {
const { createComponent, title = 'Create' } = options;
const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);
const dispatch = useDispatch();
const toggleOpen = () => {
dispatch({ type: 'TOGGLE_OPEN' });
}
return (
<div>
<ChildComponent
{...props}
toggleOpen={toggleOpen}
isOpen={isOpen}
/>
<div>
<Component.Button
key={'add'}
big={true}
round={true}
primary={true}
onClick={toggleOpen}
className={'float-right'}
tooltip={title}
>
<Component.Icon material={'add'} />
</Component.Button>
</div>
<OpenDrawerWithClose
open={isOpen}
title={title}
setisOpen={toggleOpen}
createComponent={createComponent}
/>
</div>
);
}
}
Playground Link
Summary
I have the following function inside of a functional component which keeps coming back undefined. All of the data inside the function, tableData and subtractedStats are defined and accurate.
This is probably just a small JavaScript I'm making so your help would be greatly appreciated!
Code
This is a functional component below:
const TableComponent = ({ tableData }) => {
formatTableData = () => {
console.log("inside sumDataFormat", tableData);
return tableData.forEach(competitor => {
let subtractedStats = [];
console.log("competitor in", competitor);
for (const i in competitor.startingLifeTimeStats) {
if (competitor.startingLifeTimeStats[i]) {
competitor.stats
? (subtractedStats[i] =
competitor.stats[i] - competitor.startingLifeTimeStats[i])
: (subtractedStats[i] = 0);
}
}
console.log("subtractedStats", subtractedStats);
return subtractedStats;
});
};
useEffect(() => {
console.log("formatTableData", formatTableData());
});
}
Edit:
Can someone help me to what's wrong in this code (how to solve this?) and could briefly explain a functional component
The forEach function doesn't not return anything, it simply iterates over your array, giving you an undefined, the map function could be what you were looking for :
formatTableData = () => {
console.log("inside sumDataFormat", tableData);
return tableData.map(competitor => { // This returns another array where every element is converted by what's returned in the predicate
Functional Component are the most basic kind of React component, defined by the component's (unchanging) props.
Functional Component needs return some JSX code (UI) (can be null too).
Here's an example of most basic Functional Component
const App = () => {
const greeting = 'Hello Function Component!';
return <Headline value={greeting} />;
};
const Headline = ({ value }) => {
return <h1>{value}</h1>;
};
export default App;
Solution Code
Here's the solution of the above example as a Functional Component
This is solution below uses hooks to save data to component's state and also uses lifecycle methods to parse that data on componentDidMount:
const TableComponent = (props: ) => {
const [state, setState] = useState(initialState)
// componentDidUpdate
useEffect(() => {
setState(getData(props.data));
}, []);
// getData() - Parser for data coming inside the functional Component
const getData = (tableData) => {
return tableData.map(competitor => {
return competitor.startingLifeTimeStats.map((item, index) => {
return item && competitor.stats ? competitor.stats[index]-item : 0;
})
})
};
// UI (JSX)
return (
<Text>{JSON.stringify(state)}</Text>
);
}
export default TableComponent;
Try with map sample code as below.
render() {
return (<div>
{this.state.people.map((person, index) => (
<p>Hello, {person.name} from {person.country}!</p>
))}
</div>);
}
i wanted to create multiple request from hoc,where i was able to create hoc for single request(redux action which call api),please check the code for single request
i have created hoc for reducing repeated code in every component,like on componentdidmount calling api,managing error,managing loading state but it is only for single request like you can see in intial object in given hoc,so i want a to create hoc which can executes for multiple request(redux action which calls api),i dont know that this solution which is working for single request is properly implemented or not
So,please help me to create hoc which can be resealable for any given scenario
hoc
export const ComponentWithAPIRequest = ({ onMount = null, LoaderRequestID,onUnmount = null }) => (WrappedComponent) => {
return class ComponentWithAPIRequest extends Component {
state = {
stateLoader: true // intial Load
};
componentDidMount = () => {
this.Request();
};
componentWillUnmount() {
onUnmount !== null ? this.props[onUnmount]() : null;
}
Request = () => {
onMount !== null ? this.props[onMount](LoaderRequestID) : null; // API request
this.setState({ stateLoader: false });
};
render() {
const { error, isLoading } = this.props; // pass it here somehow.
const { stateLoader } = this.state;
const isLoadingFromAPI = this.props.isLoadingRequestIds.indexOf(LoaderRequestID) !== -1 ? true : false;
if (stateLoader) {
return (
<div className="text-center">
<CircularProgress />
</div>
);
}
if (isLoadingFromAPI) {
return (
<div className="text-center">
<CircularProgress />
</div>
);
} else {
return <WrappedComponent {...this.props} retry={this.Request} />;
}
}
};
};
component
export const isContainer = ({ intial, list }) => (WrappedComponent) => {
const IsContainer = (props) => <WrappedComponent {...props} />;
return compose(ComponentWithAPIRequest(intial), hasRequestError)
(IsContainer);
};
hasRequestError // is error hoc
ComponentWithAPIRequest // is above added hoc
#isContainer({
intial: {
onMount: 'FirstRequest', // is a redux action which call api,here i want to have multiple request like ['FirstRequest','SecondRequest']
LoaderRequestID: 'FirstRequestLoader', // is an unique id for loader,which then for multiple request be converted to respective request like ['FirstRequestLoader','SecondRequestLoader']
onUnmount: 'ResetLeaderBoardAll' // is a redux action when component unmount
}
})
class ComponentView extends Component {
render() {
return (
<SomeComponent {...this.props}/>
);
}
}
const mapStateToProps = (state) => {
return {
somestate:state
};
};
export default connect(mapStateToProps, {
FirstRequest,
SecondRequest
})(ComponentView);
If I were you, I would update your hoc and pass multiple URLs as parameter to send multiple requests like:
export default connect(mapStateToProps, mapDispatchToProps)(ComponentWithAPIRequest(ComponentView,
[
"url1", "url2" ...
]));
And pass them back to ComponentView with 'props' or 'props.requestResponses'(single) object. Then I would use them like
const url1response = this.props.requestResponses["url1"];
In my case, I would store them in ComponentWithAPIRequest's state but you can use redux and get them from mapStateToProps in ComponentView as well.
I have a problem with React.
When I press the "+" button, this console message appears and nothing happens:
Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`
I found several questions with similar titles, but common thing among them is that there were calls of functions with setState inside render method.
My render method has no calls, but error appears.
Why?
Thank you for reading.
Code:
import React from 'react';
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input
ref={node => {
input = node;
}}
/>
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
const TodoList = ({todos, remove}) => {
// Map through the todos
const todoNode = todos.map((todo) => {
return (<Todo todo={todo} key={todo.id} remove={remove}/>)
});
return (<ul>{todoNode}</ul>);
};
const Title = () => {
return (
<div>
<div>
<h1>to-do</h1>
</div>
</div>
);
};
window.id = 0;
class TodoApp extends React.Component {
constructor(props) {
// Pass props to parent class
super(props);
// Set initial state
this.state = {
data: []
}
}
// Add todo handler
addTodo(val) {
// Assemble data
const todo = {text: val, id: window.id++}
// Update data
this.state.data.push(todo);
// Update state
console.log('setting state...');
this.setState({data: this.state.data});
}
// Handle remove
handleRemove(id) {
// Filter all todos except the one to be removed
const remainder = this.state.data.filter((todo) => {
if (todo.id !== id) return todo;
});
// Update state with filter
this.setState({data: remainder});
}
render() {
// Render JSX
return (
<div>
<Title />
<TodoForm addTodo={
(val)=>{
this.addTodo(val)
}
}/>
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
</div>
);
}
}
export default TodoApp;
In your render method for Todo you invoke remove, which is where your erroneous state update happens.
To fix this, return a function from the handleRemove method of TodoApp that updates the state. Simplified version:
handleRemove(id) {
return () => {
...
this.setState({ data: remainder });
}
}
Also worth noting here that because you're using the current state, it's best to use the setState callback (which gets prevState as an argument), and not rely on this.state.
setState docs
Andy_D very helped and my answer has two solutions:
First in render function change
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
to
<TodoList
todos={this.state.data}
remove={() => this.handleRemove.bind(this)}
/>
or change code
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
to that:
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={() => remove(todo.id)}>{todo.text}</li>)
};