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.
Related
I have a certain Context setup similar to this
const DataContext = createContext({ data: null });
const getData = (key) => {
switch(key) {
case 1:
return "Hello"
case 2:
return " World"
default:
return null
}
}
export const DataProvider = ({ id, children }) => {
const data = useMemo(() => {
return getData(id);
}, [id]);
return (
<DataContext.Provider
value={{
data,
}}
>
{children}
</DataContext.Provider>
);
};
export default DataContext
And child components that use it like this
const HelloComponent = () => {
return <DataProvider id={1}>
{
// children are components that useContext(DataContext) and expect data to be "Hello"
}
</DataProvider>
}
Now I need to do this
const HelloWorldComponent = () => {
return (
<DataProvider id={1}>
<DataProvider id={2}>
{
// children are components that need to read both Hello and World
}
</DataProvider>
</DataProvider>
);
};
Need to provide all parent context's data of one single Context definition to a set of children
I know useContext can only read the closest parent of a given ContextType, so I'm not sure of how to proceed with this.
You can't use two context providers of the same type and have children receive from both. The Context API is such that children components receive the context value from the closest context provider to them in the React tree.
You can, however, use a single provider that instead returns the getData function. Here's an example using a single DataProvider and a custom React hook to provide the "id" value.
Data.Context.js
import { createContext, useContext } from "react";
const DataContext = createContext({
getData: () => {}
});
const getData = (key) => {
switch (key) {
case 1:
return "Hello";
case 2:
return " World";
default:
return null;
}
};
export const useDataContext = (id) => useContext(DataContext).getData(id);
const DataProvider = ({ children }) => {
return (
<DataContext.Provider value={{ getData }}>{children}</DataContext.Provider>
);
};
export default DataProvider;
index.js
<DataProvider>
<App />
</DataProvider>
Child component
import { useDataContext } from "./Data.Context";
const Child = () => {
const data1 = useDataContext(1);
const data2 = useDataContext(2);
return (
<div>
<div>DataProvider 1: {data1}</div>
<div>DataProvider 2: {data2}</div>
</div>
)
}
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
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>)
};
i have some issues dealing with a simple case in my redux-react app: i want to reset an input text after an asynchronous operation ignited by a button.
Let’s say we have an input text in which you put a text and this is passed through a onClick event to a dispatch action.
This action contacts a server and after the server response i want to reset the input field.
I’ve implemented a number of solutions (i’m using redux thunk) to this problem but i’m not sure if they are hacky ways to solve it, let me show you:
1) Presentational component (the input field) implements a reset method that is passed as a value to the onClick method.
export default React.createClass({
reset: function () {
this.setState({searchText: ''})
},
getInitialState: function () {
return {
searchText: ''
}
},
render: function () {
return (
<div>
<TextField
value={this.state.searchText}
onChange={e => this.setState({ searchText: e.target.value })}
/>
<RaisedButton
onClick={this.props.startSearch.bind(null,
this.state.searchText,
this.reset)} // ===> HERE THE RESET FUNCTION IS PASSED
/>
</div>
)
}
})
The container dispatches the action and then calls the reset method.
const mapDispatchToProps = (dispatch) => {
return {
startSearch: (searchText, reset) => {
dispatch(actions.startSearch(searchText))
.then(() => reset())
}
}
}
2) Using ref (https://facebook.github.io/react/docs/refs-and-the-dom.html)
The container gets a reference to its child and calls reset through it
const SearchUserContainer = React.createClass({
startSearch: (searchText) => {
dispatch(actions.startSearch(searchText))
.then(() => this.child.reset())
},
render: function () {
return (
<SearchUser {...this.props} ref={(child) => { this.child = child; }}/>
)
}
})
3) The Redux Way.
searchText is managed by the store thus the action dispatched triggers a resolver that reset the searchText value, the container updates its child and we are done, well… almost:
the presentational component is a controlled component (https://facebook.github.io/react/docs/forms.html#controlled-components) that means it manages the input text as an internal state, i think we have to find a way to make the two ‘state managers’ coexist.
I wrote this code to manage the internal state and the state coming from redux, in few words the presentational gets the initial value from redux, then updates it in the onChange event and it’s ready to receive updates from redux thanks to componentWillReceiveProps.
export default React.createClass({
getInitialState: function () {
return {
searchText: this.props.searchText ==> REDUX
}
},
componentWillReceiveProps: function (nextProps) {
this.setState({
searchText: nextProps.searchText ==> REDUX
})
},
render: function () {
return (
<div>
<TextField
value={this.state.searchText}
onChange={e => this.setState({ searchText: e.target.value })}
/>
<RaisedButton
onClick={this.props.startSearch.bind(null, this.state.searchText)}
/>
</div>
)
}
})
4) Redux-Form
To complete the picture i link the redux-form options to do that
http://redux-form.com/6.5.0/docs/faq/HowToClear.md/
What do you think about those ideas?
Thanks.
Go the Redux way, except go all the way: remove the internal state from your component completely and let Redux handle it (might as well make your component a pure-functional component too):
Component:
import { connect } from 'redux';
import { actions } from 'actionCreators';
const ControlledInputComponent = (props) => {
return (
<div>
<TextField
value={this.props.searchText}
onChange={e => this.props.setSearchText(e.target.value)}
/>
<RaisedButton
onClick={this.props.startSearch}
/>
</div>
);
};
const mapStateToProps = (state) => {
return { searchText: state.searchText };
};
const mapDispatchToProps = (dispatch) => {
return {
setSearchText: (txt) => { dispatch(actions.setSearchText(txt)); },
startSearch: () => { dispatch(actions.search()); }
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ControlledInputComponent);
Action creator:
export const actions = {
setSearchText: (txt) => ({ type: 'setText', data: txt }),
//here's where the thunk comes in
//make sure you have redux-thunk and add it as a middleware when setting up the store, etc.
search: () => {
return (dispatch) => {
//use fetch or whatever to run your search (this is a simplified example)
fetch(/* your url here */).then(() => {
//presumably a success condition
//handle the search results appropriately...
//dispatch again to reset the search text
dispatch(actions.setSearchText(null);
});
};
}
};
Reducer:
const reducer = (state = { searchText: null }, action) => {
if (!action || !action.type) return state;
switch (action.type) {
//you should really define 'setText' as a constant somewhere
//so you can import it and not have to worry about typos later
case 'setText':
return Object.assign({}, state, { searchText: action.data });
default:
return state;
}
};
export default reducer;
Hopefully that helps. Good luck!