I am creating an app with reactjs and I am using context api as my state management tool
but the dispatch does not dispatch the values
city shows undefined even after the dispatch has been called.
SearchContext
Here I created the initial state where the city is undefined, date is an empty array and option values are undefined on the initial state
I just have two actions search and reset action which should be dispatched when the user click on a button
import { useReducer } from "react"
import { createContext } from "react"
const INITIAL_STATE = {
city: undefined,
dates: [],
options: {
adult: undefined,
children: undefined,
room: undefined,
}
}
export const SearchContext = createContext(INITIAL_STATE)
const SearchReducer = (state, action) => {
switch (action) {
case "NEW_SEARCH":
return action.payload;
case "RESET_SEARCH":
return INITIAL_STATE;
default:
return state
}
}
export const SearchContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(SearchReducer, INITIAL_STATE)
return (
<SearchContext.Provider
value={{ city: state.city, dates: state.dates, options: state.options, dispatch }}>
{children}
</SearchContext.Provider>
)
}
index.js
I wrapped the whole app with my searchcontext provider so that I can access the values that is passed down to all the components
import App from './App';
import { SearchContextProvider } from './context/SearchContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<SearchContextProvider>
<App />
</SearchContextProvider>
</React.StrictMode>
);
header.js
import { useContext } from "react";
import { SearchContext } from "../../context/SearchContext";
const Header = () => {
const { dispatch } = useContext(SearchContext)
const handleSearch = (e, dispatch) => {
dispatch({ type: "NEW_SEARCH", payload: { destination, "dates": "14-may-2122", options } })
navigate("/hotels", { state: { destination, dates, options } });
};
return (
<div className="header">
<button className="headerBtn" onClick={(e) => handleSearch(e, dispatch)}>
Search
</button>
</div>
)
}
You forgot .type
const SearchReducer = (state, action) => {
switch (action.type) { // <--- add `.type` here
case "NEW_SEARCH":
return action.payload;
case "RESET_SEARCH":
return INITIAL_STATE;
default:
return state
}
}
Related
I have a React app with a currency unit switch. I have a function to switch the unit and update redux so that every component that has called the unit will be re-rendered. The problem is the redux prop (storedCurrencyUnit) is UNDEFINED whenever I updated the value and call the update function to redux.
Switch component
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { updateCurrencyUnit } from '../../store/actions';
class FrontHeader extends Component {
handleCurrencyChange = (e) => {
const { updateCurrencyUnit, storedCurrencyUnit } = this.props;
updateCurrencyUnit(e.target.checked)
console.log("unit", storedCurrencyUnit) // this is UNDEFINED
this.setState({ aud: e.target.checked }, () => {
localStorage.setItem("currencyUnit", this.state.aud ? "AUD" : "USD")
})
}
render() {
return (
<Switch
checked={this.state.aud}
onChange={this.handleCurrencyChange}
color="secondary"
name="aud"
inputProps={{ 'aria-label': 'currencyUnit' }}
/>
)
}
}
const mapStateToProps = (state) => ({
storedCurrencyUnit: state.storedCurrencyUnit
})
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
updateCurrencyUnit: updateCurrencyUnit,
}, dispatch);
}
export default compose(connect(mapStateToProps, mapDispatchToProps))(FrontHeader);
currencyReducer.js
const storedCurrencyUnit = (state = null, action) => {
switch (action.type) {
case 'UPDATE_CURRENCYUNIT':
return action.payload;
default:
return state;
}
}
export default storedCurrencyUnit;
actions.js
export const updateCurrencyUnit = (updatedCurrencyUnit) => {
return {
type: 'UPDATE_CURRENCYUNIT',
payload: updatedCurrencyUnit,
}
}
How can I solve this?
You need to dispatch the action using dispatcher. only that will maintain the promise and let know the redux store.
this.props.dispatch(updateCurrencyUnit("some 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={{ ...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.
without redux it works so that not a api connection problem
I have an express app connected to react with proxy I have already managed to display my data in react but now i want to make that in redux soo:
There is my problem, i have maked all the reducers/action, store and combine reducer but I didn't see any datas in my page and i haven't any errors
There is my code :
Action
export const api = ext => `http://localhost:8080/${ext}`;
//
// ─── ACTION TYPES ───────────────────────────────────────────────────────────────
//
export const GET_ADVERTS = "GET_ADVERTS";
export const GET_ADVERT = "GET_ADVERT";
//
// ─── ACTION CREATORS ────────────────────────────────────────────────────────────
//
export function getAdverts() {
return dispatch => {
fetch("adverts")
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERTS, payload });
});
};
}
export function getAdvert(id) {
return dispatch => {
fetch(`adverts/${id}`)
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERT, payload });
});
};
}
reducer
import { combineReducers } from "redux";
import { GET_ADVERTS, GET_ADVERT } from "../actions/actions";
const INITIAL_STATE = {
adverts: [],
advert: {}
};
function todos(state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ADVERTS:
return { ...state, adverts: action.payload };
case GET_ADVERT:
return { advert: action.payload };
default:
return state;
}
}
const todoApp = combineReducers({
todos
});
export default todoApp;
index.js
//imports
const store = createStore(todoApp, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("app")
);
My advertlist page :
//imports..
class Adverts extends Component {
componentDidMount() {
this.props.getAdverts();
}
render() {
const { adverts = [] } = this.props;
return (
<div>
<Header />
<h1>Adverts</h1>
{adverts.map(advert => (
<li key={advert._id}>
<a href={"adverts/" + advert._id}>
{advert.name} {advert.surname}
</a>
</li>
))}
<Footer />
</div>
);
}
}
const mapStateToProps = state => ({
adverts: state.adverts
});
export default connect(
mapStateToProps,
{ getAdverts }
)(Adverts);
I think your problem is here:
function mapStateToProps(state) {
return {
**adverts: state.adverts**
};
}
It should work if you change state.adverts to state.todos.adverts:
function mapStateToProps(state) {
return {
adverts: state.todos.adverts
};
}
Because your reducer is called todos, and it has state { adverts }, that's why you cannot access adverts even tho they are obtained.
You can check out working version here: https://codesandbox.io/s/olqxm4mkpq
The problem is, when you just create a store with one reducer without using combine reducer, it is possible to refer it directly in the ContainerS, like this:
const mapStateToProps = state => {
return{
*name of var*: state.adverts /*direct refers to adverts*/
}
}
But, when it use combined-reducer , it has to refer to an exact reducer that you want to use.like this :
const mapStateToProps = state => {
return{
*name of var* : state.todos.adverts (indirect refers to adverts from combined-reducer todos)
}
}
I am implementing a project where the data going to be shared in different components. So I decided to use redux-react for state management.
I used redux react async api call to get data from api. However I got undefined when the component mounted for the first time and returned actual data.
However, when I tried to implement some function on returned data, I got this error:
"Cannot read property of undefined"
I can see the state in redux developer tools and it has data and the logs function display action correctly. I can not understand why I am getting undefined. Here is my code:
const initialState = {
candidate: {},
companies: [],
offers: [],
moreStatehere:...
}
Reducer for the candidate
export default function profileReducer(state = initialState, action) {
switch(action.type) {
case FETCH_POSTS_FAILURE:
return Object.assign({}, state, {
didInvalidate: true
})
case REQUEST_PROFILE:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_PROFILE:
return {
...state,
candidate: action.data
}
default:
return state;
}
}
root reducer
const rootReducer = combineReducers({
profiles: profileReducer
})
export default rootReducer;
create store
const composeEnhanser = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__||compose;
const loggerMiddleware = createLogger()
export default function configureStore() {
return createStore(
rootReducer,
composeEnhanser(applyMiddleware(thunkMiddleware,
loggerMiddleware))
);
}
index.js
const store = configureStore();
const app = (
<Provider store= {store}>
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>
)
ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();
action creator/api call
export function feachProfiles() {
return function (dispatch) {
dispatch(requestProfile)
return fetch(API_URL)
.then(
response => response.json(),
error => console.log('An error occurred.', error)
)
.then(json =>
dispatch(receiveProfile(json))
)
}
}
componentuse
class CandidatesList extends Component {
constructor (props){
super (props)
}
componentWillMount() {
this.props.feachProfiles();
}
handleClick() {
}
componentWillUnmount() {
}
render() {
const candidate = this.props.profiles.map(profile=>(
<div> </div>
));
return (
<div>
<ViewCandidate
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
profiles: state.profiles.candidate || []
}
}
const mapDispatchToProps = (dispatch) => {
return {
feachProfiles: bindActionCreators(feachProfiles, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CandidatesList);
action RECEIVE_PROFILE #
redux-logger.js:1 prev state {profiles: {…}}
redux-logger.js:1 action {type: "RECEIVE_PROFILE", data: {…}}
redux-logger.js:1 next state {profiles: {…}}
make sure to write this just before map function
if (this.props.profiles.length === 0) return null;
this.props.profiles should have array length of greater than 0
const candidate = this.props.profiles.map(profile=>(
<div> </div>
));
I am trying to pass in store as a property to AddTodo but I am getting the error: Cannot read property 'todos' of undefined
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { connect } from 'react-redux'
import { createStore } from 'redux'
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text
}
default:
return state
}
}
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
default:
return state
}
}
let store = createStore(todos)
let nextTodoId = 0
const AddTodo = () => {
let input;
//this works
console.log(store.getState())
//this doesn't
console.log(this.props.todos)
return (
<div>
<input ref={node => {input = node}}/>
<button onClick = {() => {
store.dispatch({
type: 'ADD_TODO',
id: nextTodoId++,
text: input.value
});
input.value = ''
}}>
Add Todo
</button>
</div>
)
};
store.subscribe(AddTodo)
ReactDOM.render(
<AddTodo
todos={store.getState()}
/>,
document.getElementById('root'));
I am a little confused why I am getting the error when printing this.props.todos. I thought I was passing in todos as a prop in <AddTodo todos={...}/>
Functional components work differently, they don't have a context of this, but their props are passed through the arguments of the function
To make it work, just change your function call of addToDos, like this:
const AddTodo = (props) => {
let input;
console.log(props.todos);
return (/* and the rest of your code...*/) ;
};
For the rest, as Arun Ghosh is saying, you should revisit your subscribe pattern, for example like this
store.subscribe(() => {
ReactDOM.render(
<AddTodo
todos={store.getState()}
/>,
document.getElementById('root'));
});
You should pass the store object and subscribe for changes
store.subscribe(() => {
// In our case the state will be updated when todos are set
console.log(store.getState().todos);
});