Suppose that one has the following component file HelloForm.jsx:
import React from 'react';
import {graphql} from 'react-apollo';
import {connect} from 'react-redux';
import {actions, Control, Form} from 'react-redux-form';
import {compose, withProps} from 'recompose';
import query from './HelloForm.gql';
const passThrough = fn => BaseComponent => props => {
fn(props, BaseComponent);
return BaseComponent(props);
};
export const HelloForm = ({onSubmitEventHandler}) =>
<Form
className="my-form"
model="forms.hello"
onSubmit={onSubmitEventHandler}
>
<label htmlFor="name">Name</label>
<Control.text
{...props}
model=".name"
id="name"
/>
<button type="submit">Say Hello</button>
</Form>;
export const enhance = compose(
connect(),
withProps({
onSubmitEventHandler: ({name}) => {
window.alert(`Hello, ${name}`);
},
}),
graphql(query),
passThrough(({data, dispatch, loading}) => {
if (!loading) {
dispatch(actions.load('forms.hello', data));
}
}),
);
export default enhance(HelloForm);
This appears to work as expected but one gets the following warning:
Warning: setState(...): 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.
However, React's component documentation suggests that one should dispatch the action during the componentDidMount lifecycle event (which can be done with functional components via recompose.lifecycle). But no props are provided to the componentDidMount event handler.
What is the proper way to "asynchronously" dispatch actions to Redux?
The solution was indeed to use the recompose.lifecycle higher-order component. However, one must not use an arrow function. The updated enchance higher-order component should be implemented as follows:
export const enhance = compose(
connect(),
withProps({
onSubmitEventHandler: ({name}) => {
window.alert(`Hello, ${name}`);
},
}),
graphql(query),
lifecycle({
componentDidMount: function () {
const {dispatch, loading, recipe} = this.props;
if (loading) {
dispatch(actions.load('forms.hello', data));
}
},
}),
);
Related
I want to pass the setState method of the component (SnackBar) to all the child components of the _app.js. If I pass the setState method of SnackBar to all the child components of _app.js then it will be a very tedious task. Because, there are approx 4 levels of hierarchy from _app.js to the single component node. It includes,
_app.js -> pages -> layouts -> sections -> components
The snippet of _app.js is here.
function MyApp({ Component, pageProps }) {
const [ toastOpen, setToastOpen ] = React.useState({
msg: '',
open: false
});
React.useEffect(() => {
pageProps = { ...pageProps, setToastOpen };
}, []);
return (
<React.Fragment>
<ToastMessage
message={ toastOpen.msg }
setOpenState={ setToastOpen }
openState={ toastOpen.open }
/>
<Component {...pageProps} />
</React.Fragment>
)
}
Is there any way that I can directly import the setToastOpen method in the child component and use it whenever I need it?
React have a special Feature called Context Api , using that you can skip the props chain passed into your components..
I recomend you to checkout below resources to learn about context Api -
https://reactjs.org/docs/context.html
https://www.freecodecamp.org/news/react-context-in-5-minutes
Example of ContextAPI
Create a seperate file for Context Toast-context.js , You can use any name you want.
import React, { useState } from "react"
const ToastContext = React.createContext({
message: "",
toastOpen: false,
toggleToast: () => { },
changeMessage: () => { }
})
export const ToastContextProvider = ({children}) => {
/*you need to use
this component to wrap App.js so that the App.js and all of its children
and their children components and so on will get the access to the
context*/
const [toastOpen, setToastOpen] = useState(false);
const [message, setMessage] = useState("");
const toggleToast = () => {
setToastOpen(true)
}
const changeMessage = (message) => {
setMessage(message);
}
return (
<ToastContext.Provider value={
toastOpen,
message,
toggleToast,
changeMessage
}>
{children}
</ToastContext.Provider>
)
}
now in the App.js file you need to wrap your components with ToastContextProvider component
import React, { useContext } from "react";
import { ToastContextProvider } from "./Toast-context";
import { ToastContext } from "./Toast-context";
function MyApp({ Component, pageProps }) {
const { message, toastOpen, toggleToast, changeMessage } =
useContext(ToastContext);
return (
<ToastContextProvider>
{toastOpen && <div className="toast">{message}</div>}
</ToastContextProvider>
);
}
just import the context using useContext Hook in any component you want. you don't need to wrap with <ToastContextProvider> in every component.
just use useContext hook and then you can see the state and as well as call the functions methods to change the state.
Also make sure to refer the above links to learn more about Context Api. Thank You
I have a very simple class based component. Which looks like the following:
class MyComponent extends React.Component {
onPressButton () {
console.warn('button pressed')
const { contextFunction } = this.context
contextFunction()
}
render () {
return (
<div>
My Component
<button onClick={() => onPressButton()}>Press button</button>
</div>
)
}
}
MyComponent.contextType = SomeContext
That is all fine and well and works as expected. However, I am having trouble adding unit tests with jest and enzyme. My current code looks as follows:
test('should render test Press button', async () => {
const contextFunctionMock = jest.fn()
const wrapper = shallow(<MyComponent {...props} />)
wrapper.instance().context = { contextFunction: contextFunctionMock }
console.log('wrapper.instance()', wrapper.instance())
await wrapper.instance().onPressButton() // this works just fine
expect(contextFunctionMock).toHaveBeenCalled() // this errors, basically because ti complains contextFunction is not a function
})
As you can see above, I console.logged my wrapper.instance() to see what is going on.
Interestingly enough, the context on the root of the instance object is indeed what I expected it to be based on setting the context, which is something like:
context: {
contextFunction: [Function: mockConstructor] {
_isMockFunction: true,
getMockImplementation: [Function (anonymous)],
[...Other mock function properties]
}
...
However, there is a second context, which is in the updater property of the wrapper.instance(), and it is an empty object. Basically looks like the following:
updater: <ref *2> Updater {
_renderer: ReactShallowRenderer {
_context: {},
...
}
Not exactly sure if this is the context being used for my component's unit test, but it is currently just an empty object, which makes me think this may be the one being used for it.
Anyway, how can I properly mock my context functions to run on this particular unit tests? Also, why is this happening but does not happen in others with a similar set of circumstances?
Problem
A fundamental problem with your code above is that there's no way to assert that the context function is successfully/failing to be called. Right now, you're clicking a button, but there isn't any indication on what's happening after the button is clicked (nothing is being changed/updated within the context/component to reflect any sort of UI change). So asserting that a contextual function is called won't be beneficial if there's no result of clicking the button.
In addition to the above, the enzyme-adapter doesn't support context that uses the createContext method.
However, there's a work-around for this limitation! Instead of unit testing the component, you'll want to create an integration test with the context. Instead of asserting that a contextual function was called, you'll make assertions against the result of clicking on the button that changes context and how it affects the component.
Solution
Since the component is tied to what's in context, you'll create an integration test. For example, you'll wrap the component with context in your test and make assertions against the result:
import * as React from "react";
import { mount } from "enzyme";
import Component from "./path/to/Component";
import ContextProvider from "./path/to/ContextProvider";
const wrapper = mount(
<ContextProvider>
<Component />
</ContextProvider>
);
it("updates the UI when the button is clicked", () => {
wrapper.find("button").simulate("click");
expect(wrapper.find(...)).toEqual(...);
})
By doing the above, you can make assertions against contextual updates within the Component. In addition, by using mount, you won't have to dive into the ContextProvider to view the Component markup.
Demo Example
This demo utilizes context to toggle a theme from "light" to "dark" and vice versa. Click on the Tests tab to run the App.test.js integration test.
Code Example
App.js
import * as React from "react";
import { ThemeContext } from "./ThemeProvider";
import "./styles.css";
class App extends React.PureComponent {
render() {
const { theme, toggleTheme } = this.context;
return (
<div className="app">
<h1>Current Theme</h1>
<h2 data-testid="theme" className={`${theme}-text`}>
{theme}
</h2>
<button
className={`${theme}-button button`}
data-testid="change-theme-button"
type="button"
onClick={toggleTheme}
>
Change Theme
</button>
</div>
);
}
}
App.contextType = ThemeContext;
export default App;
ThemeProvider.js
import * as React from "react";
export const ThemeContext = React.createContext();
class ThemeProvider extends React.Component {
state = {
theme: "light"
};
toggleTheme = () => {
this.setState((prevState) => ({
theme: prevState.theme === "light" ? "dark" : "light"
}));
};
render = () => (
<ThemeContext.Provider
value={{ theme: this.state.theme, toggleTheme: this.toggleTheme }}
>
{this.props.children}
</ThemeContext.Provider>
);
}
export default ThemeProvider;
index.js
import * as React from "react";
import ReactDOM from "react-dom";
import ThemeProvider from "./ThemeProvider";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
);
Test Example
An example of how to test against the demo example above.
withTheme.js (an optional reusable testing factory function to wrap a component with context -- especially useful for when you may want to call wrapper.setProps() on the root to update a component's props)
import * as React from "react";
import { mount } from "enzyme";
import ThemeProvider from "./ThemeProvider";
/**
* Factory function to create a mounted wrapper with context for a React component
*
* #param Component - Component to be mounted
* #param options - Optional options for enzyme's mount function.
* #function createElement - Creates a wrapper around passed in component with incoming props (now we can use wrapper.setProps on root)
* #returns ReactWrapper - a mounted React component with context.
*/
export const withTheme = (Component, options = {}) =>
mount(
React.createElement((props) => (
<ThemeProvider>{React.cloneElement(Component, props)}</ThemeProvider>
)),
options
);
export default withTheme;
App.test.js
import * as React from "react";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import withTheme from "./withTheme";
import App from "./App";
configure({ adapter: new Adapter() });
// wrapping "App" with some context
const wrapper = withTheme(<App />);
/*
THIS "findByTestId" FUNCTION IS OPTIONAL!
I'm using "data-testid" attributes, since they're static properties in
the DOM that are easier to find within a "wrapper".
This is 100% optional, but easier to use when a "className" may be
dynamic -- such as when using css modules that create dynamic class names.
*/
const findByTestId = (id) => wrapper.find(`[data-testid='${id}']`);
describe("App", () => {
it("initially displays a light theme", () => {
expect(findByTestId("theme").text()).toEqual("light");
expect(findByTestId("theme").prop("className")).toEqual("light-text");
expect(findByTestId("change-theme-button").prop("className")).toContain(
"light-button"
);
});
it("clicking on the 'Change Theme' button toggles the theme between 'light' and 'dark'", () => {
// change theme to "dark"
findByTestId("change-theme-button").simulate("click");
expect(findByTestId("theme").text()).toEqual("dark");
expect(findByTestId("theme").prop("className")).toEqual("dark-text");
expect(findByTestId("change-theme-button").prop("className")).toContain(
"dark-button"
);
// change theme to "light"
findByTestId("change-theme-button").simulate("click");
expect(findByTestId("theme").text()).toEqual("light");
});
});
As for today, the new context API is not supported by enzyme, the only solution I found is to use this utility https://www.npmjs.com/package/shallow-with-context
import { configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { withContext } from "shallow-with-context";
import MyComponent from "./MyComponent";
configure({ adapter: new Adapter() });
describe("Context", () => {
it("should render test Press button", async () => {
const contextFunctionMock = jest.fn();
const context = { contextFunction: contextFunctionMock };
const MyComponentWithContext = withContext(MyComponent, context);
const wrapper = shallow(<MyComponentWithContext />, { context });
await wrapper.instance().onPressButton();
expect(contextFunctionMock).toHaveBeenCalled();
});
});
https://codesandbox.io/s/enzyme-context-test-xhfj3?file=/src/MyComponent.test.tsx
This question already has answers here:
How do I access store state in React Redux?
(8 answers)
Closed 1 year ago.
I've created the reducer and using it to change the state of my store. but as you can see in App.js whenever I click on button and update the state. it updates. I can see it in console. but component does not update. as you can see I have list of tracks there it is not updating. and if I make any changes to code because of that the component re-render I can see the new state after that. why is it not rendering automatically whenever the state updates.
Action
import * as actions from './actionTypes'
export const trackAdded = (title, artist, audioSrc, img) => {
return {
type: actions.TRACK_ADDED,
payload: {
title,
artist,
audioSrc,
img
}
}
}
Reducer
import * as actions from './actionTypes'
export default function reducer(state = [], action) {
switch (action.type) {
case actions.TRACK_ADDED:
return [
...state,
{
title: action.payload.title,
artist: action.payload.artist,
audioSrc: action.payload.audioSrc,
img: action.payload.img
}
]
default:
return state
}
}
App.js
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App() {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<div className="App">
{store.getState().map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
);
}
export default App;
The component will not update because the store.getState() inside of <div className="App"> will only run once when the component is called. There is no logic that exists that tells the component to rerun the store.getState(). If you want the component to receive updates when the store's state changes, you need to connect it to the store using react-redux's connect function or a useSelector hook.
As an example, if using the connect function, you can map the redux state to a react component's props. So if the component's props change, then the component will "react" to it's props changing. The mapping of redux's state to the components props happens in the mapStateToProps function, returning a prop tracks that is mapped to the component. Otherwise there is no reason for the component to update. Also note: in the example below, the store is connected to React through a Provider component, providing the store to child components that wish to connect to it.
import { Provider, connect } from 'react-redux'
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App(props) {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<Provider store={store}>
<div className="App">
{props.tracks.map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
</Provider>
);
}
const mapStateToProps = (state) => {
const tracks = state
return { tracks }
}
export default connect(mapStateToProps)(App);
Having said that, if you go the connect route, you might want to add a mapDispatchToProps function to the connect so you dont pass around the store everywhere. You can find that info in the redux docs, but that would be an answer to a different question.
This is happening because your component does not know that store has been updated, you can use something like this
useEffect(() => {
this.artistList = store.getState();
}, [store.getState()]); // kind of watcher of store.getState()
but this is definitely not recommended. You would want to use useSelector() from react-redux, which is much concise and recommended way to doing it.
I'm trying to port from class component to react hooks with Context API, and I can't figure out what is the specific reason of getting the error.
First, my Codes:
// contexts/sample.jsx
import React, { createContext, useState, useContext } from 'react'
const SampleCtx = createContext()
const SampleProvider = (props) => {
const [ value, setValue ] = useState('Default Value')
const sampleContext = { value, setValue }
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
)
}
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return (
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
export {
useSample
}
// Sends.jsx
import React, { Component, useState, useEffect } from 'react'
import { useSample } from '../contexts/sample.jsx'
const Sends = (props) => {
const [input, setInput ] = useState('')
const handleChange = (e) => {
setInput(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault()
props.setValue(input)
}
useEffect(() => {
setInput(props.value)
}, props.value)
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
)
}
Error I got:
Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
Explanation for my code:
I used Context API to manage the states, and previously I used class components to make the views. I hope the structure is straightforward that it doesn't need any more details.
I thought it should work as well, the <Sends /> component gets passed into useSample HoC function, and it gets wrapped with <SampleProvider> component of sample.jsx, so that <Sends /> can use the props provided by the SampleCtx context. But the result is failure.
Is it not valid to use the HoC pattern with React hooks? Or is it invalid to hand the mutation function(i.e. setValue made by useState()) to other components through props? Or, is it not valid to put 2 or more function components using hooks in a single file? Please correct me what is the specific reason.
So HOCs and Context are different React concepts. Thus, let's break this into two.
Provider
Main responsibility of the provider is to provide the context values. The context values are consumed via useContext()
const SampleCtx = createContext({});
export const SampleProvider = props => {
const [value, setValue] = useState("Default Value");
const sampleContext = { value, setValue };
useEffect(() => console.log("Context Value: ", value)); // only log when value changes
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
);
};
HOC
The consumer. Uses useContext() hook and adds additional props. Returns a new component.
const withSample = WrappedComponent => props => { // curry
const sampleCtx = useContext(SampleCtx);
return (
<WrappedComponent
{...props}
value={sampleCtx.value}
setValue={sampleCtx.setValue}
/>
);
};
Then using the HOC:
export default withSample(Send)
Composing the provider and the consumers (HOC), we have:
import { SampleProvider } from "./provider";
import SampleHOCWithHooks from "./send";
import "./styles.css";
function App() {
return (
<div className="App">
<SampleProvider>
<SampleHOCWithHooks />
</SampleProvider>
</div>
);
}
See Code Sandbox for full code.
Higher order Components are functions that takes a Component and returns another Component, and the returning Components can be class component, a Functional Component with hooks or it can have no statefull logic.
In your example you're returning jsx from useSample.
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return ( // <-- here
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
if you want to make a HOC what you can do is something like this
const withSample = (WrappedComponent) => {
return props => {
const sampleCtx = useContext(SampleCtx)
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} {...props} />
}
}
I'm trying to figure out how to user the reducers with and inside my React-Component.
My goal is pretty easy - at least i thought so: I want to toggle a Drawer-Menu. I know I can solve this with React-Only, but I want to learn Redux.
So, I've got a Component…
import React, { Component } from 'react';
class Example extends Component {
// ???
render() {
return (
<button className="burgerbutton" onClick={this.toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default Example;
also a Reducer
const initialState = {
buttonstate: false
};
const example = (state = initialState, action) => {
switch (action.type) {
case 'TOGGLE_BTN':
return Object.assign({}, state, {
buttonstate: !state.buttonstate
})
default:
return state
}
}
export default example
and an Action (although I don't know where to put that since it's so simple)
export const toggleDrawer = () => {
return {
type: 'TOGGLE_DRAWER'
}
}
I read a lot of tutorials and most of them want me to seperate between "Presentational Components" and "Container Components". I can't really see how these concepts apply here.
So what do I have to do to do to make this work? Am I looking at this problem from the right angle or do I need 12 "Container Components" to solve this?
I really hope this question makes sense at all and/or is not a duplicate!
In redux you have to dispatch action to update reducer state. So normally a component is connected to the redux store and communication is done through dispatch.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from 'action file location';
class Example extends Component {
toggleDrawerHandler() {
this.props.dispatch(toggleDrawer())
}
render() {
// access button state from this.props.buttonstate
return (
<button className="burgerbutton" onClick={this.toggleDrawerHandler.bind(this)}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default connect((store) => {buttonstate: store.buttonstate})(Example);
First, I'm really enjoying using redux "ducks" which is basically a redux reducer bundle. You put your reducer, action constants, and action creators in one file (called a duck). Then you may have multiple ducks for different modules or pieces of state that you'd then combine with combineReducers.
While #duwalanise has the right idea, I'd rather see the second param of connect() be used to directly map the action to dispatch (and there's a good shortcut for it) instead of having to use this.props.dispatch
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from './duck';
class Example extends Component {
render() {
const { buttonstate, togglerDrawer } = this.props;
return (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);
}
}
const mapStateToProps = (state) => ({
buttonstate: state.buttonstate,
});
export default connect(mapStateToProps, { toggleDrawer })(Example);
One side note, if you have a handler method in your component, it's better to do .bind(this) inside the constructor instead of using an arrow function or .bind(this) inside the event, ie don't do this onClick={() => /* do something */ } or this onClick={this.myHandler.bind(this)} This is an interesting (and long) read on it.
To touch on the Container vs Presentational Component piece: The idea would be to put all of your logic, handlers, redux actions etc into your containers, and pass that through props to your simple (hopefully stateless/pure function) presentational components. Technically, your component the way it's written could be turned into a stateless component:
const Example = ({ buttonstate, togglerDrawer }) => (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);