WebContext.js
import React, { createContext, Component } from 'react';
export const WebContext = createContext();
class WebContextProvider extends Component {
state = {
inputAmount: 1,
};
render() {
return <WebContext.Provider value={{ ...this.state }}>{this.props.children}</WebContext.Provider>;
}
}
export default WebContextProvider;
App.js
const App = () => {
return (
<WebContextProvider>
<UpdateBtn />
</WebContextProvider>
);
};
export default App;
UpdateBtn.js
const UpdateBtn = () => {
return (
<Div>
<Button onClick={} />
</Div>
);
};
export default UpdateBtn;
How do I update the inputAmount state present in WebContext.js on button click in UpdateBtn.js? App.js is the parent component for UpdateBtn.js Also, How can I convert the WebContext.js into a functional component?
You should pass the function in Provider which you can call to update the value:
WebContext.js
import React, { createContext, Component } from 'react';
export const WebContext = createContext();
class WebContextProvider extends Component {
state = {
inputAmount: 1,
};
render() {
return (
<WebContext.Provider
value={{
data: ...this.state, // all data now in context.data field
update: () => { // we added this callback
this.setState((state) => ({
inputAmount: state.inputAmount + 1,
}));
},
}}
>
{this.props.children}
</WebContext.Provider>
);
}
}
export default WebContextProvider;
App.js
const App = () => {
return (
<WebContextProvider>
<UpdateBtn />
</WebContextProvider>
);
};
export default App;
UpdateBtn.js
const UpdateBtn = () => {
const context = useContext(WebContext); // we use hook to get context value
return (
<Div>
<Button onClick={context.update} />
</Div>
);
};
export default UpdateBtn;
or
const UpdateBtn = () => {
// or we can use Consumer to get context value
return (
<Div>
<WebContext.Consumer>
{context => <Button onClick={context.update} />}
</WebContext.Consumer>
</Div>
);
};
export default UpdateBtn;
An alternative approach might be to use a reducer to update your state. For example:
export const initialState = {
inputValue: 1
}
export function reducer(state, action) {
const { type, payload } = action;
switch (type) {
case 'updateInputValue': {
return { ...state, inputValue: payload };
}
default: return state;
}
}
Import those into your provider file:
import { initialState, reducer } from './reducer';
and use useReducer to create a store:
export function WebContextProvider({ children }) {
const store = useReducer(reducer, initialState);
return (
<WebContext.Provider value={store}>
{children}
</WebContext.Provider>
);
}
You can then import the context into the component that needs it and use useContext to get at the state and dispatch method. On the click of the button you can dispatch a new value to the store to update inputValue.
export default function UpdateButton() {
const [ { inputValue }, dispatch ] = useContext(WebContext);
function handleClick(e) {
dispatch({
type: 'updateInputValue',
payload: inputValue + 1
});
}
return (
<div>
<div>{inputValue}</div>
<button onClick={handleClick}>Click</button>
</div>
);
};
I've created a full demo to show you how it works in harmony.
Related
I want to use my Context in a class component (MyEventsScreen), but whenever I do so, I get an error saying it is an invalid hooks call.
Here is MyEventsScreen.js (I've simplified it to be brief):
import { useAuthState } from "../contexts/authContext";
export default class MyEventsScreen extends Component {
render() {
return (
<View>
<Text></Text>
</View>
}
I have my authContext.js here:
import React from "react";
import { authReducer } from "../reducers/authReducer";
const AuthStateContext = React.createContext();
const AuthDispatchContext = React.createContext();
function AuthProvider({ children }) {
const [state, dispatch] = React.useReducer(authReducer, {
isLoading: true,
isSignout: false,
userToken: null,
email: null,
userID: null,
});
return (
<AuthStateContext.Provider value={state}>
<AuthDispatchContext.Provider value={dispatch}>
{children}
</AuthDispatchContext.Provider>
</AuthStateContext.Provider>
);
}
function useAuthState() {
const context = React.useContext(AuthStateContext);
if (context === undefined) {
throw new Error("useAuthState must be used within a AuthProvider");
}
return context;
}
function useAuthDispatch() {
const context = React.useContext(AuthDispatchContext);
if (context === undefined) {
throw new Error("useAuthDispatch must be used within a AuthProvider");
}
return context;
}
export { AuthProvider, useAuthState, useAuthDispatch };
I have an authReducer.js here:
export const authReducer = (prevState, action) => {
switch (action.type) {
case "SIGN_IN":
return {
...prevState,
email: action.email,
isSignout: false,
userToken: action.token,
};
default:
return prevState;
}
};
Any help would be appreciated!
For class components you can use the Consumer provided by the context .
export your AuthStateContext
export const AuthStateContext = React.createContext();
In the class component you can use it as
import { useAuthState, AuthStateContext } from "../contexts/authContext";
export default class MyEventsScreen extends Component {
render() {
return (
<AuthStateContext.Consumer>
{(value) => { // read the context value here
return (
<View>
<Text></Text>
</View>
);
}}
</AuthStateContext.Consumer>
);
}
}
Reference
Context Consumer
I have a route to a component HandlingIndex:
<Route strict path={handlingCasePath} component={HandlingIndex} />
HandlingIndex is wrapped with a trackRouteParam component. trackRouteParam component looks like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { parseQueryString } from '../../utils/urlUtils';
const defaultConfig = {
paramName: '',
parse: a => a,
paramPropType: PropTypes.any,
storeParam: () => undefined,
getParamFromStore: () => undefined,
isQueryParam: false,
paramsAreEqual: (paramFromUrl, paramFromStore) => paramFromUrl === paramFromStore
};
/**
* trackRouteParam
*
* Higher order component that tracks a route parameter and stores in the application
* state whenever it changes.
* #param config
*/
const trackRouteParam = config => (WrappedComponent) => {
class RouteParamTrackerImpl extends Component {
constructor() {
super();
this.updateParam = this.updateParam.bind(this);
}
componentDidMount() {
this.updateParam();
}
componentDidUpdate(prevProps) {
this.updateParam(prevProps.paramFromUrl);
}
componentWillUnmount() {
const { storeParam } = this.props;
storeParam(undefined);
}
updateParam(prevParamFromUrl) {
const { paramFromUrl, storeParam, paramsAreEqual } = this.props;
if (!paramsAreEqual(paramFromUrl, prevParamFromUrl)) {
storeParam(paramFromUrl);
}
}
render() {
const {
paramFromUrl,
paramFromStore,
storeParam,
paramsAreEqual,
...otherProps
} = this.props;
return <WrappedComponent {...otherProps} />;
}
}
const trackingConfig = { ...defaultConfig, ...config };
RouteParamTrackerImpl.propTypes = {
paramFromUrl: trackingConfig.paramPropType,
paramFromStore: trackingConfig.paramPropType,
storeParam: PropTypes.func.isRequired,
paramsAreEqual: PropTypes.func.isRequired
};
RouteParamTrackerImpl.defaultProps = {
paramFromUrl: undefined,
paramFromStore: undefined
};
const mapStateToProps = state => ({ paramFromStore: trackingConfig.getParamFromStore(state) });
const mapDispatchToProps = dispatch => bindActionCreators({ storeParam: trackingConfig.storeParam }, dispatch);
const mapMatchToParam = (match, location) => {
const params = trackingConfig.isQueryParam ? parseQueryString(location.search) : match.params;
return trackingConfig.parse(params[trackingConfig.paramName]);
};
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
...stateProps,
...dispatchProps,
paramFromUrl: mapMatchToParam(ownProps.match, ownProps.location),
paramsAreEqual: trackingConfig.paramsAreEqual
});
const RouteParamTracker = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(RouteParamTrackerImpl));
RouteParamTracker.WrappedComponent = WrappedComponent;
Object.keys(RouteParamTracker).forEach((ownPropKey) => {
RouteParamTracker[ownPropKey] = WrappedComponent[ownPropKey];
});
return RouteParamTracker;
};
export default trackRouteParam;
In the component HandlingIndex, I am trying to get a param caseNumber from the url. Just showing the relevant parts here from the component:
const mapStateToProps = state => ({
selectedCaseNumber: getSelectedCaseNumber(state)
});
export default trackRouteParam({
paramName: 'caseNumber',
parse: caseNumberFromUrl => Number.parseInt(caseNumberFromUrl , 10),
paramPropType: PropTypes.number,
storeParam: setSelectedCaseNumber,
getParamFromStore: getSelectedCaseNumber
})(connect(mapStateToProps)(requireProps(['selectedCaseNumber'])(HandlingIndex)));
Action creator for the setSelectedCaseNumber is:
export const setSelectedCaseNumber= caseNumber=> ({
type: SET_SELECTED_CASE_NUMBER,
data: caseNumber
});
So, when I am going to the route 'case/1234', where the parameter is caseNumber: 1234 where I am setting the selectedCaseNumber I see that the data field is NaN. On inspecting the console, I can see that I in the function:
const mapMatchToParam = (match, location) => {
const params = trackingConfig.isQueryParam ? parseQueryString(location.search) : match.params;
return trackingConfig.parse(params[trackingConfig.paramName]);
};
I can see that match.params is an empty object.
I am not sure why is that, why I am getting an empty object?
In trackRouteParam HOC,
At line:
const RouteParamTracker = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(RouteParamTrackerImpl));
You try edit:
const RouteParamTracker = connect(mapStateToProps, mapDispatchToProps, mergeProps)(withRouter(RouteParamTrackerImpl));
Hope can help you!
Pretty new to Redux. I'm trying to pass a handleClick event as a prop from a container component to a presentational component, the handleClick event is supposed to call upon an action which has been received as a prop with mapDispatchToProps.
Could someone tell me how to do this correctly please?
I'm building a calculator, just started, this only has three actions so far, add, Record_input_1 and Record_Input_2.
containers/ButtonsContainer.js:
import React, { Component } from 'react';
import { Buttons } from '../components/Buttons'
import { Record_Input_1 } from '../actions/sum-action';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class ButtonsContainer extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(num) {
return this.props.onRecordInput1(num)
}
render() {
return(
<Buttons handleClick={this.handleClick} />
)
}
mapStateToProps = (state) => {
return {
inputValue1: state.inputValue1,
inputValue2: state.inputValue2,
answer: state.answer
}
}
mapDispatchToProps = (dispatch) => {
return bindActionCreators({
onRecordInput1: Record_Input_1,
onRecordInput2: Record_Input_2
}, dispatch);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ButtonsContainer);
components/Buttons.js
import React, { Component } from 'react';
class Buttons extends Component {
render() {
const buttonMaker = (buttons, row) => {
for (let value of buttons) {
row.push(<button onClick={() => this.props.handleClick(value)} key={value}>
{value}
</button> )
}
}
let row1 = [];
let buttons1 = [1,2,3]
buttonMaker(buttons1, row1)
let row2 = [];
let buttons2 = [4,5,6]
buttonMaker(buttons2, row2)
let row3 = [];
let buttons3 = [7,8,9]
buttonMaker(buttons3, row3)
return (
<div>
<div>{row1}</div>
<br />
<div>{row2}</div>
<br />
<div>{row3}</div>
</div>
)
}
}
export default Buttons;
actions/sum-actions/js:
export const ADD = 'ADD';
export const RECORD_INPUT_1 = 'RECORD_INPUT_1';
export const RECORD_INPUT_2 = 'RECORD_INPUT_2';
export const add = (newInput1, newInput2) => {
return {
type: ADD,
newAnswer: newInput1 + newInput2
}
}
export const Record_Input_1 = (newInput1) => {
return {
type: RECORD_INPUT_1,
newInput1
}
}
export const Record_Input_2 = (newInput2) => {
return {
type: RECORD_INPUT_2,
newInput2
}
}
reducders/sum-reducer.js:
import { ADD, RECORD_INPUT_1, RECORD_INPUT_2 } from '../actions/sum-action'
export const initialState = {
inputValue1: '',
inputValue2: '',
answer: 0
}
export const sumReducer = (state = initialState, action) => {
switch (action.type) {
case ADD:
return [
...state,
{
answer: action.newAnswer
}
]
case RECORD_INPUT_1:
return [
...state,
{
inputValue1: action.newInput1
}
]
case RECORD_INPUT_2:
return [
...state,
{
inputValue2: action.newInput2
}
]
default:
return state;
}
}
store.js:
import { combineReducers, createStore } from 'redux';
import { initialState, sumReducer } from './reducers/sum-reducer';
const rootReducers = combineReducers({
sumReducer
})
export default createStore(rootReducers, initialState, window.devToolsExtension && window.devToolsExtension());
The buttons display ok, when I click on one I get this error:
TypeError: _this2.props.handleClick is not a function
for:
8 | render() {
9 | const buttonMaker = (buttons, row) => {
10 | for (let value of buttons) {
> 11 | row.push(<button onClick={() => this.props.handleClick(value)} key={value}
12 | {value}
13 | </button> )
14 | }
You are declaring mapStateToProps and mapDispatchToProps within ButtonsContainer. You are then passing those two methods to react-redux's connect as if they were declared outside of ButtonsContainer, hence they are undefined. Try moving them out of ButtonsContainer as shown here. It should look something like this:
class ButtonsContainer extends Component {
...
}
const mapStateToProps = (state) => {
return {
inputValue1: state.inputValue1,
inputValue2: state.inputValue2,
answer: state.answer
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
onRecordInput1: Record_Input_1,
onRecordInput2: Record_Input_2
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ButtonsContainer);
I am following the redux counter tutorial from the official docs,- but my applications state is seemingly not updating. To be more clear, essentially the application is a counter with an increment button and a decrement button and it displays the current value on the screen.
I can get it to console log the value as it changes, but it doesn't output it on the screen. Can anyone tell me what I'm doing wrong
import React, { Component } from 'react';
import { createStore } from 'redux';
const counter = (state = 0, action) => {
switch(action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state -1;
default:
return state;
}
}
const store = createStore(counter);
store.subscribe(()=>{
console.log(store.getState());
});
class App extends Component {
render() {
return (
<div className="App">
<h1>Counter Application</h1>
<hr/>
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({type: 'INCREMENT'})}
onDecrement={() => store.dispatch({type: 'DECREMENT'})}
/>
</div>
);
}
}
const Counter = ({
value,
onIncrement,
onDecrement
}) => {
return(
<div>
<h1>{value}</h1>
<button onClick={onIncrement}> Plus </button>
<button onClick={onDecrement}> Minus </button>
</div>
)
}
export default App;
You're gonna need the provider and connect components from react-redux
Your App component won't re-render.
AFAIK, simply because a re-render can only be triggered if a component’s state or props has changed.
You need to trigger the your App component re-render inside store subscribe. I see your store subscribe basically do nothing, only logging here.
store.subscribe(()=>{
console.log(store.getState());
});
you could do something like this, to trigger re-render every time redux store updated:
const page = document.getElementById('page');
const render = () => ReactDOM.render(<App />, page);
render();
store.subscribe(render);
The reason:
In your case, the component has no idea about the changes in the redux store and therefore it doesn't re-render.
Components are only re-rendering if they receiv new props/context
or if their local state has updated (as a result of calling setState() in general)
Solution 1 (direct answer to your question, I think)
const Counter = ({ value, onIncrement, onDecrement }) => {
return (
<div>
<h1>{value}</h1>
<button onClick={onIncrement}> Plus</button>
<button onClick={onDecrement}> Minus</button>
</div>
)
};
class App extends Component {
componentDidMount() {
this._unsub = store.subscribe(this._update);
this._update();
}
componentWillUnmount() {
this._unsub();
this._unsub = null;
};
state = { value: undefined };
render() {
const { value } = this.state;
return (
<div className="App">
<h1>Counter Application</h1>
<Counter
value={value}
onIncrement={this._increment}
onDecrement={this._decrement}
/>
</div>
);
}
_decrement = () => store.dispatch({ type: 'DECREMENT' });
_increment = () => store.dispatch({ type: 'INCREMENT' });
_update = () => {
const value = store.getState();
this.setState({ value });
}
}
Solution 2 (the correct one)
Use react-redux module
Also check these:
- normalizr
- normalizr + keyWindow concept talk
- Reselect
- ComputingDerivedData Post
- react-reselect-and-redux post
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { _valueSelector1, _valueSelector2 } from 'app/feature/selectors';
import { increment, decrement } from 'app/feature/actions';
const mapStateToProps = (state, props) => ({
valueOne: _valueSelector1(state, props),
valueTwo: _valueSelector2(state, props),
})
const mapDispatchToProps = {
increment,
decrement,
};
#connect(mapStateToProps, mapDispatchToProps)
export class YourComponent extends Component {
static propTypes = {
valueOne: PropTypes.number,
valueTwo: PropTypes.number,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
}
render() {
const { valueOne, valueTwo, increment, decrement } = this.props;
return (
<div>
<span>{valueOne}</span>
<Counter value={valueTwo} onIncrement={increment} onDecrement={decrement} />
</div>
)
}
}
I have created a search box inside component. So I am calling onchange function outside in parent App.js. Now I am trying to dispatch that function if I type anything in search box but I can't access that function outside my class.
How to dispatch my function?
Please find my source code below:
import React, {Component} from "react";
import {connect} from "react-redux";
import { User } from "../components/User";
import { Main } from "../components/Main";
import Data from "../components/Data";
import MovieListing from '../components/MovieListing';
import Header from '../components/Header'
import { setName, getApiData } from "../actions/userActions";
import {apiFetch} from "../actions/dataActions"
import {searchFetch} from "../actions/searchActions"
class App extends Component {
constructor(props){
super(props)
this.searchQuery = this.searchQuery.bind(this);
}
searchQuery( query ) {
}
render() {
let dataSet=this.props.data.data.results;
let imagePath = []
let original_title = []
let release_date = []
let original_language = []
if(dataSet){
dataSet.forEach(function (value, key) {
imagePath.push(<Data key={key} imagePath={value.backdrop_path} release_date={value.release_date} original_title={value.original_title} original_language={value.original_language} />)
original_title.push(value.original_title)
})
return(
<div className="wrapper">
<Header searchQuery = { this.searchQuery } />
<div className="movies-listing">
<div className="container">
<MovieListing imagePath={imagePath} release_date={release_date} original_title={original_title} original_language={original_language} />
</div>
</div>
</div>
)
}else{
return(
<div className="middle-loader">
<h1>Loading</h1>
</div>
)
}
// console.log("this.props",this.props);
}
}
const mapStateToProps = (state) => {
return {
user: state.user,
math: state.math,
data: state.data,
searchData: state.searchData
};
};
const mapDispatchToProps = (dispatch) => {
return dispatch(apiFetch()), {searchQuery: (query) => {searchFetch(query)}}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Here I can't access that {searchQuery: (query) => {searchFetch(query)}} because of not accessible that function outside class.
HELP WOULD BE APPRECIATED!!
mapDispatchToProps takes/passes the dispatch function and then return searchQuery function as a prop.
const mapDispatchToProps = (dispatch) => {
return {
searchQuery: (query) => { dispatch(searchFetch(query)) }
}
};
Then in the Header component pass the searchQuery prop
<Header searchQuery={ this.props.searchQuery } />