I am having trouble getting reducer to work with React context. In buttonbar.js
, there are two buttons that are supposed to update the state. The state will be updated by filtering the data in the current. The buttons are being clicked and I don't get any errors, but it's also not doing anything. I think the issue is with the reducer.
context.js
import React, { useState, useEffect } from "react";
import * as moment from "moment";
import axios from "axios";
export const Context = React.createContext();
const url = "https://projects.fivethirtyeight.com/polls/polls.json";
export const filterReducer = (state, action) => {
switch (action.type) {
case "SHOW_ALL":
return state.polls;
case "SHOW_APPROVAL":
return state.polls.filter(e => e.type === "trump-approval");
default:
return state.polls;
}
};
export function Provider({ children }) {
let intialState = {
polls: [],
dispatch: action => this.setState(state => filterReducer(state, action))
};
const [state, setState, dispatch] = useState(intialState);
useEffect(() => {
var dateRange = moment()
.subtract(7, "days")
.calendar();
axios
.get(url)
.then(res => {
setState({
polls: res.data
.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange))
.reverse()
});
}, [])
.catch(error => console.log(error));
}, []);
return (
<Context.Provider value={[state, setState, dispatch]}>
{children}
</Context.Provider>
);
}
// export const Consumer = Context.Consumer;
buttonbar.js
import React, { useContext, useState, useEffect, useReducer } from "react";
import { Context, filterReducer } from "../context";
const ButtonBar = () => {
const [state, setState] = useContext(Context);
const [filter, dispatch] = useReducer(filterReducer, state);
const showAll = () => {
dispatch({ type: "SHOW_ALL" });
console.log("showAll clicked");
};
const showApproval = () => {
dispatch({ type: "SHOW_APPROVAL" });
console.log("showApproval clicked");
};
return (
<div class="mb-2">
<button class="btn btn-primary btn-sm" name="all" onClick={showAll}>
All
</button>{" "}
<button
class="btn btn-primary btn-sm"
name="trump approval"
onClick={showApproval}
>
Trump Approval
</button>
</div>
);
};
export default ButtonBar;
Your using the useReducer Hook incorrectly, just because you are using the useReducer hook in your component, does not mean you are updating the global context state.
So in your buttonbar.js
const [filter, dispatch] = useReducer(filterReducer, state);
const showAll = () => {
dispatch({ type: "SHOW_ALL" });
console.log("showAll clicked");
};
const showApproval = () => {
dispatch({ type: "SHOW_APPROVAL" });
console.log("showApproval clicked");
};
You are updating your state correctly using a reducer but it will only update local component state not the global context state.
This will seem counter-intuitive if your are coming from redux.
In context the state is contained and changed in the parent component so simply move the above code to the parent component, then access it through context.
export function Provider({ children }) {
let intialState = {
polls: [],
dispatch: action => this.setState(state => filterReducer(state, action))
};
// 2 args not 3
const [state, setState] = useState(intialState);
const [filter, dispatch] = useReducer(filterReducer, state);
const showAll = () => {
dispatch({ type: "SHOW_ALL" });
console.log("showAll clicked");
};
const showApproval = () => {
dispatch({ type: "SHOW_APPROVAL" });
console.log("showApproval clicked");
};
pass the state and functions to the value prop
<Context.Provider value={{
showAllProp: () => showAll(),
showApprovalProp: () => showApproval(),
filterProp: filter }}>
{children}
</Context.Provider>
Then you can access these values and functions in the child component with the value props.
const context = useContext(Context);
<button class="btn btn-primary btn-sm" name="all" onClick={context.showAllProp}>
All
</button>{" "}
<button
class="btn btn-primary btn-sm"
name="trump approval"
onClick={context.showApprovalProp}
>
This is essentially how you connect your context with your components.
There are a few things, you are not doing correctly.
First, you are using initialState with a dispatch method and you are instead trying to get this dispatch value from useState
using the third argument which is incorrect
Second, Since you are using the reducer pattern, its better to make use if useReducer
hook
Third, you must never filter the data in reducer otherwise the next time you want to show all the data, the complete data will be lost and only the filtered data will remain. Instead you must have selectors for it.
Relevant code:
import React, {
useEffect,
useContext,
useReducer,
useMemo,
useState
} from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import moment from "moment";
import axios from "axios";
export const Context = React.createContext();
const url = "https://projects.fivethirtyeight.com/polls/polls.json";
export const filterReducer = (state, action) => {
switch (action.type) {
case "ADD_POLLS":
console.log(action.payload);
return action.payload;
default:
return state.polls;
}
};
export function Provider({ children }) {
const [state, dispatch] = useReducer(filterReducer);
useEffect(() => {
var dateRange = moment()
.subtract(7, "days")
.calendar();
axios
.get(url)
.then(res => {
dispatch({
type: "ADD_POLLS",
payload: res.data
.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange))
.reverse()
});
}, [])
.catch(error => console.log(error));
}, []);
return (
<Context.Provider value={[state, dispatch]}>{children}</Context.Provider>
);
}
const ButtonBar = () => {
const [polls] = useContext(Context);
const [state, setState] = useState(polls);
useEffect(() => {
setState(polls);
}, [polls]);
const filterResult = useMemo(() => {
return filter => {
switch (filter) {
case "SHOW_ALL":
setState(polls);
break;
case "SHOW_APPROVAL":
setState(polls.filter(e => e.type === "trump-approval"));
break;
default:
return;
}
};
}, [polls]);
return (
<div class="mb-2">
<button
class="btn btn-primary btn-sm"
name="all"
onClick={() => filterResult("SHOW_ALL")}
>
All
</button>{" "}
<button
class="btn btn-primary btn-sm"
name="trump approval"
onClick={() => filterResult("SHOW_APPROVAL")}
>
Trump Approval
</button>
<div>{(state || []).length}</div>
<pre>{JSON.stringify(state, null, 4)}</pre>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider>
<ButtonBar />
</Provider>,
rootElement
);
Working demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With