I am trying to make a redux pattern with useContext and useReducer. I create a component that creates a context (a reducer), and I also have the initial values. I declare my reduction with the foregoing, and return to the child component that is being provided by the values of my Reducer.
Then in the next image I have a view that will render a component that makes a form and that is wrapped in the ExpenseReducer component so that it has the values of my reduce.
In the form, I import the context and try to use the dispatch, but I get an error like "undefined"
My Code
import React from "react";
//Context
export const ExpenseContext = React.createContext();
// Reducer
const reducer = (state, action) => {
switch (action.type) {
case "HANDLE_SUBMIT":
return alert("Guardado");
default:
return state;
}
};
// Valores iniciales
const initialExpenses = [
{ id: "37c237f8-3004-4f69-9101-62f59ba4ce09", charge: "carne", amount: "20" },
{
id: "32bf7455-61c8-48d5-abe1-a38c93dcf1c8",
charge: "internet",
amount: "20"
},
{ id: "7e22c2e8-7965-41fe-9f39-236f266c9f24", charge: "ca", amount: "1" }
];
function ExpenseReducer({ children }) {
const [expenses, dispatch] = React.useReducer(reducer, initialExpenses);
return (
<ExpenseContext.Provider value={{ expenses, dispatch }}>
{children}
</ExpenseContext.Provider>
);
}
export default ExpenseReducer;
import React from "react";
// Components
import ExpenseForm from "../components/ExpenseForm";
import ExpenseReducer from "../reducers/ExpenseReducer/ExpenseReducer";
function ExpenseNew() {
return (
<ExpenseReducer>
<ExpenseForm />
</ExpenseReducer>
);
}
import React, { useContext } from "react";
import "./ExpenseForm.scss";
import { ThemeContext } from "../App";
import { ExpenseContext } from "../reducers/ExpenseReducer/ExpenseReducer";
const ExpenseForm = () =>
// {
// // edit,
// // charge,
// // amount,
// // handleCharge,
// // handleAmount,
// // handleSubmit
// }
{
// Theme
const { theme } = useContext(ThemeContext);
const { expenseContext } = useContext(ExpenseContext);
return (
<form
className="form"
onSubmit={expenseContext.dispatch("HANDLE_SUBMIT")}
>
<div className="form-group">
{/*To conect the value with the variable */}
<input
type="text"
className={`${theme} form-control`}
// id="charge"
// name="charge"
// placeholder="Gasto"
// value={charge}
// onChange={handleCharge}
/>
</div>
<div className="form-group">
{/*To conect the value with the variable */}
<input
type="number"
className={`${theme} form-control`}
// id="amount"
// name="amount"
// placeholder="Cuanto"
// value={amount}
// onChange={handleAmount}
/>
<textarea
placeholder="Descripción"
className={`${theme} form-control`}
id=""
cols="30"
rows="10"
></textarea>
</div>
<button type="submit" className={`btn ${theme}`}>
{true ? "Editar" : "Guardar"}
</button>
</form>
);
};
export default ExpenseForm;
According to the docs:
Don’t forget that the argument to useContext must be the context object itself:
Correct: useContext(MyContext)
Incorrect: useContext(MyContext.Consumer)
Incorrect: useContext(MyContext.Provider)
Your reducer is returning the provider, not the context object. Hence it results in expenseContext being undefined.
Related
In my project using react-hook-form to update and create details. There is an issue in the update form, the values are not updating properly, and the code
countryupdate.tsx
import React from 'react'
import { useQuery } from 'react-query'
import { useParams } from 'react-router-dom'
import { useCountryUpdate } from '../api/useCountryUpdate'
import { getDetails, useDetails } from '../api/useDetails'
import { CountryCreateUpdateForm } from '../forms/createupdateForm'
interface data{
id: string,
name: string
}
export const CountryUpdatepage = () => {
const { dataId }: any = useParams()
const { data, isLoading, isError } = useQuery(['details', dataId], () => getDetails(dataId), {
enabled: !!dataId,
});
const { mutateAsync } = useCountryUpdate();
const onFormSubmit = async() =>{
console.log("mutate", {...data})
await mutateAsync({...data, dataId})
}
return (
<div>
<h3>Update Details</h3>
<CountryCreateUpdateForm defaultValues={data} onFormSubmit={onFormSubmit} isLoading={undefined}/>
</div>
)
}
Here, when console the value inside onFormSubmit, it shows the same data in the previous state
createupdateform.tsx
import { useState } from "react"
import { useCountryCreate } from "../api/usecountrycreate"
import { useForm } from "react-hook-form"
export const CountryCreateUpdateForm = ({ defaultValues, onFormSubmit, isLoading }: any) => {
// console.log("name", defaultValues.data.name)
const { register, handleSubmit } = useForm({ defaultValues:defaultValues?.data });
const onSubmit = handleSubmit((data) => {
onFormSubmit(data)
})
return (
<form onSubmit={onSubmit}>
<div>
<label>Name</label>
<input {...register('name')} type="text" name="name" />
</div>
<button type="submit" >submit</button>
</form>
)
}
I am a beginner in react typescript, Please give me suggestions to solve this problem.
in countryupdate.tsx
the data is undefined at the beggining, so defaultValue of form is not updated after that;
it should help:
return (
<div>
<h3>Update Details</h3>
{data?.data && <CountryCreateUpdateForm defaultValues={data} onFormSubmit={onFormSubmit} isLoading={undefined}/>
}
</div>
)
I have defined a context Transaction which takes an object and a function.
In AppProvider Transaction.Provider is returned.
The code is of GlobalState.tsx file:
import { createContext, useState } from "react";
export interface IProviderProps {
children?: any;
}
type Cat = {
id: number;
text: string;
amount: number;
}
type Ca =Cat[]
export const initialState = {
state: [
{id:4, text:'hi', amount:234},
{id:3, text:'hd', amount:-234},
{id:1, text:'hs', amount:34}
],
setState: (state: Ca) => {}
}
console.log(initialState.state)
export const Transaction = createContext(initialState);
export const AppProvider = (props: IProviderProps) => {
const [state, setState] = useState(initialState.state);
console.log(state);
return <Transaction.Provider value={{state, setState}}>{props.children}</Transaction.Provider>;
};
In App.tsx I have passed the Provider:
import React, { useState } from 'react';
import './App.css';
import { Header } from "./Components/Header";
import { Balance } from './Components/Balance';
import { IncomeExpense } from "./Components/Income_Expense";
import { TransactionHistory } from "./Components/TransactionHistory";
import { AddTransaction } from "./Components/AddTransaction";
import { AppProvider } from './Context/GlobalState'
function App() {
const [islit, setlit] = useState(true);
return (
<AppProvider>
<div className={`${islit? '': 'dark'} body`}>
<Header islit={islit} setlit={setlit} />
<div className="container">
<Balance />
<IncomeExpense />
<TransactionHistory />
<AddTransaction />
</div>
</div>
</AppProvider>
);
}
export default App;
I am trying to change 'state' with 'setState' but it is not working:
import React, { useState, useContext } from 'react';
import { Transaction} from '../Context/GlobalState';
export const AddTransaction = () => {
const initialState = useContext(Transaction);
const [Incexp, setIncExp] = useState('income');
const [text, settext] = useState('');
const [amount, setamount] = useState(0);
const transactions = initialState.state;
const settransaction = initialState.setState;
function Addition(e: any) {
e.preventDefault();
settext('');
setamount(0);
transactions.push({id:Math.floor(Math.random() * 100000000), text:text, amount:Incexp==='income'? +amount: -amount})
settransaction(transactions);
console.log(transactions);
}
return (
<div>
<h3>Add Transaction</h3>
<form onSubmit={Addition}>
<label htmlFor="description">Text</label>
<input type="text" id="description" placeholder="Enter description..." value={text} onChange={(e) => { settext(e.target.value) }} required />
<label htmlFor="amount">Amount</label>
<input type="number" id="amount" placeholder="Enter Amount..." value={amount === 0 ? '' : amount} onChange={(e) => { setamount(parseInt(e.target.value)) }} required />
<div className="Inc-Exp">
<div>
<input type="radio" id="income" name="balance" defaultChecked onClick={()=>{setIncExp('income')}}/>
<label htmlFor="income" className="inc-col">Income</label>
</div>
<div>
<input type="radio" id="expense" name="balance" onClick={()=>{setIncExp('expense')}}/>
<label htmlFor="expense" className="exp-col">Expense</label>
</div>
</div>
<input className="btn" type="submit" value="Addtransaction" />
</form>
</div>
)
}
Another child component:
import React, { useContext } from 'react';
import { Transaction } from '../Context/GlobalState';
export const Balance = () => {
const initialState = useContext(Transaction);
const transactions = initialState.state;
var total=0;
transactions.map((transaction) => total+=transaction.amount)
return (
<div>
<h4>Your Balance</h4>
<h1 className={`${total > 0 ? 'plus' : ''} ${total < 0 ? 'minus' : ''}`}>${total}</h1>
</div>
)
}
Every time I click on a button Add Transaction. I want it to update state. but it is not updating.
The state is not update because you are not changing the reference of the object transactions, do it like below
function Addition(e: any) {
e.preventDefault();
settext('');
setamount(0);
settransaction([...transactions,{id:Math.floor(Math.random() * 100000000), text:text, amount:Incexp==='income'? +amount: -amount} ]);
console.log(transactions);
}
In this case the object you pass to settransaction will have a new reference the react will update the state
Please change setState to a callback variant setState((previousState) => {...}) as:
function Addition(e: any) {
e.preventDefault();
settext('');
setamount(0);
transactions.push({id:Math.floor(Math.random() * 100000000), text:text, amount:Incexp==='income'? +amount: -amount})
settransaction(transactions);
console.log(transactions);
}
to
function Addition(e: any) {
e.preventDefault();
settext('');
setamount(0);
settransaction((prevState) => {
return prevState.push({id:Math.floor(Math.random() * 100000000), text:text, amount:Incexp==='income'? +amount: -amount});
});
}
The console.log may not show updated value, as re-render would be required to get updated context value.
But after context update a re-render would going to be trigger by react, and thus the component responsible to show updated state will eventually display the updated state.
I am trying to make my App.js route to my People.jsx etc.. but it is not working correctly. I hope I could fix the issue from there if I could make this work. I have been trying to do this for about 2 hours with the 20 min rule but this one I need help with. I have tried other variations but my goal is to get the,theID over to Person as well. I am thinking about using {useContext } to do that but I can't even get it to route. I wish I knew what I was doing wrong so I could correct it but other people are using different types of routers and I was confused with them even more.
I updated it with links still a no go for me any other suggestions?
App.js
import './App.css';
import { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import People from './components/People'
import Planet from './components/Planets'
import Starship from './components/Starships'
import { Router, Link } from '#reach/router';
function App() {
const [starwarsState, setStarwarsState] = useState('')
const [theID, setTheID] = useState('')
const selectedState = (e) => {
setStarwarsState(e.target.value)
}
const switchItem = () => {
switch (starwarsState) {
case 'people':
<Link path='/people/' />;
break;
case 'planets':
<Link path="/planets/" />;
break;
case 'starships':
<Link path='/starships/' />;
break;
default:
return null;
}
}
const addId = e => {
setTheID(e.target.value)
console.log(theID)
}
return (
<div className='App'>
<header className='App-header' >
Search For:
<select onChange={selectedState} className='form-control-lg bg-dark text-white'>
<option value='people' active >People</option>
<option value='planets' >Planets</option>
<option value='starships' >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' onClick={switchItem} >Search Item</button>
<Router>
<People path='/people/' />
<Planet path="/planets/" />
<Starship path='/starships/' />
</Router>
</header>
{starwarsState}
</div>
)
}
export default App;
People.jsx
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import { Link } from '#reach/router';
const People = props => {
const [peopleData, setpeopleData] = useState([]);
useEffect(() => {
axios.get(`https://swapi.dev/api/people/${props.theID}`)
.then(response => { setpeopleData(response.data) })
console.log(peopleData)
}, []);
return (
<div>
<span> the People have spoken</span>
<Link to='/people' />
</div>
)
}
export default People;
Issues
You aren't actually rendering the routes/links from switchItem since onClick callbacks can't return renderable UI directly to the render method.
Solution
Unconditionally render your routes all at the same time within a single Router and imperatively navigate to them in the switchItem handler.
App
...
import { Router, navigate } from "#reach/router";
...
function App() {
const [starwarsState, setStarwarsState] = useState("");
const [theID, setTheID] = useState("");
...
const switchItem = () => {
switch (starwarsState) {
case "people":
navigate("/people"); // <-- imperative navigation
break;
case "planets":
navigate("/planets");
break;
case "starships":
navigate("/starships");
break;
default:
return null;
}
};
return (
<div className="App">
<header className="App-header">
Search For:
<select
onChange={selectedState}
value={starwarsState}
className="form-control-lg bg-dark text-white"
>
<option disabled value="">
Choose Path
</option>
<option value="people">
People
</option>
<option value="planets">Planets</option>
<option value="starships">Starships</option>
</select>
ID:
<input
type="text"
onChange={addId}
className="form-control-lg col-sm-1 bg-dark text-white"
/>
<button className="btn-lg btn-warning" onClick={switchItem}>
Search Item
</button>
</header>
<Router>
<People path="/people" theID={theID} /> // <-- pass `theID` state as prop
<Planet path="/planets" />
<Starship path='/starships' />
</Router>
</div>
);
}
People
const People = ({ theID }) => {
const [peopleData, setpeopleData] = useState([]);
useEffect(() => {
axios.get(`https://swapi.dev/api/people/${theID}`)
.then(response => { setpeopleData(response.data) });
}, [theID]);
return (
<div>
<div>The ID: {theID}</div>
<span>the People have spoken</span>
</div>
);
};
Use Imperative Routing (not switch statement) with Event Handlers
Your code is using a switch statement in combination with the switchItem() function. This is not how to redirect the user imperatively (meaning, through something other than a link clicked directly by the user).
To imperatively route your users, use the navigate method.
Via Reach Router docs (link):
Sometimes you need to navigate in response to something other than the user clicking on a link. For this we have navigate. Let’s import navigate.
import {
Router,
Link,
navigate
} from "#reach/router"
In your case, the entire switch statement can be rewritten as follows:
useEffect(() => navigate(`/${starwarsState}`), [starwarsState])
useEffect will watch for changes to the starwarsState, which is either going to be 'people', 'planets', or 'starships'. Once a change occurs, it will navigate the user to the corresponding path.
Solution: Routing Only
The following solution doesn't implement axios, it focuses solely on the client-side routing logic.
I found some other issues with your code when I was working through a solution. Here is a version that I wrote that implements parameter-level routing while also making some other cleanup (refactoring the swapi categories into a config object, etc).
App.js
import React, { useState, useEffect } from 'react'
import { useWhatChanged } from "#simbathesailor/use-what-changed";
import { Router, Link, navigate } from "#reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';
function App() {
// destructure categories from config
const { people, planets, starships } = config.categories
// initialize state
const [starwarsState, setStarwarsState] = useState(people);
const [theID, setTheID] = useState('');
// log updates to ID and starwarsState
useWhatChanged([starwarsState, theID], 'starwarsState, theID')
// change state on input from user
const addId = e => setTheID(e.target.value)
const selectCategory = (e) => setStarwarsState(e.target.value)
// route the user based on starwarsState
useEffect(() => navigate(`/${starwarsState}`), [starwarsState])
// search swapi based on category and id
const searchSwapi = e => {
e.preventDefault()
navigate(`/${starwarsState}/${theID}`)
}
return (
<div className="App">
<header className='App-header' >
Search For:
<form onSubmit={searchSwapi}>
<select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
<option value={people} >People</option>
<option value={planets} >Planets</option>
<option value={starships} >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' >Search Item</button>
</form>
</header>
<Router>
<People path='/people/'>
<Person path=':personId' />
</People>
<Planets path="/planets/">
<Planet path=':planetId' />
</Planets>
<Starships path='/starships/'>
<Starship path=':starshipId' />
</Starships>
</Router>
</div>
)
}
const config = {
categories: {
people: 'people',
planets: 'planets',
starships: 'starships'
}
}
export default App;
Planets.js
import React from 'react'
export const Planets = props => {
return (
<div>
<span> the Planets have spoken</span>
{props.children}
</div>
)
}
export const Planet = props => {
return (
<div>
Planet Data
</div>
)
}
People.js
import React, { useState, useEffect } from 'react'
export const People = props => {
return (
<div>
<span> the People have spoken</span>
{props.children}
</div>
)
}
export const Person = props => {
return (
<div>
Person Data
</div>
)
}
Starships.js
import React from 'react'
export const Starships = props => {
return (
<div>
<span> the Starships have spoken</span>
{props.children}
</div>
)
}
export const Starship = props => {
return (
<div>
Starship Data
</div>
)
}
[UPDATE] Solution: Routing with API Calls
The following solution takes the code from above and refactors it using the state management pattern proposed by Leigh Halliday. The solution adds three things:
useContext for managing global state
React.memo() for memoizing AppContent component
react-query for managing remote API calls to SWAPI.
View code on GitHub
App.js
// App.js
import React, {
useState,
useEffect,
createContext,
useContext,
memo
} from 'react'
import { ReactQueryDevtools } from "react-query-devtools";
import { useWhatChanged } from "#simbathesailor/use-what-changed";
import { Router, navigate } from "#reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';
import Axios from 'axios';
// APP w/ CONTEXT PROVIDER
export default function App() {
return (
<>
<StarwarsProvider>
<AppContent />
</StarwarsProvider>
<ReactQueryDevtools initialIsOpen={false} />
</>
)
}
// CREATE CONTEXT
export const StarwarsContext = createContext()
// CONTEXT PROVIDER
function StarwarsProvider({ children }) {
// import categories
const categories = config.categories
// destructure default category of search selection
const { people } = categories
// initialize state
const [category, setCategory] = useState(people);
const [theID, setTheID] = useState('');
return (
<StarwarsContext.Provider value={{
category,
setCategory,
theID,
setTheID,
categories,
fetchStarwarsData
}}>
<AppContent />
</StarwarsContext.Provider>
)
}
async function fetchStarwarsData(category, id) {
if (!id) {
return
}
const response = await Axios.get(
`https://swapi.dev/api/${category}/${id}`
).then(res => res.data)
// const data = await response.json()
const data = response
// console.log(data)
return data
}
// APP CONTENT
const AppContent = memo(() => {
// import global state into component
const { category, setCategory } = useContext(StarwarsContext)
const { theID, setTheID } = useContext(StarwarsContext)
// destructure categories
const { categories: { people, planets, starships } } = useContext(StarwarsContext)
// log updates to ID and category
useWhatChanged([category, theID], 'category, theID')
// change state on input from user
const addId = e => setTheID(e.target.value)
const selectCategory = (e) => setCategory(e.target.value)
// route the user based on category
useEffect(() => navigate(`/${category}`), [category])
// search swapi based on category and id
const searchSwapi = e => {
e.preventDefault()
navigate(`/${category}/${theID}`)
}
return (
<div className="App">
<header className='App-header' >
Search For:
<form onSubmit={searchSwapi}>
<select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
<option value={people} >People</option>
<option value={planets} >Planets</option>
<option value={starships} >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' >Search Item</button>
</form>
</header>
<Router>
<People path='/people/'>
<Person path=':personId' fetchStarwarsData />
</People>
<Planets path="/planets/">
<Planet path=':planetId' fetchStarwarsData />
</Planets>
<Starships path='/starships/'>
<Starship path=':starshipId' fetchStarwarsData />
</Starships>
</Router>
</div>
)
})
const config = {
categories: {
people: 'people',
planets: 'planets',
starships: 'starships'
}
}
People.js
// People.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const People = props => {
return (
<div>
<span> the People have spoken</span>
{props.children}
</div>
)
}
export const Person = () => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Planets.js
// Planets.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const Planets = props => {
return (
<div>
<span> the Planets have spoken</span>
{props.children}
</div>
)
}
export const Planet = props => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Starships.js
// Starships.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const Starships = props => {
return (
<div>
<span> the Starships have spoken</span>
{props.children}
</div>
)
}
export const Starship = () => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Im really rellied about this issue, the case im trying to use a Material UI slider with redux.
here the slider component:
import { Slider } from '#material-ui/core'
const RangeSlider = ({handleRange, range}) => {
const handleChange = (event, newValues) => {
handleRange(newValues)
}
return (
<Slider
value={range}
onChange={handleChange}
valueLabelDisplay="auto"
aria-labelledby="range-slider"
min={0}
max={100}
/>
)
}
export default RangeSlider
here the filter component with redux logic:
import {
Button,
Typography as Tp,
Select,
MenuItem,
Accordion,
AccordionDetails,
AccordionSummary,
} from "#material-ui/core";
import { ExpandMore } from "#material-ui/icons";
import { RangeSlider } from "../components";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { filterUpdate } from "../redux/actions/filter";
const Filter = ({ models }) => {
const [range, setRange] = useState([0,60]);
const [modelSelected, setModelSelected] = useState("None");
const handleRange = (newValue) => setRange(newValue);
const handleModel = (event) => setModelSelected(event.target.value);
const dispatch = useDispatch();
const handleSubmit = () => {
let filterValues = {
range,
modelSelected,
};
dispatch(filterUpdate(filterValues));
};
return (
<div className="container mt-3 ">
<Accordion>
<AccordionSummary
expandIcon={<ExpandMore />}
aria-controls="panel-filter-content"
id="panel-filter-header"
>
<Tp>Sort your search</Tp>
</AccordionSummary>
<AccordionDetails className='d-flex align-items-center bg-light pb-3'>
<div className='col-md-6'>
<Tp className="mt-3">Pick up a price range</Tp>
<RangeSlider handleRange={handleRange} range={range} />
</div>
<div className="col">
<div className="col text-center pb-3">
<Tp className="mt-3">Sort by model</Tp>
<Select value={modelSelected} onChange={handleModel}>
{models.map((model) => {
return (
<MenuItem key={model} value={model}>
{model}
</MenuItem>
);
})}
</Select>
</div>
</div>
<div className=" col mt-5 text-center">
<Button onClick={handleSubmit} variant="outlined" color="primary">
Submit
</Button>
</div>
</AccordionDetails>
</Accordion>
</div>
);
};
export default Filter;
Redux action logic:
import { createAction } from '#reduxjs/toolkit'
import types from '../types'
export const filterUpdate = createAction(types.FILTER_UPDATE)
Here Reducer:
import { createReducer } from '#reduxjs/toolkit'
import { filterUpdate } from '../actions/filter'
const reducer = createReducer({
range: [0,60],
model: 'None'
}, {
[filterUpdate]: (state, action) => {
state.range = action.payload.range
state.model = action.payload.modelSelected
}
})
export default reducer
The error appear when i include the redux logic, when i tested with a console.log was fine, but with the dispatch, appear this error, also, its a random error because i can test to dispatch 2-3 times without error, but the next this error appear
What can i do to deal with that?
Thanks in advance.
In my case, it fails to sort function inside the Slider.
That because the value that I passed into the component was a read-only (immutable).
So to avoid this error I should to pass in the value prop the copy of range array. Like this:
<Slider
value={[...range]}
// ....other props
/>
I am not sure what is the problem, but your reducer is definitely wrong. You do:
const reducer = createReducer({
range: [0,60],
model: 'None'
}, {
[filterUpdate]: (state, action) => {
state.range = action.payload.range
state.model = action.payload.modelSelected
}
})
You probably meant to do this:
const reducer = createReducer({
range: [0,60],
model: 'None'
}, {
[filterUpdate]: (state, action) => ({
range: action.payload.range,
model: action.payload.modelSelected,
})
})
Also you are not actually pulling state from redux store. You are importing useSelector and not calling it anywhere. Whatever useState and useDispatch return are independent from each other in your code.
it seems that i am unable to get the form to rerender on each keystroke. I must be doing something wrong I just cannot tell what it is. The GetStartedForm function never runs twice, and neither does the function that contains the Field input.
use of redux form
const GetStartedForm = (props) => {
const {
handleSubmit,
pristine
} = props;
return (
<Form onSubmit={handleSubmit}>
<Field
name="email"
component={FormField}
type="text"
size="xl"
placeholder="Email Address"
autoComplete="email"
validate={[required(), email()]}
/>
<Button
disabled={pristine}>Get Started</Button>
</Form>
);
}
export default reduxForm({ form: 'getstarted' })(GetStartedForm);
implementation
<GetStarted onSubmit={e => {
console.log(e);
}} />
input field
const TextField = props => {
const { input, meta, size } = props;
console.log(props);
const properties = meta.uncontrolled ? {
defaultValue: props.defaultValue
} : {
value: input.value
};
return (
<React.Fragment>
{props.label && (
<label htmlFor={input.name} className="form__label">
{props.label}
{props.required ? ' *' : null}
</label>
)}
<Input
type={props.type ? props.type : 'text'}
className={props.className}
name={input.name}
id={input.name}
size={props.size}
readOnly={props.readOnly}
onChange={input.onChange}
autoFocus={props.autoFocus}
autoComplete={props.autoComplete}
placeholder={props.placeholder}
{...properties}
/>
{meta.touched && meta.error ? (
<div className="form__field-error">{meta.error}</div>
) : null}
</React.Fragment>
);
};
when i pass in uncontrolled like this i am able to get the text to at least show up on my screen, but i seem to be unable to get the form function to run again, which means that my button is stuck at disabled (pristine)
<Field
name="email"
component={FormField}
type="text"
size="xl"
placeholder="Email Address"
autoComplete="email"
validate={[required(), email()]}
meta={{uncontrolled: true}}
/>
yes reducers are set up
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { fromJS } from 'immutable';
import { combineReducers } from 'redux-immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import {
LOGOUT_USER_REQUEST
} from 'constants'
import globalReducer from 'containers/App/reducer';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
/*
* routeReducer
*
* The reducer merges route location changes into our immutable state.
* The change is necessitated by moving to react-router-redux#5
*
*/
// Initial routing state
const routeInitialState = fromJS({
location: null,
});
/**
* Merge route into the global application state
*/
function routeReducer(state = routeInitialState, action) {
switch (action.type) {
/* istanbul ignore next */
case LOCATION_CHANGE:
return state.merge({
location: action.payload,
});
default:
return state;
}
}
/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers) {
const composite = combineReducers({
route: routeReducer,
global: globalReducer,
language: languageProviderReducer,
form: formReducer,
...injectedReducers,
});
return rootReducer;
function rootReducer(state_, action) {
let state = state_;
if (action.type === LOGOUT_USER_REQUEST) {
state = null;
}
return composite(state, action);
}
}
I just realized that this react boilerplate uses immutable as its base so i had to import redux-form/immutable everywhere i was using redux-form